@specship/specship-win32-arm64 0.21.2 → 0.23.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 (122) hide show
  1. package/lib/dist/activation/search-intercept.d.ts +59 -0
  2. package/lib/dist/activation/search-intercept.d.ts.map +1 -0
  3. package/lib/dist/activation/search-intercept.js +195 -0
  4. package/lib/dist/activation/search-intercept.js.map +1 -0
  5. package/lib/dist/bin/specship.js +372 -11
  6. package/lib/dist/bin/specship.js.map +1 -1
  7. package/lib/dist/bin/uninstall.d.ts +4 -3
  8. package/lib/dist/bin/uninstall.d.ts.map +1 -1
  9. package/lib/dist/bin/uninstall.js +4 -3
  10. package/lib/dist/bin/uninstall.js.map +1 -1
  11. package/lib/dist/commands/specship/jira.md +74 -0
  12. package/lib/dist/commands/specship/spec.md +61 -1
  13. package/lib/dist/extraction/index.d.ts +6 -0
  14. package/lib/dist/extraction/index.d.ts.map +1 -1
  15. package/lib/dist/extraction/index.js.map +1 -1
  16. package/lib/dist/extraction/specs/markdown-spec-extractor.d.ts.map +1 -1
  17. package/lib/dist/extraction/specs/markdown-spec-extractor.js +8 -1
  18. package/lib/dist/extraction/specs/markdown-spec-extractor.js.map +1 -1
  19. package/lib/dist/index.d.ts +7 -0
  20. package/lib/dist/index.d.ts.map +1 -1
  21. package/lib/dist/index.js +45 -18
  22. package/lib/dist/index.js.map +1 -1
  23. package/lib/dist/installer/index.d.ts +20 -6
  24. package/lib/dist/installer/index.d.ts.map +1 -1
  25. package/lib/dist/installer/index.js +123 -63
  26. package/lib/dist/installer/index.js.map +1 -1
  27. package/lib/dist/installer/targets/claude.d.ts +9 -0
  28. package/lib/dist/installer/targets/claude.d.ts.map +1 -1
  29. package/lib/dist/installer/targets/claude.js +46 -0
  30. package/lib/dist/installer/targets/claude.js.map +1 -1
  31. package/lib/dist/installer/targets/gemini.d.ts +78 -0
  32. package/lib/dist/installer/targets/gemini.d.ts.map +1 -0
  33. package/lib/dist/installer/targets/gemini.js +200 -0
  34. package/lib/dist/installer/targets/gemini.js.map +1 -0
  35. package/lib/dist/installer/targets/registry.d.ts +5 -2
  36. package/lib/dist/installer/targets/registry.d.ts.map +1 -1
  37. package/lib/dist/installer/targets/registry.js +7 -3
  38. package/lib/dist/installer/targets/registry.js.map +1 -1
  39. package/lib/dist/installer/targets/types.d.ts +6 -2
  40. package/lib/dist/installer/targets/types.d.ts.map +1 -1
  41. package/lib/dist/jira/auto-publish.d.ts +67 -0
  42. package/lib/dist/jira/auto-publish.d.ts.map +1 -0
  43. package/lib/dist/jira/auto-publish.js +256 -0
  44. package/lib/dist/jira/auto-publish.js.map +1 -0
  45. package/lib/dist/jira/board-first.d.ts +64 -0
  46. package/lib/dist/jira/board-first.d.ts.map +1 -0
  47. package/lib/dist/jira/board-first.js +91 -0
  48. package/lib/dist/jira/board-first.js.map +1 -0
  49. package/lib/dist/jira/client.d.ts +87 -1
  50. package/lib/dist/jira/client.d.ts.map +1 -1
  51. package/lib/dist/jira/client.js +249 -2
  52. package/lib/dist/jira/client.js.map +1 -1
  53. package/lib/dist/jira/config.d.ts +1 -1
  54. package/lib/dist/jira/config.d.ts.map +1 -1
  55. package/lib/dist/jira/config.js +14 -3
  56. package/lib/dist/jira/config.js.map +1 -1
  57. package/lib/dist/jira/coverage.d.ts +105 -0
  58. package/lib/dist/jira/coverage.d.ts.map +1 -0
  59. package/lib/dist/jira/coverage.js +170 -0
  60. package/lib/dist/jira/coverage.js.map +1 -0
  61. package/lib/dist/jira/index.d.ts +1 -0
  62. package/lib/dist/jira/index.d.ts.map +1 -1
  63. package/lib/dist/jira/index.js +1 -0
  64. package/lib/dist/jira/index.js.map +1 -1
  65. package/lib/dist/jira/milestone-comment.d.ts +95 -0
  66. package/lib/dist/jira/milestone-comment.d.ts.map +1 -0
  67. package/lib/dist/jira/milestone-comment.js +271 -0
  68. package/lib/dist/jira/milestone-comment.js.map +1 -0
  69. package/lib/dist/jira/publish.d.ts +86 -2
  70. package/lib/dist/jira/publish.d.ts.map +1 -1
  71. package/lib/dist/jira/publish.js +0 -0
  72. package/lib/dist/jira/publish.js.map +1 -1
  73. package/lib/dist/jira/published-specs.d.ts +29 -0
  74. package/lib/dist/jira/published-specs.d.ts.map +1 -0
  75. package/lib/dist/jira/published-specs.js +147 -0
  76. package/lib/dist/jira/published-specs.js.map +1 -0
  77. package/lib/dist/jira/reconcile.d.ts +81 -0
  78. package/lib/dist/jira/reconcile.d.ts.map +1 -0
  79. package/lib/dist/jira/reconcile.js +111 -0
  80. package/lib/dist/jira/reconcile.js.map +1 -0
  81. package/lib/dist/jira/regression-pack.d.ts +547 -0
  82. package/lib/dist/jira/regression-pack.d.ts.map +1 -0
  83. package/lib/dist/jira/regression-pack.js +986 -0
  84. package/lib/dist/jira/regression-pack.js.map +1 -0
  85. package/lib/dist/jira/repo-config.d.ts +73 -0
  86. package/lib/dist/jira/repo-config.d.ts.map +1 -0
  87. package/lib/dist/jira/repo-config.js +249 -0
  88. package/lib/dist/jira/repo-config.js.map +1 -0
  89. package/lib/dist/jira/spec-amend.d.ts +46 -0
  90. package/lib/dist/jira/spec-amend.d.ts.map +1 -0
  91. package/lib/dist/jira/spec-amend.js +232 -0
  92. package/lib/dist/jira/spec-amend.js.map +1 -0
  93. package/lib/dist/jira/spec-writer.d.ts +22 -0
  94. package/lib/dist/jira/spec-writer.d.ts.map +1 -1
  95. package/lib/dist/jira/spec-writer.js +113 -0
  96. package/lib/dist/jira/spec-writer.js.map +1 -1
  97. package/lib/dist/jira/types.d.ts +62 -0
  98. package/lib/dist/jira/types.d.ts.map +1 -1
  99. package/lib/dist/jira/types.js +15 -1
  100. package/lib/dist/jira/types.js.map +1 -1
  101. package/lib/dist/mcp/jira-tools.d.ts +102 -0
  102. package/lib/dist/mcp/jira-tools.d.ts.map +1 -1
  103. package/lib/dist/mcp/jira-tools.js +826 -53
  104. package/lib/dist/mcp/jira-tools.js.map +1 -1
  105. package/lib/dist/mcp/server-instructions.d.ts +1 -1
  106. package/lib/dist/mcp/server-instructions.d.ts.map +1 -1
  107. package/lib/dist/mcp/server-instructions.js +4 -0
  108. package/lib/dist/mcp/server-instructions.js.map +1 -1
  109. package/lib/dist/mcp/spec-tools.d.ts.map +1 -1
  110. package/lib/dist/mcp/spec-tools.js +7 -1
  111. package/lib/dist/mcp/spec-tools.js.map +1 -1
  112. package/lib/dist/mcp/tools.d.ts +53 -0
  113. package/lib/dist/mcp/tools.d.ts.map +1 -1
  114. package/lib/dist/mcp/tools.js +255 -8
  115. package/lib/dist/mcp/tools.js.map +1 -1
  116. package/lib/dist/resolution/spec-link-resolver.d.ts +2 -0
  117. package/lib/dist/resolution/spec-link-resolver.d.ts.map +1 -1
  118. package/lib/dist/resolution/spec-link-resolver.js +11 -3
  119. package/lib/dist/resolution/spec-link-resolver.js.map +1 -1
  120. package/lib/node_modules/.package-lock.json +1 -1
  121. package/lib/package.json +1 -1
  122. package/package.json +1 -1
@@ -872,10 +872,32 @@ function main() {
872
872
  }
873
873
  const { default: SpecShip } = await loadSpecShip();
874
874
  const cg = await SpecShip.open(projectPath);
875
+ // JIRA auto-publish (REQ-JIRATEAM-002): print the summary the sync's
876
+ // auto-publish pass produced. Silent when the repo has no binding
877
+ // (sync omits the field entirely). Never fails or blocks output.
878
+ const pushJiraAutoPublish = async (result) => {
879
+ if (!result.jiraAutoPublish)
880
+ return;
881
+ try {
882
+ const { summarizeAutoPublishReport } = await Promise.resolve().then(() => __importStar(require('../jira/auto-publish')));
883
+ const line = summarizeAutoPublishReport(result.jiraAutoPublish);
884
+ if (line)
885
+ console.log(line);
886
+ for (const r of result.jiraAutoPublish.results) {
887
+ if (r.status === 'failed') {
888
+ console.log(`JIRA: ${r.specId} failed — ${r.note ?? 'unknown error'}`);
889
+ }
890
+ }
891
+ }
892
+ catch {
893
+ /* summary is best-effort */
894
+ }
895
+ };
875
896
  if (options.quiet) {
876
897
  const result = await cg.sync();
877
898
  pushDriftNotices(result);
878
899
  await pushJiraDriftComments(cg, result);
900
+ await pushJiraAutoPublish(result);
879
901
  pushDriftSummary(cg);
880
902
  cg.destroy();
881
903
  return;
@@ -905,6 +927,7 @@ function main() {
905
927
  }
906
928
  pushDriftNotices(result);
907
929
  await pushJiraDriftComments(cg, result);
930
+ await pushJiraAutoPublish(result);
908
931
  pushDriftSummary(cg);
909
932
  clack.outro('Done');
910
933
  cg.destroy();
@@ -2445,14 +2468,25 @@ function main() {
2445
2468
  .option('--statusline', 'Wire the SpecShip status-line segment into Claude (skips the prompt; never overwrites an existing status line)')
2446
2469
  .option('--skip-statusline', 'Do not add the status-line segment (skips the prompt)')
2447
2470
  .option('--skip-index', 'Do not offer to index the current project (an explicit opt-out for automation)')
2448
- .option('--print-config', 'Print MCP config snippet for Claude Code and exit (no file writes)')
2449
- // -t/--target is vestigial — kept so existing `--target claude` / `--target auto`
2450
- // invocations (including our own offline-install scripts) keep working.
2451
- .option('-t, --target <ids>', '(vestigial) accepted: "claude" | "auto" | "all" | "none"')
2471
+ .option('--print-config', 'Print the MCP config snippet and exit (no file writes). Claude Code by default; `--target gemini` prints the Gemini CLI settings snippet')
2472
+ // -t/--target SELECTS the agents to wire (GEMINI-TARGET-DOC, REQ-GEMINI-002)
2473
+ // and also picks the snippet --print-config prints. The legacy spellings
2474
+ // ("claude" / "auto" / "all" / "none") keep their Claude-only meanings, so
2475
+ // existing invocations — including our own offline-install scripts — behave
2476
+ // exactly as before.
2477
+ .option('-t, --target <ids>', 'Agents to wire, comma-separated: "claude" (default) | "gemini" | "claude,gemini". Also selects the snippet `--print-config` prints. Legacy: "auto"/"all" = claude, "none" = skip')
2452
2478
  .action(async (opts) => {
2453
2479
  if (opts.printConfig) {
2454
- const { claudeTarget } = await Promise.resolve().then(() => __importStar(require('../installer/targets/claude')));
2455
2480
  const loc = (opts.location === 'local' ? 'local' : 'global');
2481
+ // Only `gemini` selects the non-default snippet; every other value
2482
+ // (including the vestigial "auto" / "all" / "none") stays Claude, so
2483
+ // existing invocations print exactly what they printed before.
2484
+ if (opts.target === 'gemini') {
2485
+ const { geminiTarget } = await Promise.resolve().then(() => __importStar(require('../installer/targets/gemini')));
2486
+ process.stdout.write(geminiTarget.printConfig(loc));
2487
+ return;
2488
+ }
2489
+ const { claudeTarget } = await Promise.resolve().then(() => __importStar(require('../installer/targets/claude')));
2456
2490
  process.stdout.write(claudeTarget.printConfig(loc));
2457
2491
  return;
2458
2492
  }
@@ -2551,9 +2585,13 @@ function main() {
2551
2585
  console.log(chalk.dim(' (advisory — diagnose anytime with `specship doctor`)'));
2552
2586
  }
2553
2587
  // Restart reminder (REQ-HANDSHAKE-001): an MCP server added to the config
2554
- // is NOT visible to a Claude Code session that was already open.
2588
+ // is NOT visible to an agent session that was already open. Names the
2589
+ // agents actually wired, so a `--target gemini` install doesn't tell the
2590
+ // user to restart Claude Code.
2591
+ const { resolveInstallTargets } = await Promise.resolve().then(() => __importStar(require('../installer')));
2592
+ const agents = resolveInstallTargets(opts.target).map((t) => t.displayName).join(' / ');
2555
2593
  console.log();
2556
- info('Restart Claude Code (or run `/mcp`) to load the SpecShip server — it is not visible in a session that is already open.');
2594
+ info(`Restart ${agents} (or run \`/mcp\`) to load the SpecShip server — it is not visible in a session that is already open.`);
2557
2595
  }
2558
2596
  catch (err) {
2559
2597
  error(err instanceof Error ? err.message : String(err));
@@ -2781,6 +2819,54 @@ function main() {
2781
2819
  }
2782
2820
  process.exit(0);
2783
2821
  });
2822
+ /**
2823
+ * specship search-intercept (STEER-HOOK-DOC, REQ-STEER-004/005)
2824
+ *
2825
+ * Internal PreToolUse hook command on search-shaped tools (Grep/Glob): emit
2826
+ * the point-of-use redirect to specship_explore, at most once per session and
2827
+ * only when the session hasn't used specship yet. STRICTLY ADVISORY — the
2828
+ * output is additionalContext, never a permission decision, and the command
2829
+ * exits 0 on every path (including internal errors) so the matched call
2830
+ * always proceeds (REQ-STEER-004.A2).
2831
+ */
2832
+ program
2833
+ .command('search-intercept')
2834
+ .description('Internal hook: point-of-use redirect from search tools to specship_explore (PreToolUse)')
2835
+ .action(async () => {
2836
+ try {
2837
+ let cwd = process.cwd();
2838
+ let sessionId = null;
2839
+ let transcriptPath = null;
2840
+ const chunks = [];
2841
+ try {
2842
+ for await (const c of process.stdin)
2843
+ chunks.push(c);
2844
+ }
2845
+ catch { /* no stdin — fall back to process.cwd(), untracked session */ }
2846
+ const raw = Buffer.concat(chunks).toString('utf-8').trim();
2847
+ if (raw) {
2848
+ try {
2849
+ const parsed = JSON.parse(raw);
2850
+ if (typeof parsed?.cwd === 'string' && parsed.cwd.length > 0)
2851
+ cwd = parsed.cwd;
2852
+ if (typeof parsed?.session_id === 'string' && parsed.session_id.length > 0) {
2853
+ sessionId = parsed.session_id;
2854
+ }
2855
+ if (typeof parsed?.transcript_path === 'string' && parsed.transcript_path.length > 0) {
2856
+ transcriptPath = parsed.transcript_path;
2857
+ }
2858
+ }
2859
+ catch { /* not JSON — keep defaults */ }
2860
+ }
2861
+ const { buildSearchIntercept } = await Promise.resolve().then(() => __importStar(require('../activation/search-intercept')));
2862
+ const additionalContext = buildSearchIntercept({ cwd, sessionId, transcriptPath });
2863
+ if (additionalContext) {
2864
+ process.stdout.write(JSON.stringify({ hookSpecificOutput: { hookEventName: 'PreToolUse', additionalContext } }) + '\n');
2865
+ }
2866
+ }
2867
+ catch { /* advisory only — never block the matched call (REQ-STEER-004.A2) */ }
2868
+ process.exit(0);
2869
+ });
2784
2870
  /**
2785
2871
  * specship uninstall
2786
2872
  *
@@ -3296,6 +3382,23 @@ function main() {
3296
3382
  process.exit(1);
3297
3383
  }
3298
3384
  executor.approve(arg, options.comment);
3385
+ // Milestone: plan_approved (REQ-JIRATEAM-003.A1). Only fires for a
3386
+ // JIRA-tracked run (metadata.jira present) — a non-JIRA run is a
3387
+ // silent no-op. Soft-fails internally, so a JIRA hiccup never
3388
+ // reverses the approval.
3389
+ try {
3390
+ const run = cg.getSpecQueries().getWorkflowRunById(arg);
3391
+ const jira = run?.metadata?.jira;
3392
+ if (jira?.issueKey) {
3393
+ const { resolveJiraCredentials, JiraClient } = await Promise.resolve().then(() => __importStar(require('../jira')));
3394
+ const { postMilestoneComment } = await Promise.resolve().then(() => __importStar(require('../jira/milestone-comment')));
3395
+ const creds = resolveJiraCredentials();
3396
+ await postMilestoneComment(new JiraClient(creds), jira.issueKey, 'plan_approved', { specId: jira.specId ?? jira.issueKey }, { projectRoot });
3397
+ }
3398
+ }
3399
+ catch {
3400
+ /* soft-fail — the approval already committed. */
3401
+ }
3299
3402
  // eslint-disable-next-line no-console
3300
3403
  console.log(`Run ${arg} approved. Call \`specship workflow resume ${arg}\` to continue.`);
3301
3404
  break;
@@ -3590,6 +3693,52 @@ function main() {
3590
3693
  process.exit(1);
3591
3694
  }
3592
3695
  });
3696
+ /**
3697
+ * specship jira bind — persist the repo's JIRA binding into
3698
+ * specship.config.json (REQ-JIRATEAM-008.A2). The committed binding is what
3699
+ * board-first flows read — the same file `updateRepoJiraBinding` writes.
3700
+ * Credentials never live here; `assertNoCredentialsInRepoConfig` enforces it.
3701
+ */
3702
+ jira
3703
+ .command('bind')
3704
+ .description('Write the repo JIRA binding into specship.config.json (project, epic).')
3705
+ .option('--project <key>', 'JIRA project key to bind (e.g., "PROJ")')
3706
+ .option('--epic <key>', 'Default epic key to anchor new work (e.g., "PROJ-42")')
3707
+ .option('--path <projectRoot>', 'Project root (default: cwd)')
3708
+ .action(async (options) => {
3709
+ const projectRoot = path.resolve(options.path ?? process.cwd());
3710
+ if (!options.project && !options.epic) {
3711
+ error('Nothing to bind. Pass --project <KEY> and/or --epic <KEY>.');
3712
+ process.exit(1);
3713
+ }
3714
+ try {
3715
+ const { updateRepoJiraBinding, loadRepoJiraBinding } = await Promise.resolve().then(() => __importStar(require('../jira/repo-config')));
3716
+ const patch = {};
3717
+ if (options.project)
3718
+ patch.projectKey = options.project.trim().toUpperCase();
3719
+ if (options.epic)
3720
+ patch.epicKey = options.epic.trim().toUpperCase();
3721
+ // --epic alone requires an existing bound project (updateRepoJiraBinding
3722
+ // insists on projectKey being present in the merged result).
3723
+ if (!patch.projectKey) {
3724
+ const { binding } = loadRepoJiraBinding(projectRoot);
3725
+ if (!binding) {
3726
+ error('No existing JIRA project binding. Pass --project <KEY> on the first bind.');
3727
+ process.exit(1);
3728
+ }
3729
+ }
3730
+ const { path: file, binding } = updateRepoJiraBinding(projectRoot, patch);
3731
+ success(`Wrote JIRA binding to ${file}.`);
3732
+ info(` projectKey: ${binding.projectKey}`);
3733
+ if (binding.epicKey)
3734
+ info(` epicKey: ${binding.epicKey}`);
3735
+ }
3736
+ catch (err) {
3737
+ const msg = err instanceof Error ? err.message : String(err);
3738
+ error(`Failed to write binding: ${msg}`);
3739
+ process.exit(1);
3740
+ }
3741
+ });
3593
3742
  /**
3594
3743
  * specship jira transition — move a tracked issue to a target state, or list
3595
3744
  * the transitions it currently offers (REQ-JIRATRANS-001). Reuses the client's
@@ -3656,6 +3805,103 @@ function main() {
3656
3805
  cg.close();
3657
3806
  }
3658
3807
  });
3808
+ /**
3809
+ * specship jira coverage — sprint coverage report (REQ-JIRATEAM-004). Joins
3810
+ * the bound project's active (or named) sprint to spec truth. Read-only by
3811
+ * default; `--post <issueKey>` upserts a single watermarked JIRA comment.
3812
+ */
3813
+ jira
3814
+ .command('coverage')
3815
+ .description('Sprint coverage report joining JIRA sprint issues to spec truth (read-only unless --post).')
3816
+ .option('--project <key>', 'JIRA project key (default: configured publish project)')
3817
+ .option('--sprint <name>', 'named sprint (default: the project\'s open sprint)')
3818
+ .option('--post <issueKey>', 'upsert the report as a watermarked comment on this issue (edit-in-place)')
3819
+ .option('--path <projectRoot>', 'project root (default: cwd)')
3820
+ .action(async (options) => {
3821
+ const projectRoot = path.resolve(options.path ?? process.cwd());
3822
+ if (!(0, directory_1.isInitialized)(projectRoot)) {
3823
+ error(`SpecShip not initialized in ${projectRoot}. Run \`specship init -i\` first.`);
3824
+ process.exit(1);
3825
+ }
3826
+ const { SpecShip } = await loadSpecShip();
3827
+ const { handleSpecshipJiraCoverage } = await Promise.resolve().then(() => __importStar(require('../mcp/jira-tools')));
3828
+ const cg = await SpecShip.open(projectRoot);
3829
+ try {
3830
+ const args = {};
3831
+ if (options.project)
3832
+ args.project = options.project;
3833
+ if (options.sprint)
3834
+ args.sprint = options.sprint;
3835
+ if (options.post) {
3836
+ args.post = true;
3837
+ args.issue_key = options.post;
3838
+ }
3839
+ const result = await handleSpecshipJiraCoverage(args, {
3840
+ specQueries: cg.getSpecQueries(),
3841
+ projectRoot,
3842
+ });
3843
+ const out = result.content.map((c) => c.text).join('\n');
3844
+ // eslint-disable-next-line no-console
3845
+ console.log(out);
3846
+ if (result.isError)
3847
+ process.exit(1);
3848
+ }
3849
+ finally {
3850
+ cg.close();
3851
+ }
3852
+ });
3853
+ /**
3854
+ * specship jira reconcile — detect JIRA-side edits and (with --apply) fold
3855
+ * them back into the spec (REQ-JIRATEAM-005). Preview is read-only and the
3856
+ * default; --apply must be paired with --issue plus the confirmed accept
3857
+ * flags AND the fingerprint from the preview so an unreviewed apply is
3858
+ * rejected by the handler's fingerprint gate.
3859
+ */
3860
+ jira
3861
+ .command('reconcile')
3862
+ .description('Detect JIRA-side edits to published specs; --apply folds a confirmed diff back into the spec.')
3863
+ .option('--issue <key>', 'narrow to one issue (required for --apply)')
3864
+ .option('--apply', 'apply the confirmed diff — preview first and confirm the diff before using this')
3865
+ .option('--accept-content', 'apply: accept the edited summary/description')
3866
+ .option('--accept-subtask <key...>', 'apply: accept the proposed criterion for this JIRA Sub-task key (repeatable)')
3867
+ .option('--expected-fingerprint <fp>', 'apply: the live fingerprint from the preview the user just approved')
3868
+ .option('--path <projectRoot>', 'project root (default: cwd)')
3869
+ .action(async (options) => {
3870
+ const projectRoot = path.resolve(options.path ?? process.cwd());
3871
+ if (!(0, directory_1.isInitialized)(projectRoot)) {
3872
+ error(`SpecShip not initialized in ${projectRoot}. Run \`specship init -i\` first.`);
3873
+ process.exit(1);
3874
+ }
3875
+ const { SpecShip } = await loadSpecShip();
3876
+ const { handleSpecshipJiraReconcile } = await Promise.resolve().then(() => __importStar(require('../mcp/jira-tools')));
3877
+ const cg = await SpecShip.open(projectRoot);
3878
+ try {
3879
+ const args = {};
3880
+ args.mode = options.apply ? 'apply' : 'preview';
3881
+ if (options.issue)
3882
+ args.issue_key = options.issue;
3883
+ if (options.acceptContent)
3884
+ args.accept_content = true;
3885
+ if (options.acceptSubtask && options.acceptSubtask.length > 0) {
3886
+ args.accept_subtasks = options.acceptSubtask;
3887
+ }
3888
+ if (options.expectedFingerprint) {
3889
+ args.expected_live_fingerprint = options.expectedFingerprint;
3890
+ }
3891
+ const result = await handleSpecshipJiraReconcile(args, {
3892
+ specQueries: cg.getSpecQueries(),
3893
+ projectRoot,
3894
+ });
3895
+ const out = result.content.map((c) => c.text).join('\n');
3896
+ // eslint-disable-next-line no-console
3897
+ console.log(out);
3898
+ if (result.isError)
3899
+ process.exit(1);
3900
+ }
3901
+ finally {
3902
+ cg.close();
3903
+ }
3904
+ });
3659
3905
  /**
3660
3906
  * specship jira release — stamp a released version onto issues
3661
3907
  * (REQ-JIRAPUB-007): ensure the project version exists, add it as fixVersion
@@ -3683,6 +3929,9 @@ function main() {
3683
3929
  .split(',')
3684
3930
  .map((k) => k.trim())
3685
3931
  .filter(Boolean);
3932
+ // Map issue key → spec id for the release_stamped milestone (so the
3933
+ // marker keys on both the spec and the version — REQ-JIRATEAM-003).
3934
+ const issueKeyToSpecId = new Map();
3686
3935
  if (keys.length === 0) {
3687
3936
  // Default: every spec under <root>/specs with a jira_issue key.
3688
3937
  const projectRoot = path.resolve(options.path ?? process.cwd());
@@ -3691,9 +3940,14 @@ function main() {
3691
3940
  for (const name of fs.readdirSync(specsDir)) {
3692
3941
  if (!name.toLowerCase().endsWith('.md'))
3693
3942
  continue;
3694
- const key = readSpecJiraKey(path.join(specsDir, name));
3695
- if (key)
3696
- keys.push(key);
3943
+ const abs = path.join(specsDir, name);
3944
+ const key = readSpecJiraKey(abs);
3945
+ if (!key)
3946
+ continue;
3947
+ keys.push(key);
3948
+ const idLine = /^id:\s*([^\r\n]+)$/m.exec(fs.readFileSync(abs, 'utf8'))?.[1]?.trim();
3949
+ if (idLine)
3950
+ issueKeyToSpecId.set(key, idLine);
3697
3951
  }
3698
3952
  }
3699
3953
  catch {
@@ -3706,7 +3960,9 @@ function main() {
3706
3960
  process.exit(1);
3707
3961
  }
3708
3962
  const client = new JiraClient(creds);
3709
- const result = await releaseIssues(client, projectKey, version, keys);
3963
+ const result = await releaseIssues(client, projectKey, version, keys, {
3964
+ specIdForIssue: (k) => issueKeyToSpecId.get(k),
3965
+ });
3710
3966
  const stamped = result.issues.filter((i) => i.fixVersionAdded).length;
3711
3967
  const commented = result.issues.filter((i) => i.commented).length;
3712
3968
  success(`Version ${version}${result.versionCreated ? ' created' : ' already existed'} in ${projectKey}; ` +
@@ -3717,6 +3973,111 @@ function main() {
3717
3973
  process.exit(1);
3718
3974
  }
3719
3975
  });
3976
+ /**
3977
+ * specship jira regression-pack — generate or refresh the SpecShip Regression
3978
+ * Pack in the bound JIRA project (REQ-JIRAREG-001). Idempotent; --dry-run
3979
+ * prints the plan without any JIRA write.
3980
+ */
3981
+ jira
3982
+ .command('regression-pack')
3983
+ .description('Generate / refresh the SpecShip Regression Pack in the bound JIRA project.')
3984
+ .option('--project <key>', 'JIRA project key (default: configured publish project)')
3985
+ .option('--dry-run', 'plan the pack and print counts without any JIRA write')
3986
+ .option('--path <projectRoot>', 'project root (default: cwd)')
3987
+ .action(async (options) => {
3988
+ const projectRoot = path.resolve(options.path ?? process.cwd());
3989
+ if (!(0, directory_1.isInitialized)(projectRoot)) {
3990
+ error(`SpecShip not initialized in ${projectRoot}. Run \`specship init -i\` first.`);
3991
+ process.exit(1);
3992
+ }
3993
+ const { SpecShip } = await loadSpecShip();
3994
+ const { handleSpecshipJiraRegressionPack } = await Promise.resolve().then(() => __importStar(require('../mcp/jira-tools')));
3995
+ const cg = await SpecShip.open(projectRoot);
3996
+ try {
3997
+ const args = {};
3998
+ if (options.project)
3999
+ args.project = options.project;
4000
+ if (options.dryRun)
4001
+ args.dry_run = true;
4002
+ const result = await handleSpecshipJiraRegressionPack(args, {
4003
+ specQueries: cg.getSpecQueries(),
4004
+ projectRoot,
4005
+ getLinkedNodesForReq: (reqId) => cg.getLinkedNodesForReq(reqId),
4006
+ getNeighbourNodes: (nodeIds) => cg.getNeighbourNodes(nodeIds),
4007
+ });
4008
+ const out = result.content.map((c) => c.text).join('\n');
4009
+ // eslint-disable-next-line no-console
4010
+ console.log(out);
4011
+ if (result.isError)
4012
+ process.exit(1);
4013
+ }
4014
+ finally {
4015
+ cg.close();
4016
+ }
4017
+ });
4018
+ /**
4019
+ * specship jira regression-record — record a single pack case result
4020
+ * (REQ-JIRAREG-005). Comment + transition + validates-link feedback on the
4021
+ * source criterion. `--run <id>` keys idempotency per (case, run).
4022
+ */
4023
+ jira
4024
+ .command('regression-record')
4025
+ .description('Record a regression pack case result (REQ-JIRAREG-005).')
4026
+ .requiredOption('--case <key>', 'JIRA case issue key (e.g., "PROJ-42")')
4027
+ .requiredOption('--criterion <id>', 'source criterion id (e.g., "REQ-FOO-001.A2")')
4028
+ .requiredOption('--result <status>', 'pass | fail | unexecuted', 'pass')
4029
+ .option('--executor <who>', 'human | agent', 'human')
4030
+ .option('--harness <id>', 'behaviour harness id (e.g., "behaviour-e2e")')
4031
+ .option('--run <id>', 'stable run id — one comment per (case, run)')
4032
+ .option('--log-ref <ref>', 'evidence log pointer')
4033
+ .option('--error <text>', 'error text (fail path)')
4034
+ .option('--path <projectRoot>', 'project root (default: cwd)')
4035
+ .action(async (options) => {
4036
+ const projectRoot = path.resolve(options.path ?? process.cwd());
4037
+ if (!(0, directory_1.isInitialized)(projectRoot)) {
4038
+ error(`SpecShip not initialized in ${projectRoot}. Run \`specship init -i\` first.`);
4039
+ process.exit(1);
4040
+ }
4041
+ const { SpecShip } = await loadSpecShip();
4042
+ const { handleSpecshipJiraRegressionRecord } = await Promise.resolve().then(() => __importStar(require('../mcp/jira-tools')));
4043
+ const cg = await SpecShip.open(projectRoot);
4044
+ try {
4045
+ // Accept the friendlier `pass`/`fail` short forms alongside the
4046
+ // canonical `passed`/`failed` names.
4047
+ const canonicalStatus = options.result === 'pass'
4048
+ ? 'passed'
4049
+ : options.result === 'fail'
4050
+ ? 'failed'
4051
+ : options.result;
4052
+ const args = {
4053
+ case: options.case,
4054
+ criterion: options.criterion,
4055
+ result: canonicalStatus,
4056
+ };
4057
+ if (options.executor)
4058
+ args.executor = options.executor;
4059
+ if (options.harness)
4060
+ args.harness = options.harness;
4061
+ if (options.run)
4062
+ args.run = options.run;
4063
+ if (options.logRef)
4064
+ args.log_ref = options.logRef;
4065
+ if (options.error)
4066
+ args.error = options.error;
4067
+ const result = await handleSpecshipJiraRegressionRecord(args, {
4068
+ specQueries: cg.getSpecQueries(),
4069
+ projectRoot,
4070
+ });
4071
+ const out = result.content.map((c) => c.text).join('\n');
4072
+ // eslint-disable-next-line no-console
4073
+ console.log(out);
4074
+ if (result.isError)
4075
+ process.exit(1);
4076
+ }
4077
+ finally {
4078
+ cg.close();
4079
+ }
4080
+ });
3720
4081
  // Parse and run
3721
4082
  program.parse();
3722
4083
  } // end main()