@ai-sdlc/orchestrator 0.6.0 → 0.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (209) hide show
  1. package/dist/__test-helpers/git-env.d.ts +40 -0
  2. package/dist/__test-helpers/git-env.js +73 -0
  3. package/dist/admission-composite.d.ts +89 -0
  4. package/dist/admission-composite.js +241 -0
  5. package/dist/admission-enrichment.d.ts +142 -0
  6. package/dist/admission-enrichment.js +341 -0
  7. package/dist/admission-hc.d.ts +81 -0
  8. package/dist/admission-hc.js +94 -0
  9. package/dist/admission-score.d.ts +109 -5
  10. package/dist/admission-score.js +90 -11
  11. package/dist/analysis/analyzer.js +3 -2
  12. package/dist/analysis/convention-detector.d.ts +85 -2
  13. package/dist/analysis/convention-detector.js +375 -70
  14. package/dist/analysis/diff-analyzer.d.ts +33 -0
  15. package/dist/analysis/diff-analyzer.js +122 -0
  16. package/dist/analysis/hotspot-analyzer.js +3 -1
  17. package/dist/analysis/index.d.ts +2 -1
  18. package/dist/analysis/index.js +2 -1
  19. package/dist/artifacts/index.d.ts +65 -0
  20. package/dist/artifacts/index.js +142 -0
  21. package/dist/backlog-adapter.d.ts +121 -0
  22. package/dist/backlog-adapter.js +438 -0
  23. package/dist/calibration.d.ts +43 -0
  24. package/dist/calibration.js +76 -0
  25. package/dist/cli/commands/agents.d.ts +24 -0
  26. package/dist/cli/commands/agents.js +66 -1
  27. package/dist/cli/commands/git-remote.d.ts +74 -0
  28. package/dist/cli/commands/git-remote.js +170 -0
  29. package/dist/cli/commands/health.d.ts +4 -0
  30. package/dist/cli/commands/health.js +13 -1
  31. package/dist/cli/commands/init-features.d.ts +208 -0
  32. package/dist/cli/commands/init-features.js +473 -0
  33. package/dist/cli/commands/init-templates.d.ts +104 -0
  34. package/dist/cli/commands/init-templates.js +399 -0
  35. package/dist/cli/commands/init.d.ts +48 -0
  36. package/dist/cli/commands/init.js +322 -23
  37. package/dist/cli/commands/mcp-setup.d.ts +31 -0
  38. package/dist/cli/commands/mcp-setup.js +78 -8
  39. package/dist/cli/formatters/table.js +15 -2
  40. package/dist/cli/index.d.ts +14 -1
  41. package/dist/cli/index.js +81 -20
  42. package/dist/cli/versions.d.ts +57 -0
  43. package/dist/cli/versions.js +128 -0
  44. package/dist/code-area-classifier.d.ts +21 -0
  45. package/dist/code-area-classifier.js +48 -0
  46. package/dist/config.d.ts +33 -1
  47. package/dist/config.js +78 -8
  48. package/dist/database/adapters/external.d.ts +24 -0
  49. package/dist/database/adapters/external.js +80 -0
  50. package/dist/database/adapters/neon.d.ts +41 -0
  51. package/dist/database/adapters/neon.js +98 -0
  52. package/dist/database/adapters/pg-snapshot-restore.d.ts +28 -0
  53. package/dist/database/adapters/pg-snapshot-restore.js +68 -0
  54. package/dist/database/adapters/sqlite-copy.d.ts +32 -0
  55. package/dist/database/adapters/sqlite-copy.js +145 -0
  56. package/dist/database/connection-injection.d.ts +35 -0
  57. package/dist/database/connection-injection.js +93 -0
  58. package/dist/database/index.d.ts +12 -0
  59. package/dist/database/index.js +23 -0
  60. package/dist/database/registry.d.ts +13 -0
  61. package/dist/database/registry.js +27 -0
  62. package/dist/database/topology.d.ts +52 -0
  63. package/dist/database/topology.js +44 -0
  64. package/dist/database/types.d.ts +89 -0
  65. package/dist/database/types.js +26 -0
  66. package/dist/design-authority.d.ts +53 -0
  67. package/dist/design-authority.js +84 -0
  68. package/dist/design-lookahead.d.ts +64 -0
  69. package/dist/design-lookahead.js +86 -0
  70. package/dist/design-quality-trend.d.ts +87 -0
  71. package/dist/design-quality-trend.js +190 -0
  72. package/dist/design-system-context.d.ts +46 -0
  73. package/dist/design-system-context.js +80 -0
  74. package/dist/design-system-correction-loop.d.ts +64 -0
  75. package/dist/design-system-correction-loop.js +128 -0
  76. package/dist/design-system-metrics.d.ts +61 -0
  77. package/dist/design-system-metrics.js +104 -0
  78. package/dist/design-system-stewardship.d.ts +22 -0
  79. package/dist/design-system-stewardship.js +85 -0
  80. package/dist/design-system-validation.d.ts +37 -0
  81. package/dist/design-system-validation.js +88 -0
  82. package/dist/dispatch/index.d.ts +4 -0
  83. package/dist/dispatch/index.js +4 -0
  84. package/dist/dispatch/merge-gate.d.ts +46 -0
  85. package/dist/dispatch/merge-gate.js +90 -0
  86. package/dist/dispatch/requeue.d.ts +57 -0
  87. package/dist/dispatch/requeue.js +131 -0
  88. package/dist/dispatch/worker-pool.d.ts +62 -0
  89. package/dist/dispatch/worker-pool.js +60 -0
  90. package/dist/execute.d.ts +40 -0
  91. package/dist/execute.js +188 -15
  92. package/dist/fix-ci.js +8 -1
  93. package/dist/fix-review.js +8 -1
  94. package/dist/harness/adapters/claude-code.d.ts +29 -0
  95. package/dist/harness/adapters/claude-code.js +191 -0
  96. package/dist/harness/adapters/codex.d.ts +25 -0
  97. package/dist/harness/adapters/codex.js +61 -0
  98. package/dist/harness/independence.d.ts +51 -0
  99. package/dist/harness/independence.js +67 -0
  100. package/dist/harness/index.d.ts +14 -0
  101. package/dist/harness/index.js +20 -0
  102. package/dist/harness/registry.d.ts +17 -0
  103. package/dist/harness/registry.js +31 -0
  104. package/dist/harness/types.d.ts +123 -0
  105. package/dist/harness/types.js +8 -0
  106. package/dist/harness/version-probe.d.ts +14 -0
  107. package/dist/harness/version-probe.js +123 -0
  108. package/dist/index.d.ts +30 -4
  109. package/dist/index.js +35 -2
  110. package/dist/models/classifier.d.ts +78 -0
  111. package/dist/models/classifier.js +277 -0
  112. package/dist/models/index.d.ts +3 -0
  113. package/dist/models/index.js +3 -0
  114. package/dist/models/registry.d.ts +97 -0
  115. package/dist/models/registry.js +173 -0
  116. package/dist/pillar-breakdown.d.ts +85 -0
  117. package/dist/pillar-breakdown.js +162 -0
  118. package/dist/priority.d.ts +5 -0
  119. package/dist/priority.js +18 -5
  120. package/dist/review-meta.d.ts +65 -0
  121. package/dist/review-meta.js +149 -0
  122. package/dist/review.d.ts +4 -0
  123. package/dist/review.js +12 -2
  124. package/dist/runners/claude-code-sdk.d.ts +31 -0
  125. package/dist/runners/claude-code-sdk.js +238 -0
  126. package/dist/runners/claude-code.d.ts +3 -0
  127. package/dist/runners/claude-code.js +30 -66
  128. package/dist/runners/codex.js +4 -1
  129. package/dist/runners/copilot.js +4 -1
  130. package/dist/runners/cursor.js +4 -1
  131. package/dist/runners/git-utils.d.ts +81 -0
  132. package/dist/runners/git-utils.js +201 -0
  133. package/dist/runners/index.d.ts +3 -0
  134. package/dist/runners/index.js +3 -0
  135. package/dist/runners/review-agent.d.ts +20 -0
  136. package/dist/runners/review-agent.js +171 -41
  137. package/dist/runners/runner-registry.js +10 -0
  138. package/dist/runners/sdk-review-runner.d.ts +65 -0
  139. package/dist/runners/sdk-review-runner.js +185 -0
  140. package/dist/runners/security-triage.d.ts +20 -4
  141. package/dist/runners/security-triage.js +39 -15
  142. package/dist/runners/types.d.ts +6 -0
  143. package/dist/runtime/attestations.d.ts +766 -0
  144. package/dist/runtime/attestations.js +1195 -0
  145. package/dist/runtime/git-env.d.ts +53 -0
  146. package/dist/runtime/git-env.js +60 -0
  147. package/dist/runtime/index.d.ts +7 -0
  148. package/dist/runtime/index.js +7 -0
  149. package/dist/runtime/parallelism-flag.d.ts +28 -0
  150. package/dist/runtime/parallelism-flag.js +39 -0
  151. package/dist/runtime/port-allocator.d.ts +32 -0
  152. package/dist/runtime/port-allocator.js +96 -0
  153. package/dist/runtime/worktree-pool.d.ts +86 -0
  154. package/dist/runtime/worktree-pool.js +204 -0
  155. package/dist/runtime/worktree.d.ts +25 -0
  156. package/dist/runtime/worktree.js +111 -0
  157. package/dist/sa-scoring/auto-calibrate.d.ts +69 -0
  158. package/dist/sa-scoring/auto-calibrate.js +107 -0
  159. package/dist/sa-scoring/c1-sa2-computable.d.ts +42 -0
  160. package/dist/sa-scoring/c1-sa2-computable.js +59 -0
  161. package/dist/sa-scoring/composite.d.ts +107 -0
  162. package/dist/sa-scoring/composite.js +139 -0
  163. package/dist/sa-scoring/depparse-client.d.ts +79 -0
  164. package/dist/sa-scoring/depparse-client.js +187 -0
  165. package/dist/sa-scoring/did-compiler.d.ts +122 -0
  166. package/dist/sa-scoring/did-compiler.js +286 -0
  167. package/dist/sa-scoring/drift-monitor.d.ts +84 -0
  168. package/dist/sa-scoring/drift-monitor.js +186 -0
  169. package/dist/sa-scoring/exemplar-bank.d.ts +78 -0
  170. package/dist/sa-scoring/exemplar-bank.js +154 -0
  171. package/dist/sa-scoring/feedback-store.d.ts +100 -0
  172. package/dist/sa-scoring/feedback-store.js +156 -0
  173. package/dist/sa-scoring/index.d.ts +71 -0
  174. package/dist/sa-scoring/index.js +158 -0
  175. package/dist/sa-scoring/layer1-deterministic.d.ts +115 -0
  176. package/dist/sa-scoring/layer1-deterministic.js +298 -0
  177. package/dist/sa-scoring/layer2-structural.d.ts +71 -0
  178. package/dist/sa-scoring/layer2-structural.js +151 -0
  179. package/dist/sa-scoring/layer3-llm.d.ts +86 -0
  180. package/dist/sa-scoring/layer3-llm.js +282 -0
  181. package/dist/sa-scoring/rescore-orchestrator.d.ts +52 -0
  182. package/dist/sa-scoring/rescore-orchestrator.js +47 -0
  183. package/dist/scheduling/burn-down.d.ts +27 -0
  184. package/dist/scheduling/burn-down.js +43 -0
  185. package/dist/scheduling/calibration.d.ts +42 -0
  186. package/dist/scheduling/calibration.js +150 -0
  187. package/dist/scheduling/index.d.ts +8 -0
  188. package/dist/scheduling/index.js +8 -0
  189. package/dist/scheduling/ledger.d.ts +59 -0
  190. package/dist/scheduling/ledger.js +216 -0
  191. package/dist/scheduling/off-peak.d.ts +27 -0
  192. package/dist/scheduling/off-peak.js +112 -0
  193. package/dist/scheduling/schedule-decision.d.ts +39 -0
  194. package/dist/scheduling/schedule-decision.js +89 -0
  195. package/dist/scheduling/tier-analysis.d.ts +47 -0
  196. package/dist/scheduling/tier-analysis.js +81 -0
  197. package/dist/scheduling/types.d.ts +140 -0
  198. package/dist/scheduling/types.js +11 -0
  199. package/dist/shared.d.ts +13 -0
  200. package/dist/shared.js +32 -0
  201. package/dist/state/schema.d.ts +5 -1
  202. package/dist/state/schema.js +223 -1
  203. package/dist/state/store.d.ts +55 -1
  204. package/dist/state/store.js +342 -7
  205. package/dist/state/types.d.ts +139 -0
  206. package/dist/types.d.ts +1 -1
  207. package/dist/validate-agent-output.js +4 -1
  208. package/dist/watch.js +6 -0
  209. package/package.json +3 -2
@@ -1,12 +1,41 @@
1
1
  /**
2
2
  * ai-sdlc init — initialize a project with AI-SDLC config files.
3
+ *
4
+ * AISDLC-78 enhancements:
5
+ * - Print a 3-line version block at startup so operators see CLI,
6
+ * orchestrator, and plugin versions in one place. Drift triggers a
7
+ * visible warning instead of silently shipping mismatched binaries.
8
+ * - Substitute `your-org` in the pipeline.yaml template using
9
+ * `git remote get-url origin` (ssh + https forms supported). Falls
10
+ * back to the placeholder only when no remote is configured.
11
+ * - Pin `@ai-sdlc/mcp-advisor` in generated `.mcp.json` to the
12
+ * orchestrator version that ran init, with an inline opt-out hint.
13
+ * - Skip `.cursor/mcp.json` unless Cursor is detected (project-local
14
+ * `.cursor/`, user-global `~/.cursor/`) or `--cursor` is passed.
15
+ *
16
+ * AISDLC-143 enhancements (Q4(b) of the quality-gate redesign):
17
+ * - Default invocation is now an interactive WIZARD (DoR / attestation /
18
+ * classifier / branch-protection prompts) so adopters get a guided
19
+ * bootstrap instead of having to read the docs to discover features.
20
+ * - `--yes` short-circuits all prompts to "accept defaults" (CI/scripts).
21
+ * - `--with-X` flags opt into individual features without prompting.
22
+ * - `--add <feature>` extends an already-initialized repo with a single
23
+ * feature, idempotently — re-running on an initialized repo is safe.
24
+ * - `.github/workflows/ai-sdlc-gate.yml` is scaffolded UNCONDITIONALLY
25
+ * so every adopter gets the `ai-sdlc/pr-ready` rollup check on day one
26
+ * (Q1: prescriptive default).
27
+ * - A "next steps" summary closes the run with operator action items
28
+ * conditional on which features were chosen.
3
29
  */
4
30
  import { Command } from 'commander';
5
31
  import { existsSync, mkdirSync, writeFileSync, readFileSync, appendFileSync } from 'node:fs';
6
32
  import { join, basename } from 'node:path';
7
- import { detectAgents, installMcpServer } from './mcp-setup.js';
33
+ import { detectAgentsDetailed, installMcpServer } from './mcp-setup.js';
8
34
  import { detectWorkspace, generateWorkspaceYaml } from './workspace-detect.js';
9
- const PIPELINE_YAML = `apiVersion: ai-sdlc.io/v1alpha1
35
+ import { detectGitRemote, applyRemoteToPipelineYaml } from './git-remote.js';
36
+ import { resolveVersions, formatVersionBlock } from '../versions.js';
37
+ import { applyFeatureSelection, buildProductionAdapters, ensureClaudeMdPointer, renderNextSteps, resolveFeatureSelection, } from './init-features.js';
38
+ export const PIPELINE_YAML = `apiVersion: ai-sdlc.io/v1alpha1
10
39
  kind: Pipeline
11
40
  metadata:
12
41
  name: default
@@ -34,21 +63,49 @@ spec:
34
63
  - name: review
35
64
  qualityGates:
36
65
  - default-gates
66
+ # backlog: holds settings specific to the backlog-task (/ai-sdlc execute)
67
+ # workflow. These were formerly in a separate pipeline-backlog.yaml file
68
+ # (deprecated by AISDLC-245.5). Slash commands + pipeline-cli readers
69
+ # prefer this canonical location.
70
+ backlog:
71
+ branching:
72
+ pattern: 'ai-sdlc/{issueIdLower}-{slug}'
73
+ targetBranch: main
74
+ cleanup: on-merge
75
+ pullRequest:
76
+ titleTemplate: 'feat: {issueTitle} ({issueId})'
77
+ descriptionSections:
78
+ - summary
79
+ - changes
80
+ - closes
81
+ includeProvenance: true
82
+ closeKeyword: References
37
83
  `;
38
- const AGENT_ROLE_YAML = `apiVersion: ai-sdlc.io/v1alpha1
84
+ export const AGENT_ROLE_TIERS = ['coding', 'research', 'meta'];
85
+ const AGENT_ROLE_YAML_CODING = `apiVersion: ai-sdlc.io/v1alpha1
39
86
  kind: AgentRole
40
87
  metadata:
41
88
  name: default-agent
42
89
  spec:
43
90
  role: developer
44
91
  goal: Implement issue requirements with tests
92
+ # Tier: coding-agent (default).
93
+ # Filesystem + shell editing surface only. No web access, no sub-agent
94
+ # spawning, no Skill loading. This matches the pre-AISDLC-79 default
95
+ # plus NotebookEdit (parity with the Claude Code SDK's editing surface).
45
96
  tools:
46
- - Edit
47
- - Write
48
- - Read
49
- - Glob
50
- - Grep
51
- - Bash
97
+ - Edit # write to existing files
98
+ - Write # create new files
99
+ - Read # read source / configs
100
+ - Glob # discover files by pattern
101
+ - Grep # search code
102
+ - Bash # run tests, lint, build, git
103
+ - NotebookEdit # edit Jupyter notebooks (parity with SDK editing surface)
104
+ # Excluded by default (opt in via --role research|meta during init):
105
+ # - WebFetch — pulls untrusted remote content into context (research tier)
106
+ # - WebSearch — same (research tier)
107
+ # - Task — spawns sub-agents; cost + reasoning amplification (meta tier)
108
+ # - Skill — loads external skill packs (meta tier)
52
109
  constraints:
53
110
  maxFilesPerChange: 15
54
111
  requireTests: true
@@ -56,6 +113,75 @@ spec:
56
113
  - .github/workflows/**
57
114
  - .ai-sdlc/**
58
115
  `;
116
+ const AGENT_ROLE_YAML_RESEARCH = `apiVersion: ai-sdlc.io/v1alpha1
117
+ kind: AgentRole
118
+ metadata:
119
+ name: default-agent
120
+ spec:
121
+ role: research-developer
122
+ goal: Investigate, gather references, and implement issue requirements with tests
123
+ # Tier: research-agent.
124
+ # Coding-tier surface plus read-only web access. Use for tasks that need
125
+ # to consult external docs, package registries, or RFCs.
126
+ tools:
127
+ - Edit # write to existing files
128
+ - Write # create new files
129
+ - Read # read source / configs
130
+ - Glob # discover files by pattern
131
+ - Grep # search code
132
+ - Bash # run tests, lint, build, git
133
+ - NotebookEdit # edit Jupyter notebooks
134
+ - WebFetch # fetch a known URL (docs, RFCs, API references)
135
+ - WebSearch # discover sources by query
136
+ # Excluded (use --role meta to opt in):
137
+ # - Task — spawns sub-agents (meta tier)
138
+ # - Skill — loads external skill packs (meta tier)
139
+ constraints:
140
+ maxFilesPerChange: 15
141
+ requireTests: true
142
+ blockedPaths:
143
+ - .github/workflows/**
144
+ - .ai-sdlc/**
145
+ `;
146
+ const AGENT_ROLE_YAML_META = `apiVersion: ai-sdlc.io/v1alpha1
147
+ kind: AgentRole
148
+ metadata:
149
+ name: default-agent
150
+ spec:
151
+ role: meta-developer
152
+ goal: Orchestrate sub-agents, load skill packs, and implement complex multi-step work
153
+ # Tier: meta-agent.
154
+ # Full surface: filesystem + shell + web + sub-agent + skill loading.
155
+ # Use only when the task legitimately needs sub-agent fan-out or external
156
+ # skill packs. Higher cost amplification and broader attack surface.
157
+ tools:
158
+ - Edit # write to existing files
159
+ - Write # create new files
160
+ - Read # read source / configs
161
+ - Glob # discover files by pattern
162
+ - Grep # search code
163
+ - Bash # run tests, lint, build, git
164
+ - NotebookEdit # edit Jupyter notebooks
165
+ - WebFetch # fetch a known URL
166
+ - WebSearch # discover sources by query
167
+ - Task # spawn sub-agents for parallel / specialized work
168
+ - Skill # load external skill packs (declared capabilities)
169
+ constraints:
170
+ maxFilesPerChange: 15
171
+ requireTests: true
172
+ blockedPaths:
173
+ - .github/workflows/**
174
+ - .ai-sdlc/**
175
+ `;
176
+ const AGENT_ROLE_YAML_BY_TIER = {
177
+ coding: AGENT_ROLE_YAML_CODING,
178
+ research: AGENT_ROLE_YAML_RESEARCH,
179
+ meta: AGENT_ROLE_YAML_META,
180
+ };
181
+ /** Resolve the agent-role.yaml template for a given tier. Default = `coding`. */
182
+ export function getAgentRoleYaml(tier = 'coding') {
183
+ return AGENT_ROLE_YAML_BY_TIER[tier];
184
+ }
59
185
  const QUALITY_GATE_YAML = `apiVersion: ai-sdlc.io/v1alpha1
60
186
  kind: QualityGate
61
187
  metadata:
@@ -139,24 +265,26 @@ const GITIGNORE_PATHS = ['.ai-sdlc/state.db', '.ai-sdlc/state/', '.ai-sdlc/audit
139
265
  function ensureGitignore(projectDir, dryRun, prefix = '') {
140
266
  const gitignorePath = join(projectDir, '.gitignore');
141
267
  const existing = existsSync(gitignorePath) ? readFileSync(gitignorePath, 'utf-8') : '';
142
- // Only check path entries (not comments) to avoid duplicate blocks
143
- const missing = GITIGNORE_PATHS.filter((entry) => !existing.includes(entry));
268
+ const SENTINEL = '# ai-sdlc:runtime-gitignore';
269
+ if (existing.includes(SENTINEL))
270
+ return;
271
+ const missing = GITIGNORE_PATHS.filter((entry) => !existing.split('\n').some((line) => line.trim() === entry));
144
272
  if (missing.length === 0)
145
273
  return;
146
274
  if (dryRun) {
147
275
  console.log(`${prefix} Would update .gitignore`);
148
276
  return;
149
277
  }
150
- const block = '\n# AI-SDLC runtime artifacts\n' + missing.join('\n') + '\n';
278
+ const block = (existing.length > 0 ? '\n' : '') + `${SENTINEL}\n` + missing.join('\n') + '\n';
151
279
  appendFileSync(gitignorePath, block, 'utf-8');
152
280
  console.log(`${prefix} updated .gitignore`);
153
281
  }
154
282
  /** Initialize a single project directory with AI-SDLC config files. */
155
- function initProject(projectDir, configDirName, dryRun, prefix = '') {
283
+ function initProject(projectDir, configDirName, dryRun, prefix = '', options = { pipelineYaml: PIPELINE_YAML, tier: 'coding' }) {
156
284
  const configDir = join(projectDir, configDirName);
157
285
  const files = [
158
- { name: 'pipeline.yaml', content: PIPELINE_YAML },
159
- { name: 'agent-role.yaml', content: AGENT_ROLE_YAML },
286
+ { name: 'pipeline.yaml', content: options.pipelineYaml },
287
+ { name: 'agent-role.yaml', content: getAgentRoleYaml(options.tier) },
160
288
  { name: 'quality-gate.yaml', content: QUALITY_GATE_YAML },
161
289
  { name: 'autonomy-policy.yaml', content: AUTONOMY_POLICY_YAML },
162
290
  ];
@@ -205,15 +333,120 @@ function initWorkspaceRoot(workspacePath, repos, configDirName, dryRun) {
205
333
  console.log(` created workspace.yaml`);
206
334
  }
207
335
  }
336
+ /**
337
+ * Build the WizardFlags struct from Commander's parsed options. Pulled
338
+ * out into a function so tests can drive the wizard pipeline directly
339
+ * without going through Commander's stateful option store.
340
+ */
341
+ export function buildWizardFlags(opts) {
342
+ const addRaw = typeof opts.add === 'string' ? opts.add : undefined;
343
+ let add;
344
+ if (addRaw === 'dor' ||
345
+ addRaw === 'attestation' ||
346
+ addRaw === 'classifier' ||
347
+ addRaw === 'branch-protection') {
348
+ add = addRaw;
349
+ }
350
+ return {
351
+ yes: !!opts.yes,
352
+ withDor: !!opts.withDor,
353
+ withAttestation: !!opts.withAttestation,
354
+ withClassifier: !!opts.withClassifier,
355
+ withBranchProtection: !!opts.withBranchProtection,
356
+ add,
357
+ dryRun: !!opts.dryRun,
358
+ };
359
+ }
360
+ /** Validate a `--add` arg and return either the normalized value or null+error. */
361
+ function validateAddArg(addRaw) {
362
+ if (addRaw === undefined || addRaw === null || addRaw === false)
363
+ return { ok: true };
364
+ if (typeof addRaw !== 'string')
365
+ return { ok: false, error: `--add must be a string` };
366
+ const allowed = ['dor', 'attestation', 'classifier', 'branch-protection'];
367
+ if (!allowed.includes(addRaw)) {
368
+ return {
369
+ ok: false,
370
+ error: `--add: unknown feature '${addRaw}'. Expected one of: ${allowed.join(', ')}.`,
371
+ };
372
+ }
373
+ return { ok: true, value: addRaw };
374
+ }
208
375
  export const initCommand = new Command('init')
209
376
  .description('Initialize AI-SDLC configuration in the current project')
210
377
  .option('--dry-run', 'Show what would be created without writing files')
211
378
  .option('--skip-mcp', 'Skip MCP server auto-configuration')
379
+ .option('--cursor', 'Force-install Cursor MCP config even if Cursor is not detected')
212
380
  .option('-d, --dir <path>', 'Config directory name', '.ai-sdlc')
381
+ .option('--role <tier>', `Agent-role tool tier: ${AGENT_ROLE_TIERS.join(' | ')} (default: coding)`, 'coding')
382
+ // ── AISDLC-143 wizard flags ─────────────────────────────────────────
383
+ .option('-y, --yes', 'Accept all defaults (non-interactive; CI/scripts)')
384
+ .option('--with-dor', 'Scaffold Definition-of-Ready gate config + workflow')
385
+ .option('--with-attestation', 'Scaffold attestation infrastructure (audit-only)')
386
+ .option('--with-classifier', 'Scaffold review classifier config stub')
387
+ .option('--with-branch-protection', 'Apply recommended branch-protection rule to main (requires gh)')
388
+ .option('--add <feature>', 'Extend an already-initialized repo with a single feature: dor | attestation | classifier | branch-protection')
213
389
  .action(async (opts) => {
214
390
  const projectDir = process.cwd();
215
391
  const configDirName = opts.dir ?? '.ai-sdlc';
216
392
  const dryRun = !!opts.dryRun;
393
+ const tierInput = (opts.role ?? 'coding');
394
+ if (!AGENT_ROLE_TIERS.includes(tierInput)) {
395
+ console.error(`Error: invalid --role value '${tierInput}'. Expected one of: ${AGENT_ROLE_TIERS.join(', ')}.`);
396
+ process.exitCode = 1;
397
+ return;
398
+ }
399
+ const tier = tierInput;
400
+ const cursorOptIn = !!opts.cursor;
401
+ // ── Validate --add early so we error before doing any work. ────────
402
+ const addCheck = validateAddArg(opts.add);
403
+ if (!addCheck.ok) {
404
+ console.error(`Error: ${addCheck.error}`);
405
+ process.exitCode = 1;
406
+ return;
407
+ }
408
+ const flags = buildWizardFlags(opts);
409
+ // ── --add path: extend an already-initialized repo ────────────────
410
+ // AC #7: skip the "always-scaffold-baseline" path entirely; the
411
+ // wizard dispatcher's `--add` branch knows to write only the chosen
412
+ // feature's templates (no pipeline.yaml, no MCP setup, no workspace
413
+ // detection). This is the safe re-run path on a repo that already
414
+ // ran `ai-sdlc init` once.
415
+ if (flags.add) {
416
+ const adapters = buildProductionAdapters();
417
+ const selection = await resolveFeatureSelection(flags, adapters);
418
+ console.log(`Extending AI-SDLC config with --add ${flags.add}:`);
419
+ console.log('');
420
+ const result = await applyFeatureSelection(projectDir, selection, flags, adapters);
421
+ renderNextSteps(selection, result, adapters);
422
+ // Reviewer feedback (round 2, suggestion #5): when the operator
423
+ // explicitly requested branch-protection via a non-interactive flag
424
+ // (--add branch-protection here; --yes / --with-branch-protection
425
+ // in runWizardStage) and the apply failed (gh missing, not
426
+ // authenticated, etc.), surface a non-zero exit so CI scripts can
427
+ // detect failure instead of seeing the silent log line.
428
+ if (flags.add === 'branch-protection' &&
429
+ result.branchProtection &&
430
+ !result.branchProtection.applied &&
431
+ result.branchProtection.error) {
432
+ process.exitCode = 1;
433
+ }
434
+ return;
435
+ }
436
+ // ── version provenance (AC #1) ────────────────────────────────────
437
+ const versions = resolveVersions({ workDir: projectDir });
438
+ console.log(formatVersionBlock(versions));
439
+ console.log('');
440
+ // ── pipeline.yaml org/repo substitution (AC #2) ───────────────────
441
+ const remote = detectGitRemote({ cwd: projectDir });
442
+ const pipelineYaml = applyRemoteToPipelineYaml(PIPELINE_YAML, remote);
443
+ if (remote.detected) {
444
+ console.log(`Detected git remote: ${remote.org}/${remote.repo}`);
445
+ }
446
+ else {
447
+ console.log(`No git origin remote detected — pipeline.yaml will use the 'your-org' placeholder.`);
448
+ }
449
+ console.log('');
217
450
  // Detect workspace (multi-repo parent directory)
218
451
  const workspace = detectWorkspace(projectDir);
219
452
  if (workspace.isWorkspace) {
@@ -227,37 +460,103 @@ export const initCommand = new Command('init')
227
460
  // Cascade into each child repo
228
461
  for (const repo of workspace.repos) {
229
462
  console.log(`\n${repo.name}/`);
230
- initProject(repo.absPath, configDirName, dryRun, ' ');
463
+ // Per-repo remote detection so each child gets its own org/repo.
464
+ const childRemote = detectGitRemote({ cwd: repo.absPath });
465
+ const childPipeline = applyRemoteToPipelineYaml(PIPELINE_YAML, childRemote);
466
+ if (childRemote.detected) {
467
+ console.log(` detected remote: ${childRemote.org}/${childRemote.repo}`);
468
+ }
469
+ initProject(repo.absPath, configDirName, dryRun, ' ', {
470
+ pipelineYaml: childPipeline,
471
+ tier,
472
+ });
231
473
  }
232
474
  // MCP setup at workspace root (serves all repos)
233
475
  if (!opts.skipMcp) {
234
- const agents = detectAgents(projectDir, { isWorkspace: true });
476
+ const { detected, skipped } = detectAgentsDetailed(projectDir, {
477
+ isWorkspace: true,
478
+ pinVersion: versions.orchestrator,
479
+ cursorOptIn,
480
+ });
235
481
  console.log(`\nMCP server setup (workspace root):`);
236
- console.log(` detected ${agents.map((a) => a.name).join(', ')}`);
237
- for (const agent of agents) {
482
+ console.log(` detected ${detected.map((a) => a.name).join(', ') || '(none)'}`);
483
+ for (const sk of skipped) {
484
+ console.log(` skip ${sk.name} — ${sk.reason}`);
485
+ }
486
+ for (const agent of detected) {
238
487
  const status = installMcpServer(projectDir, agent, dryRun);
239
488
  const pad = status === 'merged' ? ' ' : '';
240
489
  console.log(` ${status}${pad} ${agent.configPath} (${agent.name})`);
241
490
  }
242
491
  }
243
492
  console.log(`\nAI-SDLC workspace initialized in ${projectDir}/`);
493
+ // ── AISDLC-143 wizard (workspace root) ────────────────────────
494
+ // The wizard runs ONCE at the workspace root: the baseline gate
495
+ // workflow + per-feature workflows live at `<workspace-root>/.github/`,
496
+ // not in each child repo. Children share the same CI from the
497
+ // root since GHA workflows always live at the repo root anyway.
498
+ await runWizardStage(projectDir, flags);
244
499
  console.log(`Run 'ai-sdlc health' to verify your configuration.`);
245
500
  }
246
501
  else {
247
502
  // Single-repo mode (original behavior)
248
- initProject(projectDir, configDirName, dryRun);
503
+ initProject(projectDir, configDirName, dryRun, '', { pipelineYaml, tier });
249
504
  if (!opts.skipMcp) {
250
- const agents = detectAgents(projectDir);
505
+ const { detected, skipped } = detectAgentsDetailed(projectDir, {
506
+ pinVersion: versions.orchestrator,
507
+ cursorOptIn,
508
+ });
251
509
  console.log(`\nMCP server setup:`);
252
- console.log(` detected ${agents.map((a) => a.name).join(', ')}`);
253
- for (const agent of agents) {
510
+ console.log(` detected ${detected.map((a) => a.name).join(', ') || '(none)'}`);
511
+ for (const sk of skipped) {
512
+ console.log(` skip ${sk.name} — ${sk.reason}`);
513
+ }
514
+ for (const agent of detected) {
254
515
  const status = installMcpServer(projectDir, agent, dryRun);
255
516
  const pad = status === 'merged' ? ' ' : '';
256
517
  console.log(` ${status}${pad} ${agent.configPath} (${agent.name})`);
257
518
  }
258
519
  }
259
520
  console.log(`\nAI-SDLC config initialized in ${join(projectDir, configDirName)}/`);
521
+ // ── AISDLC-143 wizard (single-repo) ──────────────────────────
522
+ await runWizardStage(projectDir, flags);
260
523
  console.log(`Run 'ai-sdlc health' to verify your configuration.`);
261
524
  }
262
525
  });
526
+ /**
527
+ * Run the AISDLC-143 wizard stage: prompt the user (or short-circuit on
528
+ * --yes / --with-X), apply the chosen feature templates, append the
529
+ * CLAUDE.md pointer, and render the "next steps" summary.
530
+ *
531
+ * Pulled out of the inline action body so both the single-repo and
532
+ * workspace-root branches share the same wiring. Adapters are built
533
+ * once here (production = real disk writes; tests inject stubs by
534
+ * calling `applyFeatureSelection`/`renderNextSteps` directly).
535
+ */
536
+ async function runWizardStage(projectDir, flags) {
537
+ const adapters = buildProductionAdapters();
538
+ console.log('');
539
+ console.log('━━━ Feature wizard ━━━');
540
+ console.log('');
541
+ const selection = await resolveFeatureSelection(flags, adapters);
542
+ console.log('');
543
+ console.log('Scaffolding selected features:');
544
+ const result = await applyFeatureSelection(projectDir, selection, flags, adapters);
545
+ ensureClaudeMdPointer(projectDir, adapters, flags.dryRun);
546
+ renderNextSteps(selection, result, adapters);
547
+ // Reviewer feedback (round 2, suggestion #5): when branch-protection
548
+ // was non-interactively requested (--with-branch-protection or --yes,
549
+ // both used by CI scripts) and the apply failed (gh missing, not
550
+ // authenticated, etc.), surface a non-zero exit so CI can detect
551
+ // failure instead of seeing the silent log line. Interactive prompt
552
+ // answers don't trip this — the human already saw the error and can
553
+ // re-run `ai-sdlc init --add branch-protection` themselves.
554
+ const branchProtectionRequestedNonInteractively = flags.yes || flags.withBranchProtection;
555
+ if (branchProtectionRequestedNonInteractively &&
556
+ result.branchProtection &&
557
+ !result.branchProtection.applied &&
558
+ result.branchProtection.error) {
559
+ process.exitCode = 1;
560
+ }
561
+ }
263
562
  //# sourceMappingURL=init.js.map
@@ -1,5 +1,17 @@
1
1
  /**
2
2
  * MCP server setup — detect coding agents and install MCP config.
3
+ *
4
+ * AISDLC-78 changes:
5
+ * - The `npx -y` arg list now pins `@ai-sdlc/mcp-advisor@<version>` so
6
+ * fresh installs don't silently jump to whatever is published when
7
+ * the orchestrator binary itself was last updated. Each generated
8
+ * config file carries a single top-level `_aiSdlcComment` documenting
9
+ * how to opt back into floating-tag behaviour.
10
+ * - Cursor is no longer detected by binary-on-PATH alone; the user
11
+ * must either have a `.cursor/` directory present (real signal of
12
+ * use) or pass `--cursor` to opt in. This avoids writing
13
+ * `.cursor/mcp.json` into projects whose author has Cursor
14
+ * installed but is not using it on this repo.
3
15
  */
4
16
  export interface DetectedAgent {
5
17
  name: string;
@@ -10,7 +22,26 @@ export interface DetectedAgent {
10
22
  export interface DetectAgentsOptions {
11
23
  /** When true, adds AI_SDLC_WORKSPACE env to server entries. */
12
24
  isWorkspace?: boolean;
25
+ /** Pin the mcp-advisor to this version in generated configs. */
26
+ pinVersion?: string;
27
+ /** User explicitly asked for Cursor MCP install. */
28
+ cursorOptIn?: boolean;
13
29
  }
30
+ /**
31
+ * Returned alongside detected agents to log skip decisions.
32
+ */
33
+ export interface DetectAgentsResult {
34
+ detected: DetectedAgent[];
35
+ /** Per-spec skip explanations for human-readable logging. */
36
+ skipped: Array<{
37
+ name: string;
38
+ reason: string;
39
+ }>;
40
+ }
41
+ export declare function detectAgentsDetailed(projectDir: string, options?: DetectAgentsOptions): DetectAgentsResult;
42
+ /**
43
+ * Backwards-compatible wrapper. New code should prefer detectAgentsDetailed.
44
+ */
14
45
  export declare function detectAgents(projectDir: string, options?: DetectAgentsOptions): DetectedAgent[];
15
46
  export declare function installMcpServer(projectDir: string, agent: DetectedAgent, dryRun: boolean): 'created' | 'merged' | 'skipped';
16
47
  //# sourceMappingURL=mcp-setup.d.ts.map
@@ -1,23 +1,48 @@
1
1
  /**
2
2
  * MCP server setup — detect coding agents and install MCP config.
3
+ *
4
+ * AISDLC-78 changes:
5
+ * - The `npx -y` arg list now pins `@ai-sdlc/mcp-advisor@<version>` so
6
+ * fresh installs don't silently jump to whatever is published when
7
+ * the orchestrator binary itself was last updated. Each generated
8
+ * config file carries a single top-level `_aiSdlcComment` documenting
9
+ * how to opt back into floating-tag behaviour.
10
+ * - Cursor is no longer detected by binary-on-PATH alone; the user
11
+ * must either have a `.cursor/` directory present (real signal of
12
+ * use) or pass `--cursor` to opt in. This avoids writing
13
+ * `.cursor/mcp.json` into projects whose author has Cursor
14
+ * installed but is not using it on this repo.
3
15
  */
4
16
  import { existsSync, readFileSync, mkdirSync, writeFileSync } from 'node:fs';
5
17
  import { join, dirname } from 'node:path';
6
18
  import { execSync } from 'node:child_process';
7
- function standardEntry(env) {
19
+ /**
20
+ * The opt-out comment we write alongside the pinned mcp-advisor entry.
21
+ * Lives at the top level of the generated MCP config under a leading-
22
+ * underscore key so JSON parsers and MCP clients ignore it; humans
23
+ * editing by hand see exactly how to re-enable floating-latest behaviour.
24
+ */
25
+ const PIN_OPT_OUT_COMMENT = 'Pinned to the orchestrator version that ran `ai-sdlc init`. ' +
26
+ 'To always pull the latest published mcp-advisor, change args to ["-y", "@ai-sdlc/mcp-advisor"].';
27
+ function pinnedSpec(version) {
28
+ return version && version !== '0.0.0'
29
+ ? `@ai-sdlc/mcp-advisor@${version}`
30
+ : '@ai-sdlc/mcp-advisor';
31
+ }
32
+ function standardEntry(version, env) {
8
33
  const entry = {
9
34
  command: 'npx',
10
- args: ['-y', '@ai-sdlc/mcp-advisor'],
35
+ args: ['-y', pinnedSpec(version)],
11
36
  };
12
37
  if (env)
13
38
  entry.env = env;
14
39
  return entry;
15
40
  }
16
- function vscodeEntry(env) {
41
+ function vscodeEntry(version, env) {
17
42
  const entry = {
18
43
  type: 'stdio',
19
44
  command: 'npx',
20
- args: ['-y', '@ai-sdlc/mcp-advisor'],
45
+ args: ['-y', pinnedSpec(version)],
21
46
  };
22
47
  if (env)
23
48
  entry.env = env;
@@ -39,6 +64,7 @@ const AGENT_SPECS = [
39
64
  entryFn: standardEntry,
40
65
  configDir: '.cursor',
41
66
  binary: 'cursor',
67
+ requiresOptIn: true,
42
68
  },
43
69
  {
44
70
  name: 'VS Code',
@@ -66,31 +92,67 @@ function hasBinary(name) {
66
92
  return false;
67
93
  }
68
94
  }
69
- export function detectAgents(projectDir, options) {
95
+ function hasUserCursorDir() {
96
+ const home = process.env.HOME ?? process.env.USERPROFILE;
97
+ if (!home)
98
+ return false;
99
+ return existsSync(join(home, '.cursor'));
100
+ }
101
+ export function detectAgentsDetailed(projectDir, options) {
70
102
  const detected = [];
103
+ const skipped = [];
71
104
  const env = options?.isWorkspace ? { AI_SDLC_WORKSPACE: '.' } : undefined;
105
+ const pinVersion = options?.pinVersion;
72
106
  for (const spec of AGENT_SPECS) {
73
107
  if (spec.alwaysDetect) {
74
108
  detected.push({
75
109
  name: spec.name,
76
110
  configPath: spec.configPath,
77
111
  configKey: spec.configKey,
78
- serverEntry: spec.entryFn(env),
112
+ serverEntry: spec.entryFn(pinVersion, env),
79
113
  });
80
114
  continue;
81
115
  }
82
116
  const hasDir = spec.configDir ? existsSync(join(projectDir, spec.configDir)) : false;
83
117
  const hasBin = spec.binary ? hasBinary(spec.binary) : false;
118
+ if (spec.requiresOptIn) {
119
+ // For Cursor specifically: explicit --cursor flag, OR a project-local
120
+ // .cursor/ dir, OR a user-global ~/.cursor/ presence. Just having
121
+ // `cursor` on PATH is no longer sufficient (too noisy on dev boxes
122
+ // where Cursor is installed but not used per-project).
123
+ const userOptIn = options?.cursorOptIn === true;
124
+ if (userOptIn || hasDir || hasUserCursorDir()) {
125
+ detected.push({
126
+ name: spec.name,
127
+ configPath: spec.configPath,
128
+ configKey: spec.configKey,
129
+ serverEntry: spec.entryFn(pinVersion, env),
130
+ });
131
+ }
132
+ else {
133
+ skipped.push({
134
+ name: spec.name,
135
+ reason: 'no .cursor/ directory in project or $HOME; pass --cursor to install Cursor MCP config',
136
+ });
137
+ }
138
+ continue;
139
+ }
84
140
  if (hasDir || hasBin) {
85
141
  detected.push({
86
142
  name: spec.name,
87
143
  configPath: spec.configPath,
88
144
  configKey: spec.configKey,
89
- serverEntry: spec.entryFn(env),
145
+ serverEntry: spec.entryFn(pinVersion, env),
90
146
  });
91
147
  }
92
148
  }
93
- return detected;
149
+ return { detected, skipped };
150
+ }
151
+ /**
152
+ * Backwards-compatible wrapper. New code should prefer detectAgentsDetailed.
153
+ */
154
+ export function detectAgents(projectDir, options) {
155
+ return detectAgentsDetailed(projectDir, options).detected;
94
156
  }
95
157
  export function installMcpServer(projectDir, agent, dryRun) {
96
158
  const fullPath = join(projectDir, agent.configPath);
@@ -110,6 +172,13 @@ export function installMcpServer(projectDir, agent, dryRun) {
110
172
  if (!dryRun) {
111
173
  section['ai-sdlc'] = agent.serverEntry;
112
174
  existing[agent.configKey] = section;
175
+ // Top-level pin-comment: standard convention is a single
176
+ // `_aiSdlcComment` key at the root of the document, not inside each
177
+ // server entry. We add it on first write but never overwrite an
178
+ // existing top-level value (caller may have customised it).
179
+ if (!('_aiSdlcComment' in existing)) {
180
+ existing._aiSdlcComment = PIN_OPT_OUT_COMMENT;
181
+ }
113
182
  writeFileSync(fullPath, JSON.stringify(existing, null, 2) + '\n', 'utf-8');
114
183
  }
115
184
  return 'merged';
@@ -120,6 +189,7 @@ export function installMcpServer(projectDir, agent, dryRun) {
120
189
  mkdirSync(dir, { recursive: true });
121
190
  }
122
191
  const config = {
192
+ _aiSdlcComment: PIN_OPT_OUT_COMMENT,
123
193
  [agent.configKey]: {
124
194
  'ai-sdlc': agent.serverEntry,
125
195
  },
@@ -37,7 +37,12 @@ export function formatTable(data) {
37
37
  lines.push('Health Check');
38
38
  lines.push('─'.repeat(40));
39
39
  lines.push(`Config: ${data.configValid ? 'valid' : 'INVALID'}`);
40
- lines.push(`State Store: ${data.stateStoreConnected ? 'connected' : 'not configured'}`);
40
+ // AISDLC-78: the previous "not configured" string read like a setup
41
+ // failure to fresh users. The state store is created on demand by
42
+ // the first pipeline run; explicitly flag deferred initialization.
43
+ lines.push(`State Store: ${data.stateStoreConnected
44
+ ? 'connected'
45
+ : 'deferred (initializes on first pipeline run; pass --init-state to create now)'}`);
41
46
  const errors = data.errors;
42
47
  if (errors.length > 0) {
43
48
  lines.push('');
@@ -65,8 +70,16 @@ export function formatTable(data) {
65
70
  for (const agent of agents) {
66
71
  const total = agent.totalTasks;
67
72
  const success = agent.successCount;
73
+ const declaredOnly = agent.declaredOnly === true;
68
74
  const pct = total > 0 ? `${Math.round((success / total) * 100)}%` : '-';
69
- const lastTask = agent.lastTaskAt ? agent.lastTaskAt.split('T')[0] : '-';
75
+ // AISDLC-78 (AC #10): mark declared-but-not-executed rows so a
76
+ // fresh-install user sees their `agent-role.yaml` declarations
77
+ // even before the first pipeline run populates the ledger.
78
+ const lastTask = declaredOnly
79
+ ? '(declared, not yet executed)'
80
+ : agent.lastTaskAt
81
+ ? agent.lastTaskAt.split('T')[0]
82
+ : '-';
70
83
  lines.push(String(agent.agentName ?? '').padEnd(20) +
71
84
  String(agent.currentLevel ?? 0).padEnd(7) +
72
85
  String(total).padEnd(7) +