@ai-sdlc/orchestrator 0.9.0 → 0.13.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 (172) 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/adapters.d.ts +42 -3
  4. package/dist/adapters.js +133 -3
  5. package/dist/admission-composite.d.ts +134 -1
  6. package/dist/admission-composite.js +170 -6
  7. package/dist/admission-enrichment.d.ts +12 -3
  8. package/dist/admission-enrichment.js +71 -10
  9. package/dist/admission-hc.d.ts +20 -1
  10. package/dist/admission-hc.js +12 -1
  11. package/dist/admission-score.d.ts +44 -0
  12. package/dist/admission-score.js +11 -2
  13. package/dist/backlog-adapter.d.ts +45 -0
  14. package/dist/backlog-adapter.js +145 -1
  15. package/dist/calibration.d.ts +81 -0
  16. package/dist/calibration.js +76 -0
  17. package/dist/cli/commands/git-remote.d.ts +25 -0
  18. package/dist/cli/commands/git-remote.js +80 -1
  19. package/dist/cli/commands/init-features.d.ts +507 -0
  20. package/dist/cli/commands/init-features.js +1100 -0
  21. package/dist/cli/commands/init-templates.d.ts +301 -0
  22. package/dist/cli/commands/init-templates.js +1341 -0
  23. package/dist/cli/commands/init.d.ts +67 -0
  24. package/dist/cli/commands/init.js +301 -2
  25. package/dist/cli/commands/run.js +9 -1
  26. package/dist/cli/index.d.ts +11 -0
  27. package/dist/cli/index.js +42 -8
  28. package/dist/compliance/composer.d.ts +79 -0
  29. package/dist/compliance/composer.js +258 -0
  30. package/dist/compliance/errors.d.ts +64 -0
  31. package/dist/compliance/errors.js +85 -0
  32. package/dist/compliance/loader.d.ts +52 -0
  33. package/dist/compliance/loader.js +124 -0
  34. package/dist/compliance/types.d.ts +184 -0
  35. package/dist/compliance/types.js +41 -0
  36. package/dist/compliance-clearance.d.ts +269 -0
  37. package/dist/compliance-clearance.js +269 -0
  38. package/dist/config.js +17 -0
  39. package/dist/cost-tracker.d.ts +22 -0
  40. package/dist/cost-tracker.js +41 -0
  41. package/dist/database/adapters/external.js +5 -1
  42. package/dist/design-authority.d.ts +13 -0
  43. package/dist/design-authority.js +13 -0
  44. package/dist/embedding/adapters/openai-text-embedding-3-small.d.ts +71 -0
  45. package/dist/embedding/adapters/openai-text-embedding-3-small.js +190 -0
  46. package/dist/embedding/consumers/tessellation-drift.d.ts +74 -0
  47. package/dist/embedding/consumers/tessellation-drift.js +76 -0
  48. package/dist/embedding/cross-provider.d.ts +78 -0
  49. package/dist/embedding/cross-provider.js +75 -0
  50. package/dist/embedding/deprecation.d.ts +151 -0
  51. package/dist/embedding/deprecation.js +229 -0
  52. package/dist/embedding/errors.d.ts +90 -0
  53. package/dist/embedding/errors.js +150 -0
  54. package/dist/embedding/index.d.ts +29 -0
  55. package/dist/embedding/index.js +24 -0
  56. package/dist/embedding/pipeline-load.d.ts +146 -0
  57. package/dist/embedding/pipeline-load.js +178 -0
  58. package/dist/embedding/registry.d.ts +45 -0
  59. package/dist/embedding/registry.js +61 -0
  60. package/dist/embedding/stale-vector.d.ts +110 -0
  61. package/dist/embedding/stale-vector.js +92 -0
  62. package/dist/embedding/storage/index.d.ts +51 -0
  63. package/dist/embedding/storage/index.js +43 -0
  64. package/dist/embedding/storage/jsonl-backend.d.ts +150 -0
  65. package/dist/embedding/storage/jsonl-backend.js +332 -0
  66. package/dist/embedding/storage/types.d.ts +135 -0
  67. package/dist/embedding/storage/types.js +13 -0
  68. package/dist/embedding/types.d.ts +180 -0
  69. package/dist/embedding/types.js +10 -0
  70. package/dist/execute.d.ts +29 -2
  71. package/dist/execute.js +179 -35
  72. package/dist/index.d.ts +14 -2
  73. package/dist/index.js +19 -2
  74. package/dist/journey/inheritance-validator.d.ts +396 -0
  75. package/dist/journey/inheritance-validator.js +370 -0
  76. package/dist/journey/state-id-drift-rule.d.ts +137 -0
  77. package/dist/journey/state-id-drift-rule.js +245 -0
  78. package/dist/journey-sa2-router.d.ts +395 -0
  79. package/dist/journey-sa2-router.js +308 -0
  80. package/dist/models/classifier.d.ts +3 -1
  81. package/dist/models/classifier.js +61 -5
  82. package/dist/pillar-breakdown.d.ts +23 -1
  83. package/dist/pillar-breakdown.js +9 -0
  84. package/dist/runners/review-agent.js +6 -2
  85. package/dist/runners/runner-registry.d.ts +36 -0
  86. package/dist/runners/runner-registry.js +90 -0
  87. package/dist/runtime/attestations.d.ts +663 -11
  88. package/dist/runtime/attestations.js +971 -24
  89. package/dist/runtime/index.d.ts +1 -1
  90. package/dist/runtime/index.js +1 -1
  91. package/dist/runtime/parallelism-flag.d.ts +21 -3
  92. package/dist/runtime/parallelism-flag.js +27 -6
  93. package/dist/runtime/port-allocator.d.ts +11 -0
  94. package/dist/runtime/port-allocator.js +30 -0
  95. package/dist/sa-scoring/layer3-llm.js +6 -1
  96. package/dist/sa-scoring/revision-proposal-config.d.ts +178 -0
  97. package/dist/sa-scoring/revision-proposal-config.js +198 -0
  98. package/dist/sa-scoring/revision-proposal.d.ts +285 -0
  99. package/dist/sa-scoring/revision-proposal.js +417 -0
  100. package/dist/signal-ingestion/adapters/community-thread.d.ts +43 -0
  101. package/dist/signal-ingestion/adapters/community-thread.js +55 -0
  102. package/dist/signal-ingestion/adapters/in-app-feedback.d.ts +67 -0
  103. package/dist/signal-ingestion/adapters/in-app-feedback.js +51 -0
  104. package/dist/signal-ingestion/adapters/manual.d.ts +78 -0
  105. package/dist/signal-ingestion/adapters/manual.js +112 -0
  106. package/dist/signal-ingestion/adapters/support-ticket.d.ts +47 -0
  107. package/dist/signal-ingestion/adapters/support-ticket.js +51 -0
  108. package/dist/signal-ingestion/classifier.d.ts +205 -0
  109. package/dist/signal-ingestion/classifier.js +494 -0
  110. package/dist/signal-ingestion/clustering-types.d.ts +36 -0
  111. package/dist/signal-ingestion/clustering-types.js +14 -0
  112. package/dist/signal-ingestion/clustering.d.ts +200 -0
  113. package/dist/signal-ingestion/clustering.js +413 -0
  114. package/dist/signal-ingestion/config.d.ts +351 -0
  115. package/dist/signal-ingestion/config.js +587 -0
  116. package/dist/signal-ingestion/d1.d.ts +252 -0
  117. package/dist/signal-ingestion/d1.js +235 -0
  118. package/dist/signal-ingestion/errors.d.ts +73 -0
  119. package/dist/signal-ingestion/errors.js +108 -0
  120. package/dist/signal-ingestion/governance-events.d.ts +181 -0
  121. package/dist/signal-ingestion/governance-events.js +189 -0
  122. package/dist/signal-ingestion/index.d.ts +35 -0
  123. package/dist/signal-ingestion/index.js +53 -0
  124. package/dist/signal-ingestion/manual-share-metric.d.ts +93 -0
  125. package/dist/signal-ingestion/manual-share-metric.js +106 -0
  126. package/dist/signal-ingestion/registry.d.ts +40 -0
  127. package/dist/signal-ingestion/registry.js +137 -0
  128. package/dist/signal-ingestion/residency.d.ts +227 -0
  129. package/dist/signal-ingestion/residency.js +238 -0
  130. package/dist/signal-ingestion/significance.d.ts +554 -0
  131. package/dist/signal-ingestion/significance.js +555 -0
  132. package/dist/signal-ingestion/types.d.ts +191 -0
  133. package/dist/signal-ingestion/types.js +8 -0
  134. package/dist/substrate/drift-composition.d.ts +270 -0
  135. package/dist/substrate/drift-composition.js +306 -0
  136. package/dist/substrate/drift-tui-surface.d.ts +61 -0
  137. package/dist/substrate/drift-tui-surface.js +102 -0
  138. package/dist/substrate/identity-class.d.ts +176 -0
  139. package/dist/substrate/identity-class.js +201 -0
  140. package/dist/tessellation/cross-soul-provenance-rule.d.ts +133 -0
  141. package/dist/tessellation/cross-soul-provenance-rule.js +171 -0
  142. package/dist/tessellation/inter-soul-embedding-distance-rule.d.ts +61 -0
  143. package/dist/tessellation/inter-soul-embedding-distance-rule.js +67 -0
  144. package/dist/tessellation/rule-registry.d.ts +269 -0
  145. package/dist/tessellation/rule-registry.js +92 -0
  146. package/dist/tessellation/soul-slug-ast-scan-rule.d.ts +90 -0
  147. package/dist/tessellation/soul-slug-ast-scan-rule.js +158 -0
  148. package/dist/tessellation-admission.d.ts +162 -0
  149. package/dist/tessellation-admission.js +146 -0
  150. package/dist/tessellation-drift.d.ts +246 -0
  151. package/dist/tessellation-drift.js +250 -0
  152. package/dist/validate-config.js +13 -0
  153. package/dist/validate-issue.js +2 -2
  154. package/dist/variant/cardinality-activation.d.ts +126 -0
  155. package/dist/variant/cardinality-activation.js +101 -0
  156. package/dist/variant/deprecation-lifecycle.d.ts +184 -0
  157. package/dist/variant/deprecation-lifecycle.js +208 -0
  158. package/dist/variant/drift-extension.d.ts +136 -0
  159. package/dist/variant/drift-extension.js +164 -0
  160. package/dist/variant/engineering-review.d.ts +185 -0
  161. package/dist/variant/engineering-review.js +142 -0
  162. package/dist/variant/index.d.ts +32 -0
  163. package/dist/variant/index.js +32 -0
  164. package/dist/variant/inheritance-validator.d.ts +165 -0
  165. package/dist/variant/inheritance-validator.js +139 -0
  166. package/dist/variant/internal-adopter/index.d.ts +11 -0
  167. package/dist/variant/internal-adopter/index.js +10 -0
  168. package/dist/variant/internal-adopter/products.d.ts +156 -0
  169. package/dist/variant/internal-adopter/products.js +366 -0
  170. package/dist/variant-admission.d.ts +316 -0
  171. package/dist/variant-admission.js +247 -0
  172. package/package.json +11 -8
@@ -12,8 +12,24 @@
12
12
  * orchestrator version that ran init, with an inline opt-out hint.
13
13
  * - Skip `.cursor/mcp.json` unless Cursor is detected (project-local
14
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.
15
29
  */
16
30
  import { Command } from 'commander';
31
+ import { type WizardFlags } from './init-features.js';
32
+ export declare const PIPELINE_YAML = "apiVersion: ai-sdlc.io/v1alpha1\nkind: Pipeline\nmetadata:\n name: default\nspec:\n triggers:\n - event: issue.labeled\n filter:\n labels:\n - ai-eligible\n providers:\n sourceControl:\n type: github\n config:\n org: your-org\n stages:\n - name: validate\n qualityGates:\n - default-gates\n - name: code\n agent: default-agent\n timeout: PT30M\n onFailure:\n strategy: retry\n maxRetries: 2\n - name: review\n qualityGates:\n - default-gates\n # backlog: holds settings specific to the backlog-task (/ai-sdlc execute)\n # workflow. These were formerly in a separate pipeline-backlog.yaml file\n # (deprecated by AISDLC-245.5). Slash commands + pipeline-cli readers\n # prefer this canonical location.\n backlog:\n branching:\n pattern: 'ai-sdlc/{issueIdLower}-{slug}'\n targetBranch: main\n cleanup: on-merge\n pullRequest:\n titleTemplate: 'feat: {issueTitle} ({issueId})'\n descriptionSections:\n - summary\n - changes\n - closes\n includeProvenance: true\n closeKeyword: References\n";
17
33
  /**
18
34
  * Tier-based agent-role templates (AISDLC-79).
19
35
  *
@@ -28,5 +44,56 @@ export type AgentRoleTier = 'coding' | 'research' | 'meta';
28
44
  export declare const AGENT_ROLE_TIERS: readonly AgentRoleTier[];
29
45
  /** Resolve the agent-role.yaml template for a given tier. Default = `coding`. */
30
46
  export declare function getAgentRoleYaml(tier?: AgentRoleTier): string;
47
+ /**
48
+ * Options for `scaffoldSoulDsbs`.
49
+ */
50
+ export interface ScaffoldSoulDsbsOptions {
51
+ /**
52
+ * Name of the platform-root DesignSystemBinding resource.
53
+ * Referenced in each per-soul DSB's `spec.extends` field.
54
+ * Defaults to `'platform-dsb'` when not supplied.
55
+ */
56
+ platformDsbName?: string;
57
+ /**
58
+ * When true, print what would be created without writing files.
59
+ * Mirrors the `--dry-run` semantics of `initProject()`.
60
+ */
61
+ dryRun?: boolean;
62
+ /**
63
+ * Output prefix for console lines (e.g. ' ' for indented workspace output).
64
+ * Defaults to empty string.
65
+ */
66
+ prefix?: string;
67
+ }
68
+ /**
69
+ * Scaffold per-soul DesignSystemBinding template files for a Tessellated Platform.
70
+ *
71
+ * Creates `.ai-sdlc/souls/<slug>/design-system-binding.yaml` for each soul slug.
72
+ * Each file is a complete `DesignSystemBinding` resource that additively extends
73
+ * the platform-root DSB per RFC-0009 §6 resolution rules.
74
+ *
75
+ * Idempotent: existing soul DSB files are skipped (not overwritten).
76
+ *
77
+ * @param soulSlugs - soul identifiers to scaffold (e.g. ['soul-a', 'soul-b'])
78
+ * @param aiSdlcDir - absolute path to the `.ai-sdlc/` directory (config dir)
79
+ * @param options - optional configuration: platformDsbName, dryRun, prefix
80
+ *
81
+ * @example
82
+ * ```ts
83
+ * scaffoldSoulDsbs(['soul-a', 'soul-b', 'soul-c'], '/project/.ai-sdlc', {
84
+ * platformDsbName: 'acme-platform-dsb',
85
+ * });
86
+ * // Created: /project/.ai-sdlc/souls/soul-a/design-system-binding.yaml
87
+ * // Created: /project/.ai-sdlc/souls/soul-b/design-system-binding.yaml
88
+ * // Created: /project/.ai-sdlc/souls/soul-c/design-system-binding.yaml
89
+ * ```
90
+ */
91
+ export declare function scaffoldSoulDsbs(soulSlugs: readonly string[], aiSdlcDir: string, options?: ScaffoldSoulDsbsOptions): void;
92
+ /**
93
+ * Build the WizardFlags struct from Commander's parsed options. Pulled
94
+ * out into a function so tests can drive the wizard pipeline directly
95
+ * without going through Commander's stateful option store.
96
+ */
97
+ export declare function buildWizardFlags(opts: Record<string, unknown>): WizardFlags;
31
98
  export declare const initCommand: Command;
32
99
  //# sourceMappingURL=init.d.ts.map
@@ -12,6 +12,20 @@
12
12
  * orchestrator version that ran init, with an inline opt-out hint.
13
13
  * - Skip `.cursor/mcp.json` unless Cursor is detected (project-local
14
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.
15
29
  */
16
30
  import { Command } from 'commander';
17
31
  import { existsSync, mkdirSync, writeFileSync, readFileSync, appendFileSync } from 'node:fs';
@@ -20,7 +34,9 @@ import { detectAgentsDetailed, installMcpServer } from './mcp-setup.js';
20
34
  import { detectWorkspace, generateWorkspaceYaml } from './workspace-detect.js';
21
35
  import { detectGitRemote, applyRemoteToPipelineYaml } from './git-remote.js';
22
36
  import { resolveVersions, formatVersionBlock } from '../versions.js';
23
- const PIPELINE_YAML = `apiVersion: ai-sdlc.io/v1alpha1
37
+ import { applyFeatureSelection, buildProductionAdapters, ensureClaudeMdPointer, renderNextSteps, resolveFeatureSelection, resolveInstallTarget, runComplianceStep, } from './init-features.js';
38
+ import { CALIBRATION_YAML_STUB, EMBEDDING_CONFIG_YAML_STUB, buildSoulDsbTemplate, } from './init-templates.js';
39
+ export const PIPELINE_YAML = `apiVersion: ai-sdlc.io/v1alpha1
24
40
  kind: Pipeline
25
41
  metadata:
26
42
  name: default
@@ -48,6 +64,23 @@ spec:
48
64
  - name: review
49
65
  qualityGates:
50
66
  - default-gates
67
+ # backlog: holds settings specific to the backlog-task (/ai-sdlc execute)
68
+ # workflow. These were formerly in a separate pipeline-backlog.yaml file
69
+ # (deprecated by AISDLC-245.5). Slash commands + pipeline-cli readers
70
+ # prefer this canonical location.
71
+ backlog:
72
+ branching:
73
+ pattern: 'ai-sdlc/{issueIdLower}-{slug}'
74
+ targetBranch: main
75
+ cleanup: on-merge
76
+ pullRequest:
77
+ titleTemplate: 'feat: {issueTitle} ({issueId})'
78
+ descriptionSections:
79
+ - summary
80
+ - changes
81
+ - closes
82
+ includeProvenance: true
83
+ closeKeyword: References
51
84
  `;
52
85
  export const AGENT_ROLE_TIERS = ['coding', 'research', 'meta'];
53
86
  const AGENT_ROLE_YAML_CODING = `apiVersion: ai-sdlc.io/v1alpha1
@@ -228,6 +261,51 @@ spec:
228
261
  action: demote-one-level
229
262
  cooldown: 2w
230
263
  `;
264
+ /**
265
+ * Scaffold per-soul DesignSystemBinding template files for a Tessellated Platform.
266
+ *
267
+ * Creates `.ai-sdlc/souls/<slug>/design-system-binding.yaml` for each soul slug.
268
+ * Each file is a complete `DesignSystemBinding` resource that additively extends
269
+ * the platform-root DSB per RFC-0009 §6 resolution rules.
270
+ *
271
+ * Idempotent: existing soul DSB files are skipped (not overwritten).
272
+ *
273
+ * @param soulSlugs - soul identifiers to scaffold (e.g. ['soul-a', 'soul-b'])
274
+ * @param aiSdlcDir - absolute path to the `.ai-sdlc/` directory (config dir)
275
+ * @param options - optional configuration: platformDsbName, dryRun, prefix
276
+ *
277
+ * @example
278
+ * ```ts
279
+ * scaffoldSoulDsbs(['soul-a', 'soul-b', 'soul-c'], '/project/.ai-sdlc', {
280
+ * platformDsbName: 'acme-platform-dsb',
281
+ * });
282
+ * // Created: /project/.ai-sdlc/souls/soul-a/design-system-binding.yaml
283
+ * // Created: /project/.ai-sdlc/souls/soul-b/design-system-binding.yaml
284
+ * // Created: /project/.ai-sdlc/souls/soul-c/design-system-binding.yaml
285
+ * ```
286
+ */
287
+ export function scaffoldSoulDsbs(soulSlugs, aiSdlcDir, options = {}) {
288
+ const { platformDsbName = 'platform-dsb', dryRun = false, prefix = '' } = options;
289
+ if (soulSlugs.length === 0) {
290
+ console.log(`${prefix}No soul slugs provided — skipping soul DSB scaffolding.`);
291
+ return;
292
+ }
293
+ for (const slug of soulSlugs) {
294
+ const soulDir = join(aiSdlcDir, 'souls', slug);
295
+ const dsbPath = join(soulDir, 'design-system-binding.yaml');
296
+ if (dryRun) {
297
+ console.log(`${prefix}Would create ${dsbPath}`);
298
+ continue;
299
+ }
300
+ if (existsSync(dsbPath)) {
301
+ console.log(`${prefix} skip souls/${slug}/design-system-binding.yaml (already exists)`);
302
+ continue;
303
+ }
304
+ mkdirSync(soulDir, { recursive: true });
305
+ writeFileSync(dsbPath, buildSoulDsbTemplate(slug, platformDsbName), 'utf-8');
306
+ console.log(`${prefix} created souls/${slug}/design-system-binding.yaml`);
307
+ }
308
+ }
231
309
  const GITIGNORE_PATHS = ['.ai-sdlc/state.db', '.ai-sdlc/state/', '.ai-sdlc/audit.jsonl'];
232
310
  /** Ensure .gitignore includes AI-SDLC runtime artifact entries. */
233
311
  function ensureGitignore(projectDir, dryRun, prefix = '') {
@@ -255,6 +333,16 @@ function initProject(projectDir, configDirName, dryRun, prefix = '', options = {
255
333
  { name: 'agent-role.yaml', content: getAgentRoleYaml(options.tier) },
256
334
  { name: 'quality-gate.yaml', content: QUALITY_GATE_YAML },
257
335
  { name: 'autonomy-policy.yaml', content: AUTONOMY_POLICY_YAML },
336
+ // RFC-0031 §12.6 per-org calibration config (Refit AISDLC-310):
337
+ // confidence thresholds + rejection weights/floor for the DID revision
338
+ // proposal mechanism. Defaults match the operator-affirmed shipped values.
339
+ { name: 'calibration.yaml', content: CALIBRATION_YAML_STUB },
340
+ // RFC-0019 §15.1 per-org embedding-framework defaults (AISDLC-340).
341
+ // Documents every NEW re-walkthrough field (scaleEscalationHeuristic,
342
+ // perConsumerOverridesAllowed, crossProviderPolicy split, catalogDedup
343
+ // milestones, unifiedCostReport, adapterBillingModelRespected). The
344
+ // file is OPTIONAL — when absent, framework defaults match these values.
345
+ { name: 'embedding-config.yaml', content: EMBEDDING_CONFIG_YAML_STUB },
258
346
  ];
259
347
  if (dryRun) {
260
348
  console.log(`${prefix}Would create ${configDir}/`);
@@ -301,6 +389,60 @@ function initWorkspaceRoot(workspacePath, repos, configDirName, dryRun) {
301
389
  console.log(` created workspace.yaml`);
302
390
  }
303
391
  }
392
+ /**
393
+ * Build the WizardFlags struct from Commander's parsed options. Pulled
394
+ * out into a function so tests can drive the wizard pipeline directly
395
+ * without going through Commander's stateful option store.
396
+ */
397
+ export function buildWizardFlags(opts) {
398
+ const addRaw = typeof opts.add === 'string' ? opts.add : undefined;
399
+ let add;
400
+ if (addRaw === 'dor' ||
401
+ addRaw === 'attestation' ||
402
+ addRaw === 'classifier' ||
403
+ addRaw === 'branch-protection' ||
404
+ addRaw === 'workflows' ||
405
+ addRaw === 'signal-ingestion') {
406
+ add = addRaw;
407
+ }
408
+ return {
409
+ yes: !!opts.yes,
410
+ withDor: !!opts.withDor,
411
+ withAttestation: !!opts.withAttestation,
412
+ withClassifier: !!opts.withClassifier,
413
+ withBranchProtection: !!opts.withBranchProtection,
414
+ withWorkflows: !!opts.withWorkflows,
415
+ withSignalIngestion: !!opts.withSignalIngestion,
416
+ add,
417
+ dryRun: !!opts.dryRun,
418
+ workspace: typeof opts.workspace === 'string' ? opts.workspace : undefined,
419
+ force: !!opts.force,
420
+ };
421
+ }
422
+ /** Validate a `--add` arg and return either the normalized value or null+error. */
423
+ function validateAddArg(addRaw) {
424
+ if (addRaw === undefined || addRaw === null || addRaw === false)
425
+ return { ok: true };
426
+ if (typeof addRaw !== 'string')
427
+ return { ok: false, error: `--add must be a string` };
428
+ const allowed = [
429
+ 'dor',
430
+ 'attestation',
431
+ 'classifier',
432
+ 'branch-protection',
433
+ 'workflows',
434
+ 'signal-ingestion',
435
+ 'souls',
436
+ ];
437
+ if (!allowed.includes(addRaw)) {
438
+ return {
439
+ ok: false,
440
+ error: `--add: unknown feature '${addRaw}'. Expected one of: ${allowed.join(', ')}.`,
441
+ };
442
+ }
443
+ // 'souls' is handled separately in the action; cast the rest to WizardFlags['add']
444
+ return { ok: true, value: addRaw };
445
+ }
304
446
  export const initCommand = new Command('init')
305
447
  .description('Initialize AI-SDLC configuration in the current project')
306
448
  .option('--dry-run', 'Show what would be created without writing files')
@@ -308,10 +450,48 @@ export const initCommand = new Command('init')
308
450
  .option('--cursor', 'Force-install Cursor MCP config even if Cursor is not detected')
309
451
  .option('-d, --dir <path>', 'Config directory name', '.ai-sdlc')
310
452
  .option('--role <tier>', `Agent-role tool tier: ${AGENT_ROLE_TIERS.join(' | ')} (default: coding)`, 'coding')
453
+ // ── AISDLC-143 wizard flags ─────────────────────────────────────────
454
+ .option('-y, --yes', 'Accept all defaults (non-interactive; CI/scripts)')
455
+ .option('--with-dor', 'Scaffold Definition-of-Ready gate config + workflow')
456
+ .option('--with-attestation', 'Scaffold attestation infrastructure (audit-only)')
457
+ .option('--with-classifier', 'Scaffold review classifier config stub')
458
+ .option('--with-branch-protection', 'Apply recommended branch-protection rule to main (requires gh)')
459
+ // ── AISDLC-261 workflow scaffold flags ──────────────────────────────
460
+ .option('--with-workflows', 'Scaffold GitHub Actions workflow bundle (gate, review, attestation, auto-merge)')
461
+ .option('--force', 'Overwrite existing workflow files (use with --with-workflows or --add workflows)')
462
+ // ── AISDLC-348 RFC-0030 signal-ingestion scaffold flag ──────────────
463
+ .option('--with-signal-ingestion', 'Scaffold RFC-0030 signal-ingestion config (default OFF; opt in via AI_SDLC_SIGNAL_INGESTION soak)')
464
+ .option('--add <feature>', 'Extend an already-initialized repo with a single feature: dor | attestation | classifier | branch-protection | workflows | signal-ingestion | souls')
465
+ // ── RFC-0009 Phase 2.2 — per-soul DSB scaffolding ──────────────────
466
+ .option('--souls <slugs>', 'Comma-separated soul slugs for --add souls (e.g. --souls soul-a,soul-b,soul-c)')
467
+ .option('--platform-dsb <name>', 'Platform-root DSB name for --add souls extends reference (default: platform-dsb)', 'platform-dsb')
468
+ .option('--workspace <name>', 'Opt into a per-workspace install at packages/<name>/.ai-sdlc/ instead of the git-root default')
311
469
  .action(async (opts) => {
312
- const projectDir = process.cwd();
313
470
  const configDirName = opts.dir ?? '.ai-sdlc';
314
471
  const dryRun = !!opts.dryRun;
472
+ // ── AISDLC-262: resolve install target via git rev-parse --show-toplevel ─
473
+ // Default: install at the git root. If the root already has .ai-sdlc/,
474
+ // refuse with a clear message unless --workspace <name> is passed.
475
+ // Printed on the first output line so adopters can sanity-check the
476
+ // resolved target before any files are written (AC #4).
477
+ const targetResult = resolveInstallTarget({
478
+ cwd: process.cwd(),
479
+ workspace: typeof opts.workspace === 'string' ? opts.workspace : undefined,
480
+ configDirName,
481
+ // --add is the extension path: the root's .ai-sdlc/ already exists by
482
+ // design, so the "already installed" nesting check must be suppressed.
483
+ skipExistingCheck: !!opts.add,
484
+ });
485
+ if (targetResult.error) {
486
+ console.error(`Error: ${targetResult.error}`);
487
+ process.exitCode = 1;
488
+ return;
489
+ }
490
+ const projectDir = targetResult.installDir;
491
+ if (targetResult.resolved) {
492
+ console.log(`Resolved install target: ${projectDir}`);
493
+ console.log('');
494
+ }
315
495
  const tierInput = (opts.role ?? 'coding');
316
496
  if (!AGENT_ROLE_TIERS.includes(tierInput)) {
317
497
  console.error(`Error: invalid --role value '${tierInput}'. Expected one of: ${AGENT_ROLE_TIERS.join(', ')}.`);
@@ -320,6 +500,76 @@ export const initCommand = new Command('init')
320
500
  }
321
501
  const tier = tierInput;
322
502
  const cursorOptIn = !!opts.cursor;
503
+ // ── Validate --add early so we error before doing any work. ────────
504
+ const addCheck = validateAddArg(opts.add);
505
+ if (!addCheck.ok) {
506
+ console.error(`Error: ${addCheck.error}`);
507
+ process.exitCode = 1;
508
+ return;
509
+ }
510
+ const flags = buildWizardFlags(opts);
511
+ // ── --add souls path (RFC-0009 Phase 2.2) ─────────────────────────
512
+ // Scaffold per-soul DSB templates for a Tessellated Platform.
513
+ // Usage: ai-sdlc init --add souls --souls soul-a,soul-b,soul-c
514
+ if (addCheck.value === 'souls') {
515
+ const soulsRaw = typeof opts.souls === 'string' ? opts.souls : '';
516
+ const platformDsbName = typeof opts.platformDsb === 'string' ? opts.platformDsb : 'platform-dsb';
517
+ const soulSlugs = soulsRaw
518
+ .split(',')
519
+ .map((slug) => slug.trim())
520
+ .filter((slug) => slug.length > 0);
521
+ if (soulSlugs.length === 0) {
522
+ console.error('Error: --add souls requires --souls <slug1,slug2,...> with at least one soul slug.');
523
+ process.exitCode = 1;
524
+ return;
525
+ }
526
+ // Validate soul slugs (must match ^[a-z0-9-]+$ per RFC-0009 §5.2 soulId pattern)
527
+ const invalidSlugs = soulSlugs.filter((slug) => !/^[a-z0-9-]+$/.test(slug));
528
+ if (invalidSlugs.length > 0) {
529
+ console.error(`Error: invalid soul slug(s): ${invalidSlugs.join(', ')}. Slugs must match ^[a-z0-9-]+$.`);
530
+ process.exitCode = 1;
531
+ return;
532
+ }
533
+ const aiSdlcDir = join(projectDir, configDirName);
534
+ console.log(`Scaffolding per-soul DSB templates in ${aiSdlcDir}/souls/:`);
535
+ console.log('');
536
+ scaffoldSoulDsbs(soulSlugs, aiSdlcDir, {
537
+ platformDsbName,
538
+ dryRun,
539
+ prefix: ' ',
540
+ });
541
+ console.log('');
542
+ console.log(`Done. Edit each .ai-sdlc/souls/<slug>/design-system-binding.yaml to`);
543
+ console.log(`configure soul-specific design system bindings.`);
544
+ return;
545
+ }
546
+ // ── --add path: extend an already-initialized repo ────────────────
547
+ // AC #7: skip the "always-scaffold-baseline" path entirely; the
548
+ // wizard dispatcher's `--add` branch knows to write only the chosen
549
+ // feature's templates (no pipeline.yaml, no MCP setup, no workspace
550
+ // detection). This is the safe re-run path on a repo that already
551
+ // ran `ai-sdlc init` once.
552
+ if (flags.add) {
553
+ const adapters = buildProductionAdapters();
554
+ const selection = await resolveFeatureSelection(flags, adapters);
555
+ console.log(`Extending AI-SDLC config with --add ${flags.add}:`);
556
+ console.log('');
557
+ const result = await applyFeatureSelection(projectDir, selection, flags, adapters);
558
+ renderNextSteps(selection, result, adapters);
559
+ // Reviewer feedback (round 2, suggestion #5): when the operator
560
+ // explicitly requested branch-protection via a non-interactive flag
561
+ // (--add branch-protection here; --yes / --with-branch-protection
562
+ // in runWizardStage) and the apply failed (gh missing, not
563
+ // authenticated, etc.), surface a non-zero exit so CI scripts can
564
+ // detect failure instead of seeing the silent log line.
565
+ if (flags.add === 'branch-protection' &&
566
+ result.branchProtection &&
567
+ !result.branchProtection.applied &&
568
+ result.branchProtection.error) {
569
+ process.exitCode = 1;
570
+ }
571
+ return;
572
+ }
323
573
  // ── version provenance (AC #1) ────────────────────────────────────
324
574
  const versions = resolveVersions({ workDir: projectDir });
325
575
  console.log(formatVersionBlock(versions));
@@ -377,6 +627,12 @@ export const initCommand = new Command('init')
377
627
  }
378
628
  }
379
629
  console.log(`\nAI-SDLC workspace initialized in ${projectDir}/`);
630
+ // ── AISDLC-143 wizard (workspace root) ────────────────────────
631
+ // The wizard runs ONCE at the workspace root: the baseline gate
632
+ // workflow + per-feature workflows live at `<workspace-root>/.github/`,
633
+ // not in each child repo. Children share the same CI from the
634
+ // root since GHA workflows always live at the repo root anyway.
635
+ await runWizardStage(projectDir, flags);
380
636
  console.log(`Run 'ai-sdlc health' to verify your configuration.`);
381
637
  }
382
638
  else {
@@ -399,7 +655,50 @@ export const initCommand = new Command('init')
399
655
  }
400
656
  }
401
657
  console.log(`\nAI-SDLC config initialized in ${join(projectDir, configDirName)}/`);
658
+ // ── AISDLC-143 wizard (single-repo) ──────────────────────────
659
+ await runWizardStage(projectDir, flags);
402
660
  console.log(`Run 'ai-sdlc health' to verify your configuration.`);
403
661
  }
404
662
  });
663
+ /**
664
+ * Run the AISDLC-143 wizard stage: prompt the user (or short-circuit on
665
+ * --yes / --with-X), apply the chosen feature templates, append the
666
+ * CLAUDE.md pointer, and render the "next steps" summary.
667
+ *
668
+ * Pulled out of the inline action body so both the single-repo and
669
+ * workspace-root branches share the same wiring. Adapters are built
670
+ * once here (production = real disk writes; tests inject stubs by
671
+ * calling `applyFeatureSelection`/`renderNextSteps` directly).
672
+ */
673
+ async function runWizardStage(projectDir, flags) {
674
+ const adapters = buildProductionAdapters();
675
+ console.log('');
676
+ console.log('━━━ Compliance posture ━━━');
677
+ console.log('');
678
+ // RFC-0022 §7 / AISDLC-324: compliance step runs BEFORE gate-config feature prompts
679
+ await runComplianceStep(projectDir, flags, adapters);
680
+ console.log('');
681
+ console.log('━━━ Feature wizard ━━━');
682
+ console.log('');
683
+ const selection = await resolveFeatureSelection(flags, adapters);
684
+ console.log('');
685
+ console.log('Scaffolding selected features:');
686
+ const result = await applyFeatureSelection(projectDir, selection, flags, adapters);
687
+ ensureClaudeMdPointer(projectDir, adapters, flags.dryRun);
688
+ renderNextSteps(selection, result, adapters);
689
+ // Reviewer feedback (round 2, suggestion #5): when branch-protection
690
+ // was non-interactively requested (--with-branch-protection or --yes,
691
+ // both used by CI scripts) and the apply failed (gh missing, not
692
+ // authenticated, etc.), surface a non-zero exit so CI can detect
693
+ // failure instead of seeing the silent log line. Interactive prompt
694
+ // answers don't trip this — the human already saw the error and can
695
+ // re-run `ai-sdlc init --add branch-protection` themselves.
696
+ const branchProtectionRequestedNonInteractively = flags.yes || flags.withBranchProtection;
697
+ if (branchProtectionRequestedNonInteractively &&
698
+ result.branchProtection &&
699
+ !result.branchProtection.applied &&
700
+ result.branchProtection.error) {
701
+ process.exitCode = 1;
702
+ }
703
+ }
405
704
  //# sourceMappingURL=init.js.map
@@ -8,6 +8,8 @@ export const runCommand = new Command('run')
8
8
  .description('Run the AI-SDLC pipeline for a specific issue')
9
9
  .requiredOption('-i, --issue <id>', 'Issue ID to process')
10
10
  .option('--state <path>', 'SQLite state database path')
11
+ .option('--runner <name>', 'Select a registered runner by name (e.g. claude-code, copilot, cursor). ' +
12
+ 'Fails fast when the name is not registered — no silent fallback.')
11
13
  .action(async (opts, cmd) => {
12
14
  const globalOpts = cmd.parent?.opts() ?? {};
13
15
  const format = globalOpts.format ?? 'table';
@@ -16,7 +18,13 @@ export const runCommand = new Command('run')
16
18
  statePath: opts.state,
17
19
  });
18
20
  try {
19
- const result = await orchestrator.run(opts.issue);
21
+ // opts.runner maps to ExecuteOptions.runnerName; resolveRunner() in execute.ts
22
+ // applies the full precedence chain and throws on unknown names.
23
+ // Only pass overrides when a flag is explicitly set to avoid breaking callers
24
+ // that match on the exact argument list (e.g. existing tests).
25
+ const result = opts.runner
26
+ ? await orchestrator.run(opts.issue, { runnerName: opts.runner })
27
+ : await orchestrator.run(opts.issue);
20
28
  console.log(formatOutput(format, {
21
29
  type: 'run',
22
30
  issueId: opts.issue,
@@ -16,4 +16,15 @@ import { type VersionTriple } from './versions.js';
16
16
  * exact factory at startup.
17
17
  */
18
18
  export declare function buildProgram(versions: VersionTriple): Command;
19
+ /**
20
+ * Determine whether this module is being run as the CLI entry point,
21
+ * correctly handling npm bin symlinks.
22
+ *
23
+ * Exported for unit-testing; production callers should use the module-level
24
+ * `isMainEntry` constant instead.
25
+ *
26
+ * @param moduleUrl - The `import.meta.url` of the caller module.
27
+ * @param argv1 - `process.argv[1]` (the script path Node was given).
28
+ */
29
+ export declare function computeIsMainEntry(moduleUrl: string, argv1: string | undefined): boolean;
19
30
  //# sourceMappingURL=index.d.ts.map
package/dist/cli/index.js CHANGED
@@ -7,6 +7,8 @@
7
7
  * unknown-subcommand hint that points at the upgrade flow when version
8
8
  * drift is detected.
9
9
  */
10
+ import { fileURLToPath } from 'url';
11
+ import { realpathSync } from 'fs';
10
12
  import { Command } from 'commander';
11
13
  import { initCommand } from './commands/init.js';
12
14
  import { runCommand } from './commands/run.js';
@@ -83,14 +85,46 @@ export function buildProgram(versions) {
83
85
  // Bin entry: only run argv parsing when invoked as a script, not when the
84
86
  // module is imported by tests.
85
87
  //
86
- // `process.argv[1]` is the path Node resolved for the entry script.
87
- // When it ends with the compiled CLI bundle (`dist/cli/index.js`) we are
88
- // the main entry. When imported (e.g. via Vitest) argv[1] points at the
89
- // test runner, so we skip parsing.
90
- const isMainEntry = (() => {
91
- const entry = process.argv[1] ?? '';
92
- return entry.endsWith('cli/index.js') || entry.endsWith('cli/index.ts');
93
- })();
88
+ // IMPORTANT: Node does NOT resolve symlinks for `process.argv[1]`. When
89
+ // the CLI is installed globally via npm (`npm install -g`), the bin path
90
+ // is a symlink (e.g. `/usr/local/bin/ai-sdlc`) that points to the real
91
+ // `dist/cli/index.js`. The old `endsWith('cli/index.js')` check failed
92
+ // for that case because argv[1] held the symlink path, not the target.
93
+ //
94
+ // Fix: compare `import.meta.url` (always the real module's file URL) to
95
+ // `pathToFileURL(realpathSync(argv[1]))` (which resolves the symlink).
96
+ // When imported by Vitest, argv[1] points at the test runner, so
97
+ // `realpathSync` still returns a path that differs from import.meta.url
98
+ // and the guard correctly returns false.
99
+ /**
100
+ * Determine whether this module is being run as the CLI entry point,
101
+ * correctly handling npm bin symlinks.
102
+ *
103
+ * Exported for unit-testing; production callers should use the module-level
104
+ * `isMainEntry` constant instead.
105
+ *
106
+ * @param moduleUrl - The `import.meta.url` of the caller module.
107
+ * @param argv1 - `process.argv[1]` (the script path Node was given).
108
+ */
109
+ export function computeIsMainEntry(moduleUrl, argv1) {
110
+ if (!argv1)
111
+ return false;
112
+ try {
113
+ // Resolve symlinks on BOTH sides before comparing, so that:
114
+ // 1. npm bin symlinks in argv1 resolve to the real dist/cli/index.js.
115
+ // 2. OS-level dir symlinks (e.g. macOS /tmp → /private/tmp) don't cause
116
+ // spurious mismatches when moduleUrl itself was built from an
117
+ // unresolved path.
118
+ const realArgv1 = realpathSync(argv1);
119
+ const realModule = realpathSync(fileURLToPath(moduleUrl));
120
+ return realArgv1 === realModule;
121
+ }
122
+ catch {
123
+ // realpathSync / fileURLToPath can throw — treat as "not main".
124
+ return false;
125
+ }
126
+ }
127
+ const isMainEntry = computeIsMainEntry(import.meta.url, process.argv[1]);
94
128
  if (isMainEntry) {
95
129
  const program = buildProgram(resolveVersions());
96
130
  program.parse();
@@ -0,0 +1,79 @@
1
+ /**
2
+ * Regime → DerivedGates composer (RFC-0022 §9 Phase 2).
3
+ *
4
+ * Reads the CompliancePosture's declared regime list, looks up each regime's
5
+ * DerivedGates values from spec/compliance/regime-mappings.yaml, composes
6
+ * them using "tightest-wins" semantics (RFC-0022 §6), applies adopter
7
+ * regimeOverrides per-regime (OQ-1), and finally applies operator-level
8
+ * derivedGates overrides last.
9
+ *
10
+ * Key design decisions:
11
+ * - YAML mapping file is read at call time (not module load) so tests can
12
+ * supply a custom mappings path without patching module state.
13
+ * - Tightest-wins is axis-specific: ordinal enums max, boolean OR, numeric max.
14
+ * - UnknownRegime is thrown when a declared regime ID has no entry in the
15
+ * mapping table (Phase 2 activation of the placeholder from Phase 1 errors.ts).
16
+ * - Adopter regimeOverrides (OQ-1) let per-regime control values be adjusted
17
+ * before the operator-override pass, enabling bespoke auditor interpretations
18
+ * without changing the canonical mapping table.
19
+ * - Operator derivedGates overrides (validated by the loader) always win last.
20
+ */
21
+ import type { CompliancePosture, DerivedGates } from './types.js';
22
+ /**
23
+ * Options for `composePostureDerivedGates`.
24
+ */
25
+ export interface ComposerOptions {
26
+ /**
27
+ * Absolute path to the regime-mappings.yaml file.
28
+ * Defaults to resolving `spec/compliance/regime-mappings.yaml` from the
29
+ * repository root (detected by walking up from __dirname).
30
+ *
31
+ * Override in tests to supply fixture mapping files.
32
+ */
33
+ mappingsPath?: string;
34
+ /**
35
+ * Root directory for finding the default mappings file.
36
+ * Defaults to walking up from the module file location.
37
+ */
38
+ repoRoot?: string;
39
+ }
40
+ /**
41
+ * The result of composing a CompliancePosture's regimes into DerivedGates.
42
+ */
43
+ export interface CompositionResult {
44
+ /**
45
+ * The composed DerivedGates after regime composition + overrides.
46
+ */
47
+ derivedGates: DerivedGates;
48
+ /**
49
+ * List of regime IDs that were composed.
50
+ * Empty means baseline was used (no regimes declared).
51
+ */
52
+ composedRegimes: string[];
53
+ /**
54
+ * Regime IDs that had adopter regimeOverrides applied.
55
+ */
56
+ regimesWithAdopterOverrides: string[];
57
+ /**
58
+ * Gate fields that were overridden by the operator-level derivedGates.
59
+ */
60
+ operatorOverriddenFields: string[];
61
+ }
62
+ /**
63
+ * Compose a loaded CompliancePosture into a concrete DerivedGates value.
64
+ *
65
+ * Algorithm (RFC-0022 §6):
66
+ * 1. Start from BASELINE_DERIVED_GATES.
67
+ * 2. For each declared regime, look up its entry in regime-mappings.yaml.
68
+ * Throw UnknownRegime if not found.
69
+ * 3. Apply adopter regimeOverrides (per-regime) before composition (OQ-1).
70
+ * 4. Merge each regime's gates into the accumulator via tightest-wins.
71
+ * 5. Apply operator derivedGates overrides last — they always win.
72
+ *
73
+ * @param posture - A single CompliancePosture (element from the loader's list).
74
+ * @param options - Optional override for the mappings file path.
75
+ * @returns CompositionResult with the resolved DerivedGates.
76
+ * @throws UnknownRegime if any declared regime ID is not in the mapping table.
77
+ */
78
+ export declare function composePostureDerivedGates(posture: CompliancePosture, options?: ComposerOptions): CompositionResult;
79
+ //# sourceMappingURL=composer.d.ts.map