@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.
- package/lib/dist/activation/search-intercept.d.ts +59 -0
- package/lib/dist/activation/search-intercept.d.ts.map +1 -0
- package/lib/dist/activation/search-intercept.js +195 -0
- package/lib/dist/activation/search-intercept.js.map +1 -0
- package/lib/dist/bin/specship.js +372 -11
- package/lib/dist/bin/specship.js.map +1 -1
- package/lib/dist/bin/uninstall.d.ts +4 -3
- package/lib/dist/bin/uninstall.d.ts.map +1 -1
- package/lib/dist/bin/uninstall.js +4 -3
- package/lib/dist/bin/uninstall.js.map +1 -1
- package/lib/dist/commands/specship/jira.md +74 -0
- package/lib/dist/commands/specship/spec.md +61 -1
- package/lib/dist/extraction/index.d.ts +6 -0
- package/lib/dist/extraction/index.d.ts.map +1 -1
- package/lib/dist/extraction/index.js.map +1 -1
- package/lib/dist/extraction/specs/markdown-spec-extractor.d.ts.map +1 -1
- package/lib/dist/extraction/specs/markdown-spec-extractor.js +8 -1
- package/lib/dist/extraction/specs/markdown-spec-extractor.js.map +1 -1
- package/lib/dist/index.d.ts +7 -0
- package/lib/dist/index.d.ts.map +1 -1
- package/lib/dist/index.js +45 -18
- package/lib/dist/index.js.map +1 -1
- package/lib/dist/installer/index.d.ts +20 -6
- package/lib/dist/installer/index.d.ts.map +1 -1
- package/lib/dist/installer/index.js +123 -63
- package/lib/dist/installer/index.js.map +1 -1
- package/lib/dist/installer/targets/claude.d.ts +9 -0
- package/lib/dist/installer/targets/claude.d.ts.map +1 -1
- package/lib/dist/installer/targets/claude.js +46 -0
- package/lib/dist/installer/targets/claude.js.map +1 -1
- package/lib/dist/installer/targets/gemini.d.ts +78 -0
- package/lib/dist/installer/targets/gemini.d.ts.map +1 -0
- package/lib/dist/installer/targets/gemini.js +200 -0
- package/lib/dist/installer/targets/gemini.js.map +1 -0
- package/lib/dist/installer/targets/registry.d.ts +5 -2
- package/lib/dist/installer/targets/registry.d.ts.map +1 -1
- package/lib/dist/installer/targets/registry.js +7 -3
- package/lib/dist/installer/targets/registry.js.map +1 -1
- package/lib/dist/installer/targets/types.d.ts +6 -2
- package/lib/dist/installer/targets/types.d.ts.map +1 -1
- package/lib/dist/jira/auto-publish.d.ts +67 -0
- package/lib/dist/jira/auto-publish.d.ts.map +1 -0
- package/lib/dist/jira/auto-publish.js +256 -0
- package/lib/dist/jira/auto-publish.js.map +1 -0
- package/lib/dist/jira/board-first.d.ts +64 -0
- package/lib/dist/jira/board-first.d.ts.map +1 -0
- package/lib/dist/jira/board-first.js +91 -0
- package/lib/dist/jira/board-first.js.map +1 -0
- package/lib/dist/jira/client.d.ts +87 -1
- package/lib/dist/jira/client.d.ts.map +1 -1
- package/lib/dist/jira/client.js +249 -2
- package/lib/dist/jira/client.js.map +1 -1
- package/lib/dist/jira/config.d.ts +1 -1
- package/lib/dist/jira/config.d.ts.map +1 -1
- package/lib/dist/jira/config.js +14 -3
- package/lib/dist/jira/config.js.map +1 -1
- package/lib/dist/jira/coverage.d.ts +105 -0
- package/lib/dist/jira/coverage.d.ts.map +1 -0
- package/lib/dist/jira/coverage.js +170 -0
- package/lib/dist/jira/coverage.js.map +1 -0
- package/lib/dist/jira/index.d.ts +1 -0
- package/lib/dist/jira/index.d.ts.map +1 -1
- package/lib/dist/jira/index.js +1 -0
- package/lib/dist/jira/index.js.map +1 -1
- package/lib/dist/jira/milestone-comment.d.ts +95 -0
- package/lib/dist/jira/milestone-comment.d.ts.map +1 -0
- package/lib/dist/jira/milestone-comment.js +271 -0
- package/lib/dist/jira/milestone-comment.js.map +1 -0
- package/lib/dist/jira/publish.d.ts +86 -2
- package/lib/dist/jira/publish.d.ts.map +1 -1
- package/lib/dist/jira/publish.js +0 -0
- package/lib/dist/jira/publish.js.map +1 -1
- package/lib/dist/jira/published-specs.d.ts +29 -0
- package/lib/dist/jira/published-specs.d.ts.map +1 -0
- package/lib/dist/jira/published-specs.js +147 -0
- package/lib/dist/jira/published-specs.js.map +1 -0
- package/lib/dist/jira/reconcile.d.ts +81 -0
- package/lib/dist/jira/reconcile.d.ts.map +1 -0
- package/lib/dist/jira/reconcile.js +111 -0
- package/lib/dist/jira/reconcile.js.map +1 -0
- package/lib/dist/jira/regression-pack.d.ts +547 -0
- package/lib/dist/jira/regression-pack.d.ts.map +1 -0
- package/lib/dist/jira/regression-pack.js +986 -0
- package/lib/dist/jira/regression-pack.js.map +1 -0
- package/lib/dist/jira/repo-config.d.ts +73 -0
- package/lib/dist/jira/repo-config.d.ts.map +1 -0
- package/lib/dist/jira/repo-config.js +249 -0
- package/lib/dist/jira/repo-config.js.map +1 -0
- package/lib/dist/jira/spec-amend.d.ts +46 -0
- package/lib/dist/jira/spec-amend.d.ts.map +1 -0
- package/lib/dist/jira/spec-amend.js +232 -0
- package/lib/dist/jira/spec-amend.js.map +1 -0
- package/lib/dist/jira/spec-writer.d.ts +22 -0
- package/lib/dist/jira/spec-writer.d.ts.map +1 -1
- package/lib/dist/jira/spec-writer.js +113 -0
- package/lib/dist/jira/spec-writer.js.map +1 -1
- package/lib/dist/jira/types.d.ts +62 -0
- package/lib/dist/jira/types.d.ts.map +1 -1
- package/lib/dist/jira/types.js +15 -1
- package/lib/dist/jira/types.js.map +1 -1
- package/lib/dist/mcp/jira-tools.d.ts +102 -0
- package/lib/dist/mcp/jira-tools.d.ts.map +1 -1
- package/lib/dist/mcp/jira-tools.js +826 -53
- package/lib/dist/mcp/jira-tools.js.map +1 -1
- package/lib/dist/mcp/server-instructions.d.ts +1 -1
- package/lib/dist/mcp/server-instructions.d.ts.map +1 -1
- package/lib/dist/mcp/server-instructions.js +4 -0
- package/lib/dist/mcp/server-instructions.js.map +1 -1
- package/lib/dist/mcp/spec-tools.d.ts.map +1 -1
- package/lib/dist/mcp/spec-tools.js +7 -1
- package/lib/dist/mcp/spec-tools.js.map +1 -1
- package/lib/dist/mcp/tools.d.ts +53 -0
- package/lib/dist/mcp/tools.d.ts.map +1 -1
- package/lib/dist/mcp/tools.js +255 -8
- package/lib/dist/mcp/tools.js.map +1 -1
- package/lib/dist/resolution/spec-link-resolver.d.ts +2 -0
- package/lib/dist/resolution/spec-link-resolver.d.ts.map +1 -1
- package/lib/dist/resolution/spec-link-resolver.js +11 -3
- package/lib/dist/resolution/spec-link-resolver.js.map +1 -1
- package/lib/node_modules/.package-lock.json +1 -1
- package/lib/package.json +1 -1
- package/package.json +1 -1
package/lib/dist/bin/specship.js
CHANGED
|
@@ -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
|
|
2449
|
-
// -t/--target
|
|
2450
|
-
//
|
|
2451
|
-
|
|
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
|
|
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(
|
|
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
|
|
3695
|
-
|
|
3696
|
-
|
|
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()
|