Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions adapters/agy/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# AGY adapter

Generated AGY/Antigravity adapters project AgentFlow SDLC skills into skill-compatible local adapter surfaces.

Rules:

- Use Windows-safe generated files, not symlinks.
- The AGY-facing skill must run deterministic CLI validators and interpret JSON results.
- `.agy` or AGY plugin folders are generated adapter surfaces only.
9 changes: 9 additions & 0 deletions adapters/claude-code/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# Claude Code adapter

Generated Claude Code adapters project AgentFlow SDLC skills into `.claude/skills/`.

Rules:

- Canonical source remains under `skills/` and product docs/schemas.
- Generated adapters instruct Claude to use deterministic `agentflow-sdlc sdlc ...` validators.
- `.claude` settings/skills are generated or harness-owned local artifacts, not source of truth.
9 changes: 9 additions & 0 deletions adapters/codex/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# Codex adapter

Generated Codex adapters project AgentFlow SDLC skills into Codex-compatible skill/plugin surfaces.

Rules:

- Keep plugin metadata generated from canonical product payload.
- Do not install duplicate modes for one harness: choose skills-only, native-plugin, or cli-managed.
- Codex adapters delegate validation to `agentflow-sdlc` CLI commands.
9 changes: 9 additions & 0 deletions adapters/pi/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# Pi adapter

Generated Pi adapters project AgentFlow SDLC skills into `.pi/skills/` or future Pi package surfaces.

Rules:

- `.pi` is not canonical product source.
- Pi adapter copies are generated from harness-neutral `skills/` sources.
- Pi workflows must record launcher/executor/transport/delegation boundary like every other harness.
9 changes: 2 additions & 7 deletions bin/cli.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -111,13 +111,8 @@ function handleSdlc(rest, targetDir) {
return runScript('scripts/validate-sdlc-skill.mjs', pass, targetDir)
if (subcommand === 'validate-agent')
return runScript('scripts/validate-sdlc-agent.mjs', pass, targetDir)
if (subcommand === 'audit') return runScript('scripts/validate-sdlc-config.mjs', pass, targetDir)
if (subcommand === 'migrate') {
process.stdout.write(
'SDLC migration is preview-first. Initial v1 migrator validates config and reports no writes.\n',
)
return runScript('scripts/validate-sdlc-config.mjs', pass, targetDir)
}
if (subcommand === 'audit') return runScript('scripts/sdlc-audit.mjs', pass, targetDir)
if (subcommand === 'migrate') return runScript('scripts/sdlc-migrate.mjs', pass, targetDir)
process.stderr.write(`Usage:
agentflow-sdlc sdlc validate [--target <dir>] [--json]
agentflow-sdlc sdlc validate-issue --path <issue.json> [--json]
Expand Down
8 changes: 8 additions & 0 deletions lib/framework-files.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -232,6 +232,11 @@ export const FRAMEWORK_FILES = [
'skills/sdlc-audit/templates/audit-report.md',
'skills/sdlc-audit/checklists/workflow-compliance.md',
'skills/sdlc-audit/evals/audit-findings-cases.md',
'adapters/claude-code/README.md',
'adapters/agy/README.md',
'adapters/codex/README.md',
'adapters/pi/README.md',
'manifests/product-payload.json',
'agents/templates/role-pass.md',
'agents/templates/pr-manifest.md',
'agents/templates/workflow-status-comment.md',
Expand All @@ -253,6 +258,9 @@ export const FRAMEWORK_FILES = [
'scripts/validate-sdlc-release.mjs',
'scripts/validate-sdlc-skill.mjs',
'scripts/validate-sdlc-agent.mjs',
'scripts/sdlc-audit.mjs',
'scripts/sdlc-migrate.mjs',
'scripts/sdlc-sandbox-smoke.mjs',
'scripts/validate-extension-packs.mjs',
'scripts/extension-pack.mjs',
'scripts/resolve-role-route.mjs',
Expand Down
2 changes: 1 addition & 1 deletion lib/sdlc-state.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,7 @@ export function validateNoForbiddenEvidenceText(text = '') {
if (/BEGIN (RSA|OPENSSH|PRIVATE) KEY|api[_-]?key\s*=|secret\s*=|token\s*=/i.test(text)) {
findings.push(finding('blocker', 'evidence.secrets', 'possible secret in durable evidence'))
}
if (/raw transcript|tool input|full log|prompt:/i.test(text) && text.length > 1000) {
if (/(^|\n)(User|Assistant|Tool|System):/i.test(text) && text.length > 2000) {
findings.push(
finding('high', 'evidence.raw-log', 'possible raw prompt/transcript/log in durable evidence'),
)
Expand Down
40 changes: 40 additions & 0 deletions manifests/product-payload.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
{
"version": 1,
"entries": [
{
"source": "docs/sdlc-definition.md",
"target": "docs/sdlc-definition.md",
"ownership": "managed"
},
{
"source": "docs/sdlc-packaging.md",
"target": "docs/sdlc-packaging.md",
"ownership": "managed"
},
{
"source": "defaults/sdlc.config.json",
"target": "sdlc.config.json",
"ownership": "seed-once"
},
{
"source": "schemas/sdlc-config.schema.json",
"target": "schemas/sdlc-config.schema.json",
"ownership": "managed"
},
{
"source": "skills/sdlc-definition/SKILL.md",
"target": "skills/sdlc-definition/SKILL.md",
"ownership": "managed"
},
{
"source": "skills/sdlc-migration/SKILL.md",
"target": "skills/sdlc-migration/SKILL.md",
"ownership": "managed"
},
{
"source": "skills/sdlc-audit/SKILL.md",
"target": "skills/sdlc-audit/SKILL.md",
"ownership": "managed"
}
]
}
80 changes: 80 additions & 0 deletions scripts/sdlc-audit.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
#!/usr/bin/env node
import { existsSync, readFileSync } from 'node:fs'
import { join } from 'node:path'
import {
loadSdlcConfig,
validateSdlcConfigShape,
validateNoForbiddenEvidenceText,
finding,
report,
} from '../lib/sdlc-state.mjs'

const args = process.argv.slice(2)
const json = args.includes('--json')
const target = args.includes('--target') ? args[args.indexOf('--target') + 1] : process.cwd()
const findings = []

const config = loadSdlcConfig(target)
findings.push(...validateSdlcConfigShape(config).findings)

for (const dir of ['.pi', '.claude', '.agy', '.codex']) {
if (existsSync(join(target, dir))) {
findings.push(
finding(
'info',
'harness.generated-surface',
`${dir} present; ensure no canonical product source is stored there`,
{ source: dir },
),
)
}
}

for (const skill of ['sdlc-definition', 'sdlc-migration', 'sdlc-audit']) {
const skillPath = join(target, 'skills', skill, 'SKILL.md')
if (!existsSync(skillPath))
findings.push(finding('high', 'skill.missing', `missing ${skill} skill`, { source: skillPath }))
}

for (const path of ['docs/sdlc-definition.md', 'schemas/sdlc-config.schema.json']) {
if (!existsSync(join(target, path)))
findings.push(finding('blocker', 'sdlc.required-file', `missing ${path}`, { source: path }))
}
if (
!existsSync(join(target, 'sdlc.config.json')) &&
!existsSync(join(target, 'defaults/sdlc.config.json'))
) {
findings.push(
finding(
'blocker',
'sdlc.required-file',
'missing sdlc.config.json or defaults/sdlc.config.json',
{
source: 'sdlc.config.json',
},
),
)
}

for (const path of ['docs/sdlc-definition.md', 'docs/cockpit-concepts-and-rules.md']) {
const full = join(target, path)
if (existsSync(full))
findings.push(
...validateNoForbiddenEvidenceText(readFileSync(full, 'utf8')).findings.map((item) => ({
...item,
source: path,
})),
)
}

const result = report(findings, 'full')
if (json) process.stdout.write(`${JSON.stringify(result, null, 2)}\n`)
else {
process.stdout.write('[sdlc-audit]\n')
for (const item of result.findings)
process.stdout.write(
` ${item.severity.toUpperCase()} ${item.code}: ${item.message}${item.source ? ` (${item.source})` : ''}\n`,
)
process.stdout.write(`Result: ${result.ok ? 'READY' : 'FAILED'}\n`)
}
process.exit(result.ok ? 0 : 1)
37 changes: 37 additions & 0 deletions scripts/sdlc-migrate.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
#!/usr/bin/env node
import { existsSync } from 'node:fs'
import { join } from 'node:path'
import { loadSdlcConfig, validateSdlcConfigShape } from '../lib/sdlc-state.mjs'

const args = process.argv.slice(2)
const json = args.includes('--json')
const target = args.includes('--target') ? args[args.indexOf('--target') + 1] : process.cwd()
const apply = args.includes('--apply') || args.includes('--write')
const changes = []
for (const path of [
'docs/sdlc-definition.md',
'sdlc.config.json',
'schemas/sdlc-config.schema.json',
]) {
if (!existsSync(join(target, path)))
changes.push({ action: 'install', path, reason: 'required SDLC product file missing' })
}
const configReport = existsSync(join(target, 'sdlc.config.json'))
? validateSdlcConfigShape(loadSdlcConfig(target))
: { ok: false, findings: [] }
const result = {
ok: !apply && changes.length >= 0,
mode: apply ? 'blocked-preview-first' : 'dry-run',
message: apply
? 'Migration apply is gated; run init/sync for file installation and review this dry-run first.'
: 'Preview migration plan; no writes performed.',
changes,
validation: configReport,
}
if (json) process.stdout.write(`${JSON.stringify(result, null, 2)}\n`)
else {
process.stdout.write(`[sdlc-migrate] ${result.mode}\n${result.message}\n`)
for (const change of changes)
process.stdout.write(` - ${change.action} ${change.path}: ${change.reason}\n`)
}
process.exit(apply ? 1 : 0)
31 changes: 31 additions & 0 deletions scripts/sdlc-sandbox-smoke.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
#!/usr/bin/env node
import { execFileSync } from 'node:child_process'
import { existsSync, mkdtempSync } from 'node:fs'
import { tmpdir } from 'node:os'
import { join, resolve } from 'node:path'

const repoRoot = resolve(process.cwd())
const cli = join(repoRoot, 'bin', 'cli.mjs')
const tmp = mkdtempSync(join(tmpdir(), 'agentflow-sdlc-smoke-'))
function run(args) {
return execFileSync(process.execPath, [cli, ...args, '--target', tmp], { encoding: 'utf8' })
}
execFileSync('git', ['init'], { cwd: tmp, stdio: 'ignore' })
run(['init'])
run(['sdlc', 'validate', '--json'])
run(['sdlc', 'audit', '--json'])
run(['sdlc', 'migrate', '--json'])
run(['skills', 'sync', '--harness', 'all', '--apply'])
run(['skills', 'status', '--harness', 'all', '--json'])
const expected = [
'docs/sdlc-definition.md',
'sdlc.config.json',
'schemas/sdlc-config.schema.json',
'.claude/skills/sdlc-definition/SKILL.md',
'.pi/skills/sdlc-audit/SKILL.md',
'.agents/skills/sdlc-migration/SKILL.md',
]
const missing = expected.filter((item) => !existsSync(join(tmp, item)))
const result = { ok: missing.length === 0, tmp, missing }
process.stdout.write(`${JSON.stringify(result, null, 2)}\n`)
process.exit(result.ok ? 0 : 1)
Loading