@ai-sdlc/orchestrator 0.10.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 (156) hide show
  1. package/dist/adapters.d.ts +42 -3
  2. package/dist/adapters.js +133 -3
  3. package/dist/admission-composite.d.ts +112 -1
  4. package/dist/admission-composite.js +85 -4
  5. package/dist/admission-enrichment.d.ts +12 -3
  6. package/dist/admission-enrichment.js +64 -13
  7. package/dist/admission-score.d.ts +30 -0
  8. package/dist/admission-score.js +4 -1
  9. package/dist/backlog-adapter.d.ts +45 -0
  10. package/dist/backlog-adapter.js +145 -1
  11. package/dist/calibration.d.ts +81 -0
  12. package/dist/calibration.js +76 -0
  13. package/dist/cli/commands/init-features.d.ts +301 -2
  14. package/dist/cli/commands/init-features.js +634 -7
  15. package/dist/cli/commands/init-templates.d.ts +198 -1
  16. package/dist/cli/commands/init-templates.js +943 -1
  17. package/dist/cli/commands/init.d.ts +45 -0
  18. package/dist/cli/commands/init.js +147 -5
  19. package/dist/cli/commands/run.js +9 -1
  20. package/dist/cli/index.d.ts +11 -0
  21. package/dist/cli/index.js +42 -8
  22. package/dist/compliance/composer.d.ts +79 -0
  23. package/dist/compliance/composer.js +258 -0
  24. package/dist/compliance/errors.d.ts +64 -0
  25. package/dist/compliance/errors.js +85 -0
  26. package/dist/compliance/loader.d.ts +52 -0
  27. package/dist/compliance/loader.js +124 -0
  28. package/dist/compliance/types.d.ts +184 -0
  29. package/dist/compliance/types.js +41 -0
  30. package/dist/compliance-clearance.d.ts +269 -0
  31. package/dist/compliance-clearance.js +269 -0
  32. package/dist/config.js +17 -0
  33. package/dist/cost-tracker.d.ts +22 -0
  34. package/dist/cost-tracker.js +41 -0
  35. package/dist/database/adapters/external.js +5 -1
  36. package/dist/embedding/adapters/openai-text-embedding-3-small.d.ts +71 -0
  37. package/dist/embedding/adapters/openai-text-embedding-3-small.js +190 -0
  38. package/dist/embedding/consumers/tessellation-drift.d.ts +74 -0
  39. package/dist/embedding/consumers/tessellation-drift.js +76 -0
  40. package/dist/embedding/cross-provider.d.ts +78 -0
  41. package/dist/embedding/cross-provider.js +75 -0
  42. package/dist/embedding/deprecation.d.ts +151 -0
  43. package/dist/embedding/deprecation.js +229 -0
  44. package/dist/embedding/errors.d.ts +90 -0
  45. package/dist/embedding/errors.js +150 -0
  46. package/dist/embedding/index.d.ts +29 -0
  47. package/dist/embedding/index.js +24 -0
  48. package/dist/embedding/pipeline-load.d.ts +146 -0
  49. package/dist/embedding/pipeline-load.js +178 -0
  50. package/dist/embedding/registry.d.ts +45 -0
  51. package/dist/embedding/registry.js +61 -0
  52. package/dist/embedding/stale-vector.d.ts +110 -0
  53. package/dist/embedding/stale-vector.js +92 -0
  54. package/dist/embedding/storage/index.d.ts +51 -0
  55. package/dist/embedding/storage/index.js +43 -0
  56. package/dist/embedding/storage/jsonl-backend.d.ts +150 -0
  57. package/dist/embedding/storage/jsonl-backend.js +332 -0
  58. package/dist/embedding/storage/types.d.ts +135 -0
  59. package/dist/embedding/storage/types.js +13 -0
  60. package/dist/embedding/types.d.ts +180 -0
  61. package/dist/embedding/types.js +10 -0
  62. package/dist/execute.d.ts +29 -2
  63. package/dist/execute.js +171 -30
  64. package/dist/index.d.ts +14 -2
  65. package/dist/index.js +19 -2
  66. package/dist/journey/inheritance-validator.d.ts +396 -0
  67. package/dist/journey/inheritance-validator.js +370 -0
  68. package/dist/journey/state-id-drift-rule.d.ts +137 -0
  69. package/dist/journey/state-id-drift-rule.js +245 -0
  70. package/dist/journey-sa2-router.d.ts +395 -0
  71. package/dist/journey-sa2-router.js +308 -0
  72. package/dist/runners/review-agent.js +6 -2
  73. package/dist/runners/runner-registry.d.ts +36 -0
  74. package/dist/runners/runner-registry.js +90 -0
  75. package/dist/runtime/attestations.d.ts +173 -13
  76. package/dist/runtime/attestations.js +252 -40
  77. package/dist/runtime/index.d.ts +1 -1
  78. package/dist/runtime/index.js +1 -1
  79. package/dist/sa-scoring/layer3-llm.js +6 -1
  80. package/dist/sa-scoring/revision-proposal-config.d.ts +178 -0
  81. package/dist/sa-scoring/revision-proposal-config.js +198 -0
  82. package/dist/sa-scoring/revision-proposal.d.ts +285 -0
  83. package/dist/sa-scoring/revision-proposal.js +417 -0
  84. package/dist/signal-ingestion/adapters/community-thread.d.ts +43 -0
  85. package/dist/signal-ingestion/adapters/community-thread.js +55 -0
  86. package/dist/signal-ingestion/adapters/in-app-feedback.d.ts +67 -0
  87. package/dist/signal-ingestion/adapters/in-app-feedback.js +51 -0
  88. package/dist/signal-ingestion/adapters/manual.d.ts +78 -0
  89. package/dist/signal-ingestion/adapters/manual.js +112 -0
  90. package/dist/signal-ingestion/adapters/support-ticket.d.ts +47 -0
  91. package/dist/signal-ingestion/adapters/support-ticket.js +51 -0
  92. package/dist/signal-ingestion/classifier.d.ts +205 -0
  93. package/dist/signal-ingestion/classifier.js +494 -0
  94. package/dist/signal-ingestion/clustering-types.d.ts +36 -0
  95. package/dist/signal-ingestion/clustering-types.js +14 -0
  96. package/dist/signal-ingestion/clustering.d.ts +200 -0
  97. package/dist/signal-ingestion/clustering.js +413 -0
  98. package/dist/signal-ingestion/config.d.ts +351 -0
  99. package/dist/signal-ingestion/config.js +587 -0
  100. package/dist/signal-ingestion/d1.d.ts +252 -0
  101. package/dist/signal-ingestion/d1.js +235 -0
  102. package/dist/signal-ingestion/errors.d.ts +73 -0
  103. package/dist/signal-ingestion/errors.js +108 -0
  104. package/dist/signal-ingestion/governance-events.d.ts +181 -0
  105. package/dist/signal-ingestion/governance-events.js +189 -0
  106. package/dist/signal-ingestion/index.d.ts +35 -0
  107. package/dist/signal-ingestion/index.js +53 -0
  108. package/dist/signal-ingestion/manual-share-metric.d.ts +93 -0
  109. package/dist/signal-ingestion/manual-share-metric.js +106 -0
  110. package/dist/signal-ingestion/registry.d.ts +40 -0
  111. package/dist/signal-ingestion/registry.js +137 -0
  112. package/dist/signal-ingestion/residency.d.ts +227 -0
  113. package/dist/signal-ingestion/residency.js +238 -0
  114. package/dist/signal-ingestion/significance.d.ts +554 -0
  115. package/dist/signal-ingestion/significance.js +555 -0
  116. package/dist/signal-ingestion/types.d.ts +191 -0
  117. package/dist/signal-ingestion/types.js +8 -0
  118. package/dist/substrate/drift-composition.d.ts +270 -0
  119. package/dist/substrate/drift-composition.js +306 -0
  120. package/dist/substrate/drift-tui-surface.d.ts +61 -0
  121. package/dist/substrate/drift-tui-surface.js +102 -0
  122. package/dist/substrate/identity-class.d.ts +176 -0
  123. package/dist/substrate/identity-class.js +201 -0
  124. package/dist/tessellation/cross-soul-provenance-rule.d.ts +133 -0
  125. package/dist/tessellation/cross-soul-provenance-rule.js +171 -0
  126. package/dist/tessellation/inter-soul-embedding-distance-rule.d.ts +61 -0
  127. package/dist/tessellation/inter-soul-embedding-distance-rule.js +67 -0
  128. package/dist/tessellation/rule-registry.d.ts +269 -0
  129. package/dist/tessellation/rule-registry.js +92 -0
  130. package/dist/tessellation/soul-slug-ast-scan-rule.d.ts +90 -0
  131. package/dist/tessellation/soul-slug-ast-scan-rule.js +158 -0
  132. package/dist/tessellation-admission.d.ts +162 -0
  133. package/dist/tessellation-admission.js +146 -0
  134. package/dist/tessellation-drift.d.ts +246 -0
  135. package/dist/tessellation-drift.js +250 -0
  136. package/dist/validate-config.js +13 -0
  137. package/dist/validate-issue.js +2 -2
  138. package/dist/variant/cardinality-activation.d.ts +126 -0
  139. package/dist/variant/cardinality-activation.js +101 -0
  140. package/dist/variant/deprecation-lifecycle.d.ts +184 -0
  141. package/dist/variant/deprecation-lifecycle.js +208 -0
  142. package/dist/variant/drift-extension.d.ts +136 -0
  143. package/dist/variant/drift-extension.js +164 -0
  144. package/dist/variant/engineering-review.d.ts +185 -0
  145. package/dist/variant/engineering-review.js +142 -0
  146. package/dist/variant/index.d.ts +32 -0
  147. package/dist/variant/index.js +32 -0
  148. package/dist/variant/inheritance-validator.d.ts +165 -0
  149. package/dist/variant/inheritance-validator.js +139 -0
  150. package/dist/variant/internal-adopter/index.d.ts +11 -0
  151. package/dist/variant/internal-adopter/index.js +10 -0
  152. package/dist/variant/internal-adopter/products.d.ts +156 -0
  153. package/dist/variant/internal-adopter/products.js +366 -0
  154. package/dist/variant-admission.d.ts +316 -0
  155. package/dist/variant-admission.js +247 -0
  156. package/package.json +10 -8
@@ -44,6 +44,51 @@ export type AgentRoleTier = 'coding' | 'research' | 'meta';
44
44
  export declare const AGENT_ROLE_TIERS: readonly AgentRoleTier[];
45
45
  /** Resolve the agent-role.yaml template for a given tier. Default = `coding`. */
46
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;
47
92
  /**
48
93
  * Build the WizardFlags struct from Commander's parsed options. Pulled
49
94
  * out into a function so tests can drive the wizard pipeline directly
@@ -34,7 +34,8 @@ import { detectAgentsDetailed, installMcpServer } from './mcp-setup.js';
34
34
  import { detectWorkspace, generateWorkspaceYaml } from './workspace-detect.js';
35
35
  import { detectGitRemote, applyRemoteToPipelineYaml } from './git-remote.js';
36
36
  import { resolveVersions, formatVersionBlock } from '../versions.js';
37
- import { applyFeatureSelection, buildProductionAdapters, ensureClaudeMdPointer, renderNextSteps, resolveFeatureSelection, } from './init-features.js';
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';
38
39
  export const PIPELINE_YAML = `apiVersion: ai-sdlc.io/v1alpha1
39
40
  kind: Pipeline
40
41
  metadata:
@@ -260,6 +261,51 @@ spec:
260
261
  action: demote-one-level
261
262
  cooldown: 2w
262
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
+ }
263
309
  const GITIGNORE_PATHS = ['.ai-sdlc/state.db', '.ai-sdlc/state/', '.ai-sdlc/audit.jsonl'];
264
310
  /** Ensure .gitignore includes AI-SDLC runtime artifact entries. */
265
311
  function ensureGitignore(projectDir, dryRun, prefix = '') {
@@ -287,6 +333,16 @@ function initProject(projectDir, configDirName, dryRun, prefix = '', options = {
287
333
  { name: 'agent-role.yaml', content: getAgentRoleYaml(options.tier) },
288
334
  { name: 'quality-gate.yaml', content: QUALITY_GATE_YAML },
289
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 },
290
346
  ];
291
347
  if (dryRun) {
292
348
  console.log(`${prefix}Would create ${configDir}/`);
@@ -344,7 +400,9 @@ export function buildWizardFlags(opts) {
344
400
  if (addRaw === 'dor' ||
345
401
  addRaw === 'attestation' ||
346
402
  addRaw === 'classifier' ||
347
- addRaw === 'branch-protection') {
403
+ addRaw === 'branch-protection' ||
404
+ addRaw === 'workflows' ||
405
+ addRaw === 'signal-ingestion') {
348
406
  add = addRaw;
349
407
  }
350
408
  return {
@@ -353,8 +411,12 @@ export function buildWizardFlags(opts) {
353
411
  withAttestation: !!opts.withAttestation,
354
412
  withClassifier: !!opts.withClassifier,
355
413
  withBranchProtection: !!opts.withBranchProtection,
414
+ withWorkflows: !!opts.withWorkflows,
415
+ withSignalIngestion: !!opts.withSignalIngestion,
356
416
  add,
357
417
  dryRun: !!opts.dryRun,
418
+ workspace: typeof opts.workspace === 'string' ? opts.workspace : undefined,
419
+ force: !!opts.force,
358
420
  };
359
421
  }
360
422
  /** Validate a `--add` arg and return either the normalized value or null+error. */
@@ -363,13 +425,22 @@ function validateAddArg(addRaw) {
363
425
  return { ok: true };
364
426
  if (typeof addRaw !== 'string')
365
427
  return { ok: false, error: `--add must be a string` };
366
- const allowed = ['dor', 'attestation', 'classifier', 'branch-protection'];
428
+ const allowed = [
429
+ 'dor',
430
+ 'attestation',
431
+ 'classifier',
432
+ 'branch-protection',
433
+ 'workflows',
434
+ 'signal-ingestion',
435
+ 'souls',
436
+ ];
367
437
  if (!allowed.includes(addRaw)) {
368
438
  return {
369
439
  ok: false,
370
440
  error: `--add: unknown feature '${addRaw}'. Expected one of: ${allowed.join(', ')}.`,
371
441
  };
372
442
  }
443
+ // 'souls' is handled separately in the action; cast the rest to WizardFlags['add']
373
444
  return { ok: true, value: addRaw };
374
445
  }
375
446
  export const initCommand = new Command('init')
@@ -385,11 +456,42 @@ export const initCommand = new Command('init')
385
456
  .option('--with-attestation', 'Scaffold attestation infrastructure (audit-only)')
386
457
  .option('--with-classifier', 'Scaffold review classifier config stub')
387
458
  .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')
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')
389
469
  .action(async (opts) => {
390
- const projectDir = process.cwd();
391
470
  const configDirName = opts.dir ?? '.ai-sdlc';
392
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
+ }
393
495
  const tierInput = (opts.role ?? 'coding');
394
496
  if (!AGENT_ROLE_TIERS.includes(tierInput)) {
395
497
  console.error(`Error: invalid --role value '${tierInput}'. Expected one of: ${AGENT_ROLE_TIERS.join(', ')}.`);
@@ -406,6 +508,41 @@ export const initCommand = new Command('init')
406
508
  return;
407
509
  }
408
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
+ }
409
546
  // ── --add path: extend an already-initialized repo ────────────────
410
547
  // AC #7: skip the "always-scaffold-baseline" path entirely; the
411
548
  // wizard dispatcher's `--add` branch knows to write only the chosen
@@ -536,6 +673,11 @@ export const initCommand = new Command('init')
536
673
  async function runWizardStage(projectDir, flags) {
537
674
  const adapters = buildProductionAdapters();
538
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('');
539
681
  console.log('━━━ Feature wizard ━━━');
540
682
  console.log('');
541
683
  const selection = await resolveFeatureSelection(flags, adapters);
@@ -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