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
96 changes: 95 additions & 1 deletion bin/cli.mjs
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
#!/usr/bin/env node
import { spawnSync } from 'node:child_process'
import { dirname, resolve } from 'node:path'
import { fileURLToPath } from 'node:url'
import { doctor, init, markMerged, sync } from '../lib/install.mjs'
import { validateEnvironment } from '../lib/environment.mjs'
import { adapterStatus, syncSkillAdapters } from '../lib/skill-adapters.mjs'
import { buildReleasePlan } from '../lib/release-versioning.mjs'
import { migrateRename } from '../lib/rename-migration.mjs'
import {
Expand Down Expand Up @@ -81,6 +83,90 @@ function printExtensionRegistry(registry) {
}
}

function runScript(script, args, targetDir) {
const result = spawnSync(
process.execPath,
[resolve(packageRoot, script), ...args, '--target', targetDir],
{
stdio: 'inherit',
},
)
process.exit(result.status ?? 1)
}

function handleSdlc(rest, targetDir) {
const [subcommand] = positionalArgs(rest)
const pass = rest.filter((arg) => arg !== subcommand)
if (subcommand === 'validate' || subcommand === 'validate-config')
return runScript('scripts/validate-sdlc-config.mjs', pass, targetDir)
if (subcommand === 'validate-issue')
return runScript('scripts/validate-sdlc-issue.mjs', pass, targetDir)
if (subcommand === 'validate-role-pass')
return runScript('scripts/validate-sdlc-role-pass.mjs', pass, targetDir)
if (subcommand === 'validate-pr')
return runScript('scripts/validate-sdlc-pr.mjs', pass, targetDir)
if (subcommand === 'validate-release')
return runScript('scripts/validate-sdlc-release.mjs', pass, targetDir)
if (subcommand === 'validate-skill')
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)
}
process.stderr.write(`Usage:
agentflow-sdlc sdlc validate [--target <dir>] [--json]
agentflow-sdlc sdlc validate-issue --path <issue.json> [--json]
agentflow-sdlc sdlc validate-role-pass --path <role-pass.md> [--json]
agentflow-sdlc sdlc validate-pr --path <pr-body.md> [--json]
agentflow-sdlc sdlc validate-release --path <issue.json> [--json]
agentflow-sdlc sdlc validate-skill --path <SKILL.md> [--json]
agentflow-sdlc sdlc validate-agent --path <AGENT.md> [--json]
agentflow-sdlc sdlc audit [--json]
agentflow-sdlc sdlc migrate [--json]
`)
process.exit(2)
}

function handleSkills(rest, targetDir) {
const [subcommand] = positionalArgs(rest)
const json = rest.includes('--json')
const harness = getFlag(rest, '--harness', 'all')
if (subcommand === 'sync') {
const result = syncSkillAdapters({
packageRoot,
targetDir,
harness,
write: rest.includes('--apply') || !rest.includes('--dry-run'),
})
if (json) process.stdout.write(`${JSON.stringify(result, null, 2)}\n`)
else
printReport(`Skill adapter sync (${result.mode})`, {
entries: result.entries.map(
(entry) => `${entry.harness}:${entry.skill} -> ${entry.target}`,
),
})
process.exit(0)
}
if (subcommand === 'status') {
const result = adapterStatus({ packageRoot, targetDir, harness })
if (json) process.stdout.write(`${JSON.stringify(result, null, 2)}\n`)
else
printReport('Skill adapter status', {
stale: result.stale.map((entry) => `${entry.harness}:${entry.skill}`),
})
process.exit(result.stale.length ? 1 : 0)
}
process.stderr.write(
`Usage:\n agentflow-sdlc skills sync [--target <dir>] [--harness all|claude-code,agy,codex,pi] [--dry-run|--apply] [--json]\n agentflow-sdlc skills status [--target <dir>] [--harness all|claude-code,agy,codex,pi] [--json]\n`,
)
process.exit(2)
}

function handleExtensions(rest, targetDir) {
const [subcommand, selector] = positionalArgs(rest)
const json = rest.includes('--json')
Expand Down Expand Up @@ -185,6 +271,14 @@ function main() {
const [command, ...rest] = process.argv.slice(2)
const targetDir = resolve(getFlag(rest, '--target', process.cwd()))

if (command === 'skills') {
handleSkills(rest, targetDir)
}

if (command === 'sdlc') {
handleSdlc(rest, targetDir)
}

if (command === 'extensions') {
try {
handleExtensions(rest, targetDir)
Expand Down Expand Up @@ -268,7 +362,7 @@ function main() {
}

process.stderr.write(
'Usage: agentflow-sdlc <init|sync|doctor|doctor-env|extensions|onboarding-prompt|update-prompt|migrate-rename|release-plan|mark-merged> [path] [--target <dir>] [--json]\n',
'Usage: agentflow-sdlc <init|sync|doctor|doctor-env|sdlc|skills|extensions|onboarding-prompt|update-prompt|migrate-rename|release-plan|mark-merged> [path] [--target <dir>] [--json]\n',
)
process.exit(2)
}
Expand Down
173 changes: 173 additions & 0 deletions defaults/sdlc.config.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,173 @@
{
"version": 1,
"authority": {
"human": "docs/sdlc-definition.md",
"machine": "sdlc.config.json",
"executionAdapter": "agent-workflow.config.json"
},
"principles": [
"AgentFlow concepts lead; source systems are substrate.",
"Durable evidence is required for delivery claims.",
"Single-agent is default; multi-agent claims require attribution.",
"High-assurance work requires a human approval gate.",
"Skipped-by-path roles do not reduce readiness.",
"Follow-up issues replace hidden TODOs.",
"Harness directories contain generated adapters only."
],
"roles": [
{
"phase": 0,
"slug": "product-manager-jtbd",
"label": "Product manager / JTBD",
"owns": ["goal", "job", "release-intent"]
},
{
"phase": 1,
"slug": "analyst",
"label": "Analyst",
"owns": ["requirements", "acceptance-criteria", "scope-boundary"]
},
{
"phase": 2,
"slug": "architect",
"label": "Architect",
"owns": ["technical-design", "risk", "path-selection"]
},
{
"phase": 3,
"slug": "developer-planning",
"label": "Developer planning",
"owns": ["implementation-plan", "validation-plan"]
},
{
"phase": 4,
"slug": "developer",
"label": "Developer",
"owns": ["implementation", "commit-evidence"]
},
{
"phase": 5,
"slug": "tester",
"label": "Tester",
"owns": ["validation-evidence", "coverage-notes"]
},
{
"phase": 6,
"slug": "review",
"label": "Review",
"owns": ["review-findings", "independence-boundary"]
},
{
"phase": 7,
"slug": "tech-writer",
"label": "Tech writer",
"owns": ["docs", "release-notes", "product-language"]
},
{
"phase": 8,
"slug": "pr-readiness",
"label": "PR readiness",
"owns": ["pr-manifest", "merge-readiness", "follow-up-status"]
}
],
"paths": {
"bounded": {
"requiredRoles": [
"analyst",
"developer-planning",
"developer",
"tester",
"review",
"pr-readiness"
],
"optionalRoles": ["product-manager-jtbd", "architect", "tech-writer"],
"allowsSelfReview": true,
"requiresHumanApproval": false
},
"standard": {
"requiredRoles": [
"analyst",
"architect",
"developer-planning",
"developer",
"tester",
"review",
"pr-readiness"
],
"optionalRoles": ["product-manager-jtbd", "tech-writer"],
"allowsSelfReview": true,
"requiresHumanApproval": false
},
"high-assurance": {
"requiredRoles": [
"product-manager-jtbd",
"analyst",
"architect",
"developer-planning",
"developer",
"tester",
"review",
"tech-writer",
"pr-readiness"
],
"optionalRoles": [],
"allowsSelfReview": false,
"requiresHumanApproval": true
},
"exploratory": {
"requiredRoles": ["product-manager-jtbd", "analyst", "architect", "review"],
"optionalRoles": ["developer-planning", "developer", "tester", "tech-writer", "pr-readiness"],
"allowsSelfReview": true,
"requiresHumanApproval": false
}
},
"transitions": [
["product-manager-jtbd", "analyst"],
["analyst", "architect"],
["architect", "developer-planning"],
["developer-planning", "developer"],
["developer", "tester"],
["tester", "review"],
["review", "tech-writer"],
["tech-writer", "pr-readiness"],
["developer", "developer-planning"],
["review", "developer"],
["tech-writer", "developer"],
["pr-readiness", "developer"]
],
"labels": {
"type": ["epic", "feature", "bug", "dx", "tooling", "documentation", "qa", "exploratory"],
"routingPrefixes": ["for-implementation:"],
"lifecyclePrefixes": ["drafted-by:", "implemented-by:", "for-review:", "reviewed-by:"],
"forbiddenPrefixes": ["agent:"],
"release": ["awaiting-release"],
"integrationPrefixes": ["integrated:"]
},
"release": {
"candidateSources": ["issue-field", "milestone"],
"assignmentStates": ["assigned", "needs-assignment", "released", "no-release-impact"],
"impactStates": ["major", "minor", "patch", "docs-only", "none", "unknown"],
"filters": ["unreleased", "released", "all", "needs-assignment"],
"rejectIncidentalSemver": true
},
"gateways": {
"humanApproval": {
"requiredFor": ["high-assurance"],
"fields": ["Reviewer", "Scope", "Decision", "Notes"],
"decisions": ["approved", "changes-requested", "blocked"]
},
"guidedAction": {
"requires": ["preview", "auth", "csrf", "confirmation", "audit"]
}
},
"extensionPolicy": {
"mayAdd": ["roles", "labels", "gateways", "paths", "validators", "harnessAdapters"],
"mustPreserve": [
"role-pass-provenance",
"human-approval-for-high-assurance",
"readiness-denominator-rules",
"no-raw-secrets-or-transcripts",
"follow-ups-over-hidden-todos"
]
}
}
Loading
Loading