@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
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Registry of supported agent targets.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
4
|
+
* Claude Code plus the one ratified exception, Gemini CLI (GEMINI-TARGET-DOC).
|
|
5
|
+
* Registration does NOT make a target install by default — `specship install`
|
|
6
|
+
* writes Claude alone unless `--target` names another (REQ-GEMINI-002.A4).
|
|
7
|
+
* Being here means the target participates in the contract suite, in
|
|
8
|
+
* `detectAll`, and in the uninstall sweep.
|
|
6
9
|
*/
|
|
7
10
|
import { AgentTarget, Location, TargetId } from './types';
|
|
8
11
|
export declare const ALL_TARGETS: readonly AgentTarget[];
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"registry.d.ts","sourceRoot":"","sources":["../../../src/installer/targets/registry.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"registry.d.ts","sourceRoot":"","sources":["../../../src/installer/targets/registry.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,EAAE,WAAW,EAAE,QAAQ,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AAI1D,eAAO,MAAM,WAAW,EAAE,SAAS,WAAW,EAAgD,CAAC;AAE/F,wBAAgB,SAAS,CAAC,EAAE,EAAE,MAAM,GAAG,WAAW,GAAG,SAAS,CAE7D;AAED,wBAAgB,aAAa,IAAI,QAAQ,EAAE,CAE1C;AAED;;;GAGG;AACH,wBAAgB,SAAS,CAAC,GAAG,EAAE,QAAQ,GAAG,KAAK,CAAC;IAC9C,MAAM,EAAE,WAAW,CAAC;IACpB,SAAS,EAAE,UAAU,CAAC,WAAW,CAAC,QAAQ,CAAC,CAAC,CAAC;CAC9C,CAAC,CAKD"}
|
|
@@ -2,8 +2,11 @@
|
|
|
2
2
|
/**
|
|
3
3
|
* Registry of supported agent targets.
|
|
4
4
|
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
5
|
+
* Claude Code plus the one ratified exception, Gemini CLI (GEMINI-TARGET-DOC).
|
|
6
|
+
* Registration does NOT make a target install by default — `specship install`
|
|
7
|
+
* writes Claude alone unless `--target` names another (REQ-GEMINI-002.A4).
|
|
8
|
+
* Being here means the target participates in the contract suite, in
|
|
9
|
+
* `detectAll`, and in the uninstall sweep.
|
|
7
10
|
*/
|
|
8
11
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
9
12
|
exports.ALL_TARGETS = void 0;
|
|
@@ -11,7 +14,8 @@ exports.getTarget = getTarget;
|
|
|
11
14
|
exports.listTargetIds = listTargetIds;
|
|
12
15
|
exports.detectAll = detectAll;
|
|
13
16
|
const claude_1 = require("./claude");
|
|
14
|
-
|
|
17
|
+
const gemini_1 = require("./gemini");
|
|
18
|
+
exports.ALL_TARGETS = Object.freeze([claude_1.claudeTarget, gemini_1.geminiTarget]);
|
|
15
19
|
function getTarget(id) {
|
|
16
20
|
return exports.ALL_TARGETS.find((t) => t.id === id);
|
|
17
21
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"registry.js","sourceRoot":"","sources":["../../../src/installer/targets/registry.ts"],"names":[],"mappings":";AAAA
|
|
1
|
+
{"version":3,"file":"registry.js","sourceRoot":"","sources":["../../../src/installer/targets/registry.ts"],"names":[],"mappings":";AAAA;;;;;;;;GAQG;;;AAQH,8BAEC;AAED,sCAEC;AAMD,8BAQC;AAzBD,qCAAwC;AACxC,qCAAwC;AAE3B,QAAA,WAAW,GAA2B,MAAM,CAAC,MAAM,CAAC,CAAC,qBAAY,EAAE,qBAAY,CAAC,CAAC,CAAC;AAE/F,SAAgB,SAAS,CAAC,EAAU;IAClC,OAAO,mBAAW,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,CAAC;AAC9C,CAAC;AAED,SAAgB,aAAa;IAC3B,OAAO,mBAAW,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;AACtC,CAAC;AAED;;;GAGG;AACH,SAAgB,SAAS,CAAC,GAAa;IAIrC,OAAO,mBAAW,CAAC,GAAG,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC;QAClC,MAAM;QACN,SAAS,EAAE,MAAM,CAAC,MAAM,CAAC,GAAG,CAAC;KAC9B,CAAC,CAAC,CAAC;AACN,CAAC"}
|
|
@@ -8,8 +8,12 @@
|
|
|
8
8
|
* + one registry entry. See `targets/registry.ts`.
|
|
9
9
|
*/
|
|
10
10
|
export type Location = 'global' | 'local';
|
|
11
|
-
/**
|
|
12
|
-
|
|
11
|
+
/**
|
|
12
|
+
* Stable string ids for supported targets. `gemini` (GEMINI-TARGET-DOC) is
|
|
13
|
+
* declared here from Phase 0 on, but only its `printConfig` is implemented
|
|
14
|
+
* and it is not in `ALL_TARGETS` yet — see `targets/gemini.ts`.
|
|
15
|
+
*/
|
|
16
|
+
export type TargetId = 'claude' | 'gemini';
|
|
13
17
|
/**
|
|
14
18
|
* Result of `target.detect(location)`.
|
|
15
19
|
*
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/installer/targets/types.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,MAAM,MAAM,QAAQ,GAAG,QAAQ,GAAG,OAAO,CAAC;AAE1C
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/installer/targets/types.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,MAAM,MAAM,QAAQ,GAAG,QAAQ,GAAG,OAAO,CAAC;AAE1C;;;;GAIG;AACH,MAAM,MAAM,QAAQ,GAAG,QAAQ,GAAG,QAAQ,CAAC;AAE3C;;;;;;;GAOG;AACH,MAAM,WAAW,eAAe;IAC9B,SAAS,EAAE,OAAO,CAAC;IACnB,iBAAiB,EAAE,OAAO,CAAC;IAC3B,+DAA+D;IAC/D,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED;;;;;;GAMG;AACH,MAAM,WAAW,WAAW;IAC1B,KAAK,EAAE,KAAK,CAAC;QACX,IAAI,EAAE,MAAM,CAAC;QACb,MAAM,EAAE,SAAS,GAAG,SAAS,GAAG,WAAW,GAAG,SAAS,GAAG,WAAW,GAAG,MAAM,CAAC;KAChF,CAAC,CAAC;IACH,kEAAkE;IAClE,KAAK,CAAC,EAAE,MAAM,EAAE,CAAC;CAClB;AAED,MAAM,WAAW,cAAc;IAC7B;;;OAGG;IACH,SAAS,EAAE,OAAO,CAAC;IACnB;;;;;;;;;OASG;IACH,GAAG,CAAC,EAAE,OAAO,CAAC;IACd;;;;;;;OAOG;IACH,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB;;;;;;OAMG;IACH,iBAAiB,CAAC,EAAE,OAAO,CAAC;CAC7B;AAED,MAAM,WAAW,WAAW;IAC1B,+CAA+C;IAC/C,QAAQ,CAAC,EAAE,EAAE,QAAQ,CAAC;IACtB,0DAA0D;IAC1D,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,iEAAiE;IACjE,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC;IAC1B,+DAA+D;IAC/D,gBAAgB,CAAC,GAAG,EAAE,QAAQ,GAAG,OAAO,CAAC;IACzC,MAAM,CAAC,GAAG,EAAE,QAAQ,GAAG,eAAe,CAAC;IACvC,OAAO,CAAC,GAAG,EAAE,QAAQ,EAAE,IAAI,EAAE,cAAc,GAAG,WAAW,CAAC;IAC1D;;;;;OAKG;IACH,SAAS,CAAC,GAAG,EAAE,QAAQ,GAAG,WAAW,CAAC;IACtC;;;;OAIG;IACH,WAAW,CAAC,GAAG,EAAE,QAAQ,GAAG,MAAM,CAAC;IACnC,oEAAoE;IACpE,aAAa,CAAC,GAAG,EAAE,QAAQ,GAAG,MAAM,EAAE,CAAC;CACxC"}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Auto-publish specs to JIRA on sync (REQ-JIRATEAM-002).
|
|
3
|
+
*
|
|
4
|
+
* With a repo-committed JIRA project binding in `specship.config.json`
|
|
5
|
+
* (REQ-JIRATEAM-001), every cleanly-indexed requirement spec is
|
|
6
|
+
* published/refreshed to JIRA during `sync` — reusing the existing
|
|
7
|
+
* idempotent `publishSpecToJira` primitive so the fingerprint short-circuits
|
|
8
|
+
* unchanged specs, missing Sub-tasks are added, and `writeBackJiraIdentity`
|
|
9
|
+
* stamps the `jira_issue:` back into the file.
|
|
10
|
+
*
|
|
11
|
+
* A5 gate: with NO binding, this returns immediately without constructing a
|
|
12
|
+
* JIRA client or reading credentials — zero JIRA calls. Even with a binding,
|
|
13
|
+
* a repo whose user has no credentials configured degrades to a single
|
|
14
|
+
* per-sync note (never crashes, never blocks sync). Per-spec publish failures
|
|
15
|
+
* are collected as `failed` results; siblings still publish.
|
|
16
|
+
*/
|
|
17
|
+
import { resolveJiraCredentials } from './config';
|
|
18
|
+
import { type PublishJiraClient } from './publish';
|
|
19
|
+
export type AutoPublishStatus = 'published' | 'unchanged' | 'opted_out' | 'failed';
|
|
20
|
+
export interface AutoPublishResultRow {
|
|
21
|
+
specId: string;
|
|
22
|
+
status: AutoPublishStatus;
|
|
23
|
+
jiraKey?: string;
|
|
24
|
+
subtasksCreated?: number;
|
|
25
|
+
note?: string;
|
|
26
|
+
}
|
|
27
|
+
export interface AutoPublishReport {
|
|
28
|
+
/**
|
|
29
|
+
* Present when the pass short-circuited before touching JIRA:
|
|
30
|
+
* - 'unbound' → no repo binding (A5)
|
|
31
|
+
* - 'no-credentials' → binding present but user credentials missing (A4-like)
|
|
32
|
+
*/
|
|
33
|
+
skipped?: 'unbound' | 'no-credentials';
|
|
34
|
+
/** Human-readable note when `skipped` is set (surfaced by the caller). */
|
|
35
|
+
note?: string;
|
|
36
|
+
results: AutoPublishResultRow[];
|
|
37
|
+
}
|
|
38
|
+
/** The structural slice of SpecQueries this pass reads. */
|
|
39
|
+
export interface AutoPublishSpecQueries {
|
|
40
|
+
getAllSpecs(): Array<{
|
|
41
|
+
id: string;
|
|
42
|
+
kind: string;
|
|
43
|
+
title: string;
|
|
44
|
+
body: string;
|
|
45
|
+
sourcePath: string;
|
|
46
|
+
}>;
|
|
47
|
+
getSpecsByParent(id: string): Array<{
|
|
48
|
+
id: string;
|
|
49
|
+
kind: string;
|
|
50
|
+
title: string;
|
|
51
|
+
body: string;
|
|
52
|
+
}>;
|
|
53
|
+
}
|
|
54
|
+
export interface AutoPublishDeps {
|
|
55
|
+
specQueries: AutoPublishSpecQueries;
|
|
56
|
+
projectRoot: string;
|
|
57
|
+
/** Test seam: build a JIRA client from resolved creds. Default: JiraClient. */
|
|
58
|
+
makeClient?: (creds: ReturnType<typeof resolveJiraCredentials>) => PublishJiraClient;
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Publish/refresh every requirement spec to JIRA when a repo binding is
|
|
62
|
+
* present. Never throws — every failure lands in the returned report.
|
|
63
|
+
*/
|
|
64
|
+
export declare function autoPublishSpecsOnSync(deps: AutoPublishDeps): Promise<AutoPublishReport>;
|
|
65
|
+
/** One-line summary suitable for CLI output. */
|
|
66
|
+
export declare function summarizeAutoPublishReport(report: AutoPublishReport): string;
|
|
67
|
+
//# sourceMappingURL=auto-publish.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"auto-publish.d.ts","sourceRoot":"","sources":["../../src/jira/auto-publish.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAGH,OAAO,EAAE,sBAAsB,EAAE,MAAM,UAAU,CAAC;AAGlD,OAAO,EAOL,KAAK,iBAAiB,EAEvB,MAAM,WAAW,CAAC;AAKnB,MAAM,MAAM,iBAAiB,GAAG,WAAW,GAAG,WAAW,GAAG,WAAW,GAAG,QAAQ,CAAC;AAEnF,MAAM,WAAW,oBAAoB;IACnC,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,iBAAiB,CAAC;IAC1B,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,iBAAiB;IAChC;;;;OAIG;IACH,OAAO,CAAC,EAAE,SAAS,GAAG,gBAAgB,CAAC;IACvC,0EAA0E;IAC1E,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,oBAAoB,EAAE,CAAC;CACjC;AAED,2DAA2D;AAC3D,MAAM,WAAW,sBAAsB;IACrC,WAAW,IAAI,KAAK,CAAC;QACnB,EAAE,EAAE,MAAM,CAAC;QACX,IAAI,EAAE,MAAM,CAAC;QACb,KAAK,EAAE,MAAM,CAAC;QACd,IAAI,EAAE,MAAM,CAAC;QACb,UAAU,EAAE,MAAM,CAAC;KACpB,CAAC,CAAC;IACH,gBAAgB,CAAC,EAAE,EAAE,MAAM,GAAG,KAAK,CAAC;QAClC,EAAE,EAAE,MAAM,CAAC;QACX,IAAI,EAAE,MAAM,CAAC;QACb,KAAK,EAAE,MAAM,CAAC;QACd,IAAI,EAAE,MAAM,CAAC;KACd,CAAC,CAAC;CACJ;AAED,MAAM,WAAW,eAAe;IAC9B,WAAW,EAAE,sBAAsB,CAAC;IACpC,WAAW,EAAE,MAAM,CAAC;IACpB,+EAA+E;IAC/E,UAAU,CAAC,EAAE,CAAC,KAAK,EAAE,UAAU,CAAC,OAAO,sBAAsB,CAAC,KAAK,iBAAiB,CAAC;CACtF;AAED;;;GAGG;AACH,wBAAsB,sBAAsB,CAC1C,IAAI,EAAE,eAAe,GACpB,OAAO,CAAC,iBAAiB,CAAC,CA4K5B;AA8BD,gDAAgD;AAChD,wBAAgB,0BAA0B,CAAC,MAAM,EAAE,iBAAiB,GAAG,MAAM,CAmB5E"}
|
|
@@ -0,0 +1,256 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Auto-publish specs to JIRA on sync (REQ-JIRATEAM-002).
|
|
4
|
+
*
|
|
5
|
+
* With a repo-committed JIRA project binding in `specship.config.json`
|
|
6
|
+
* (REQ-JIRATEAM-001), every cleanly-indexed requirement spec is
|
|
7
|
+
* published/refreshed to JIRA during `sync` — reusing the existing
|
|
8
|
+
* idempotent `publishSpecToJira` primitive so the fingerprint short-circuits
|
|
9
|
+
* unchanged specs, missing Sub-tasks are added, and `writeBackJiraIdentity`
|
|
10
|
+
* stamps the `jira_issue:` back into the file.
|
|
11
|
+
*
|
|
12
|
+
* A5 gate: with NO binding, this returns immediately without constructing a
|
|
13
|
+
* JIRA client or reading credentials — zero JIRA calls. Even with a binding,
|
|
14
|
+
* a repo whose user has no credentials configured degrades to a single
|
|
15
|
+
* per-sync note (never crashes, never blocks sync). Per-spec publish failures
|
|
16
|
+
* are collected as `failed` results; siblings still publish.
|
|
17
|
+
*/
|
|
18
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
19
|
+
if (k2 === undefined) k2 = k;
|
|
20
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
21
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
22
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
23
|
+
}
|
|
24
|
+
Object.defineProperty(o, k2, desc);
|
|
25
|
+
}) : (function(o, m, k, k2) {
|
|
26
|
+
if (k2 === undefined) k2 = k;
|
|
27
|
+
o[k2] = m[k];
|
|
28
|
+
}));
|
|
29
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
30
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
31
|
+
}) : function(o, v) {
|
|
32
|
+
o["default"] = v;
|
|
33
|
+
});
|
|
34
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
35
|
+
var ownKeys = function(o) {
|
|
36
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
37
|
+
var ar = [];
|
|
38
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
39
|
+
return ar;
|
|
40
|
+
};
|
|
41
|
+
return ownKeys(o);
|
|
42
|
+
};
|
|
43
|
+
return function (mod) {
|
|
44
|
+
if (mod && mod.__esModule) return mod;
|
|
45
|
+
var result = {};
|
|
46
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
47
|
+
__setModuleDefault(result, mod);
|
|
48
|
+
return result;
|
|
49
|
+
};
|
|
50
|
+
})();
|
|
51
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
52
|
+
exports.autoPublishSpecsOnSync = autoPublishSpecsOnSync;
|
|
53
|
+
exports.summarizeAutoPublishReport = summarizeAutoPublishReport;
|
|
54
|
+
const path = __importStar(require("path"));
|
|
55
|
+
const config_1 = require("./config");
|
|
56
|
+
const repo_config_1 = require("./repo-config");
|
|
57
|
+
const client_1 = require("./client");
|
|
58
|
+
const publish_1 = require("./publish");
|
|
59
|
+
const spec_writer_1 = require("./spec-writer");
|
|
60
|
+
const milestone_comment_1 = require("./milestone-comment");
|
|
61
|
+
const fs = __importStar(require("fs"));
|
|
62
|
+
/**
|
|
63
|
+
* Publish/refresh every requirement spec to JIRA when a repo binding is
|
|
64
|
+
* present. Never throws — every failure lands in the returned report.
|
|
65
|
+
*/
|
|
66
|
+
async function autoPublishSpecsOnSync(deps) {
|
|
67
|
+
// A5: no repo binding → do NOTHING. No client construction, no credential
|
|
68
|
+
// read, no JIRA call.
|
|
69
|
+
const binding = (0, repo_config_1.loadRepoJiraBinding)(deps.projectRoot);
|
|
70
|
+
if (!binding.binding) {
|
|
71
|
+
return { skipped: 'unbound', results: [] };
|
|
72
|
+
}
|
|
73
|
+
// Bound repo without user credentials: one per-sync note, never a crash.
|
|
74
|
+
let creds;
|
|
75
|
+
try {
|
|
76
|
+
creds = (0, config_1.resolveJiraCredentials)(deps.projectRoot);
|
|
77
|
+
}
|
|
78
|
+
catch (err) {
|
|
79
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
80
|
+
return {
|
|
81
|
+
skipped: 'no-credentials',
|
|
82
|
+
note: msg,
|
|
83
|
+
results: [],
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
const projectKey = creds.project;
|
|
87
|
+
if (!projectKey) {
|
|
88
|
+
return {
|
|
89
|
+
skipped: 'no-credentials',
|
|
90
|
+
note: 'No JIRA project resolved from binding or user config.',
|
|
91
|
+
results: [],
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
const client = deps.makeClient
|
|
95
|
+
? deps.makeClient(creds)
|
|
96
|
+
: new client_1.JiraClient(creds);
|
|
97
|
+
const results = [];
|
|
98
|
+
const requirements = deps.specQueries.getAllSpecs().filter((s) => s.kind === 'requirement');
|
|
99
|
+
// Requirements per file (REQ-JIRATEAM-010): a file holding >1 requirement
|
|
100
|
+
// keys each one's JIRA identity per-requirement — the plain file-level
|
|
101
|
+
// `jira_issue:` is only trusted when the file has exactly one.
|
|
102
|
+
const reqsPerFile = new Map();
|
|
103
|
+
for (const s of requirements) {
|
|
104
|
+
reqsPerFile.set(s.sourcePath, (reqsPerFile.get(s.sourcePath) ?? 0) + 1);
|
|
105
|
+
}
|
|
106
|
+
for (const spec of requirements) {
|
|
107
|
+
const absPath = path.isAbsolute(spec.sourcePath)
|
|
108
|
+
? spec.sourcePath
|
|
109
|
+
: path.join(deps.projectRoot, spec.sourcePath);
|
|
110
|
+
// Per-spec frontmatter opt-out (A3). Note: readFrontmatterValue reads
|
|
111
|
+
// the file's leading frontmatter block only — a `jira_publish:` mention
|
|
112
|
+
// in the body cannot opt a spec out.
|
|
113
|
+
let fileContent;
|
|
114
|
+
try {
|
|
115
|
+
fileContent = fs.readFileSync(absPath, 'utf8');
|
|
116
|
+
}
|
|
117
|
+
catch (err) {
|
|
118
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
119
|
+
results.push({ specId: spec.id, status: 'failed', note: msg });
|
|
120
|
+
continue;
|
|
121
|
+
}
|
|
122
|
+
const optOut = (0, publish_1.readFrontmatterValue)(fileContent, 'jira_publish');
|
|
123
|
+
if (optOut !== null && optOut.toLowerCase() === 'false') {
|
|
124
|
+
results.push({ specId: spec.id, status: 'opted_out' });
|
|
125
|
+
continue;
|
|
126
|
+
}
|
|
127
|
+
const acceptance = deps.specQueries
|
|
128
|
+
.getSpecsByParent(spec.id)
|
|
129
|
+
.filter((c) => c.kind === 'acceptance')
|
|
130
|
+
.map((c) => ({ id: c.id, text: (c.title || c.body || '').trim() }))
|
|
131
|
+
.filter((c) => c.text.length > 0);
|
|
132
|
+
const source = {
|
|
133
|
+
specId: spec.id,
|
|
134
|
+
title: spec.title,
|
|
135
|
+
body: spec.body ?? '',
|
|
136
|
+
specRelPath: spec.sourcePath,
|
|
137
|
+
acceptance,
|
|
138
|
+
};
|
|
139
|
+
// REQ-JIRATEAM-010.A4: never resolve another requirement's issue. The
|
|
140
|
+
// per-requirement key wins; the plain file-level key applies only when
|
|
141
|
+
// this requirement is the file's sole one.
|
|
142
|
+
const soleRequirement = (reqsPerFile.get(spec.sourcePath) ?? 1) === 1;
|
|
143
|
+
const perKeys = (0, publish_1.perSpecFrontmatterKeys)(spec.id);
|
|
144
|
+
const existingKey = (0, publish_1.readFrontmatterValue)(fileContent, perKeys.issueKey) ??
|
|
145
|
+
(soleRequirement ? (0, spec_writer_1.readSpecJiraKey)(absPath) : null);
|
|
146
|
+
// REQ-JIRATEAM-006: resolve the epic anchor — frontmatter first, then
|
|
147
|
+
// the binding default. `requireEpic` is off here so a bound repo whose
|
|
148
|
+
// spec pre-dates board-first intake doesn't hard-fail on every sync;
|
|
149
|
+
// the intake gate is what fences new specs. A refresh with no epic and
|
|
150
|
+
// no live parent still publishes unchanged Story text.
|
|
151
|
+
const specEpicKey = (0, publish_1.readFrontmatterValue)(fileContent, 'epicKey') ??
|
|
152
|
+
binding.binding.epicKey ??
|
|
153
|
+
undefined;
|
|
154
|
+
// Fingerprint short-circuit (A1): an already-published spec whose
|
|
155
|
+
// rendered Story fields match the last-published fingerprint performs
|
|
156
|
+
// ZERO JIRA writes — no updateIssue, no getIssue, no Sub-task probe.
|
|
157
|
+
if (existingKey) {
|
|
158
|
+
const priorFp = (0, publish_1.readFrontmatterValue)(fileContent, perKeys.fingerprintKey) ??
|
|
159
|
+
(soleRequirement
|
|
160
|
+
? (0, publish_1.readFrontmatterValue)(fileContent, 'jira_fingerprint')
|
|
161
|
+
: null);
|
|
162
|
+
if (priorFp) {
|
|
163
|
+
const fields = (0, publish_1.buildIssueFields)(source);
|
|
164
|
+
const nextFp = (0, publish_1.issueContentFingerprint)(fields.summary, fields.description);
|
|
165
|
+
if (priorFp === nextFp) {
|
|
166
|
+
results.push({
|
|
167
|
+
specId: spec.id,
|
|
168
|
+
status: 'unchanged',
|
|
169
|
+
jiraKey: existingKey,
|
|
170
|
+
subtasksCreated: 0,
|
|
171
|
+
});
|
|
172
|
+
continue;
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
try {
|
|
177
|
+
const result = await (0, publish_1.publishSpecToJira)(client, source, {
|
|
178
|
+
projectKey,
|
|
179
|
+
// REQ-JIRATEAM-009: honor the binding's issue-type overrides — a
|
|
180
|
+
// team-managed project without a `Story` type publishes as `Task`.
|
|
181
|
+
...(0, repo_config_1.bindingIssueTypes)(deps.projectRoot),
|
|
182
|
+
...(specEpicKey ? { epicKey: specEpicKey } : {}),
|
|
183
|
+
}, existingKey);
|
|
184
|
+
// Frontmatter write-back stamps jira_issue: and the fingerprint. Never
|
|
185
|
+
// re-id or rename here — auto-publish runs on every sync and must be
|
|
186
|
+
// safe on files that already carry links or share a file with siblings.
|
|
187
|
+
(0, publish_1.writeBackJiraIdentity)(absPath, result.key, {
|
|
188
|
+
fingerprint: result.fingerprint,
|
|
189
|
+
reId: null,
|
|
190
|
+
renameTo: null,
|
|
191
|
+
...(result.epicKey ? { epicKey: result.epicKey } : {}),
|
|
192
|
+
// REQ-JIRATEAM-010.A2: per-requirement keys in multi-REQ files.
|
|
193
|
+
...(soleRequirement ? {} : { forSpecId: spec.id }),
|
|
194
|
+
});
|
|
195
|
+
const status = result.created || result.subtasksCreated > 0 ? 'published' : 'unchanged';
|
|
196
|
+
results.push({
|
|
197
|
+
specId: spec.id,
|
|
198
|
+
status,
|
|
199
|
+
jiraKey: result.key,
|
|
200
|
+
subtasksCreated: result.subtasksCreated,
|
|
201
|
+
});
|
|
202
|
+
// Milestone: spec_published (REQ-JIRATEAM-003.A1). Idempotent — the
|
|
203
|
+
// dispatcher itself skips when the marker already exists, so calling
|
|
204
|
+
// it on every publish (including refresh) is safe. Soft-fail so a
|
|
205
|
+
// milestone comment issue never breaks auto-publish (A3).
|
|
206
|
+
if (status === 'published') {
|
|
207
|
+
await postMilestoneOnSuccess(client, result.key, spec.id, deps.projectRoot);
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
catch (err) {
|
|
211
|
+
// A4: one spec's failure never blocks the sync or its siblings.
|
|
212
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
213
|
+
results.push({ specId: spec.id, status: 'failed', note: msg });
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
return { results };
|
|
217
|
+
}
|
|
218
|
+
/**
|
|
219
|
+
* Best-effort spec_published milestone comment. A fake `PublishJiraClient`
|
|
220
|
+
* (some tests) doesn't implement the milestone methods — probe first and
|
|
221
|
+
* silently skip when they're missing, so test seams stay narrow.
|
|
222
|
+
*/
|
|
223
|
+
async function postMilestoneOnSuccess(client, issueKey, specId, projectRoot) {
|
|
224
|
+
const maybe = client;
|
|
225
|
+
if (typeof maybe.listCommentsDetailed !== 'function' ||
|
|
226
|
+
typeof maybe.addComment !== 'function' ||
|
|
227
|
+
typeof maybe.updateComment !== 'function') {
|
|
228
|
+
return;
|
|
229
|
+
}
|
|
230
|
+
await (0, milestone_comment_1.postMilestoneComment)(maybe, issueKey, 'spec_published', { specId }, { projectRoot });
|
|
231
|
+
}
|
|
232
|
+
/** One-line summary suitable for CLI output. */
|
|
233
|
+
function summarizeAutoPublishReport(report) {
|
|
234
|
+
if (report.skipped === 'unbound')
|
|
235
|
+
return '';
|
|
236
|
+
if (report.skipped === 'no-credentials') {
|
|
237
|
+
return `JIRA auto-publish skipped: ${report.note ?? 'no credentials'}`;
|
|
238
|
+
}
|
|
239
|
+
let published = 0;
|
|
240
|
+
let unchanged = 0;
|
|
241
|
+
let optedOut = 0;
|
|
242
|
+
let failed = 0;
|
|
243
|
+
for (const r of report.results) {
|
|
244
|
+
if (r.status === 'published')
|
|
245
|
+
published++;
|
|
246
|
+
else if (r.status === 'unchanged')
|
|
247
|
+
unchanged++;
|
|
248
|
+
else if (r.status === 'opted_out')
|
|
249
|
+
optedOut++;
|
|
250
|
+
else if (r.status === 'failed')
|
|
251
|
+
failed++;
|
|
252
|
+
}
|
|
253
|
+
return (`JIRA auto-publish: ${published} published, ${unchanged} unchanged, ` +
|
|
254
|
+
`${optedOut} opted out, ${failed} failed`);
|
|
255
|
+
}
|
|
256
|
+
//# sourceMappingURL=auto-publish.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"auto-publish.js","sourceRoot":"","sources":["../../src/jira/auto-publish.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Board-first anchor resolution (REQ-JIRATEAM-007).
|
|
3
|
+
*
|
|
4
|
+
* Every work-creating flow on a JIRA-bound repo MUST be anchored to a JIRA
|
|
5
|
+
* issue. This module answers ONE question for the CLI, MCP tool, and slash
|
|
6
|
+
* command that gate work: "given cwd + optional explicit/picked issueKey,
|
|
7
|
+
* do we have an anchor, refuse, or should we no-op (unbound)?"
|
|
8
|
+
*
|
|
9
|
+
* The return is a THREE-way discriminated union — `unbound` is first-class
|
|
10
|
+
* so callers on an unbound repo just proceed unchanged.
|
|
11
|
+
*
|
|
12
|
+
* When a bound repo refuses because no issue was picked, we ALSO surface the
|
|
13
|
+
* epic-scoped pickable work in the refusal payload so the pick can happen in
|
|
14
|
+
* the same round-trip (spec correction: A1 requires the refusal to LIST the
|
|
15
|
+
* pickable work, not just hint at it).
|
|
16
|
+
*/
|
|
17
|
+
import type { JiraIssue } from './types';
|
|
18
|
+
export type WorkAnchor = {
|
|
19
|
+
issueKey: string;
|
|
20
|
+
summary: string;
|
|
21
|
+
source: 'explicit' | 'picked' | 'epic-child';
|
|
22
|
+
};
|
|
23
|
+
export type AnchorUnbound = {
|
|
24
|
+
status: 'unbound';
|
|
25
|
+
};
|
|
26
|
+
export type AnchorAnchored = {
|
|
27
|
+
status: 'anchored';
|
|
28
|
+
anchor: WorkAnchor;
|
|
29
|
+
};
|
|
30
|
+
export type AnchorRefused = {
|
|
31
|
+
status: 'refused';
|
|
32
|
+
reason: 'no-pick' | 'no-epic-no-pick';
|
|
33
|
+
fixHint: string;
|
|
34
|
+
/** Epic-scoped open stories/tasks — populated on `no-pick` so the caller can
|
|
35
|
+
* render the picker in the same turn (A1). */
|
|
36
|
+
pickable?: JiraIssue[];
|
|
37
|
+
};
|
|
38
|
+
export type AnchorResolution = AnchorUnbound | AnchorAnchored | AnchorRefused;
|
|
39
|
+
/** Minimal client seam so tests inject a fake and never touch a real host. */
|
|
40
|
+
export interface AnchorJiraClient {
|
|
41
|
+
listMyIssues(opts?: {
|
|
42
|
+
project?: string;
|
|
43
|
+
sprint?: 'active';
|
|
44
|
+
epicKey?: string;
|
|
45
|
+
}): Promise<{
|
|
46
|
+
ok: true;
|
|
47
|
+
issues: JiraIssue[];
|
|
48
|
+
}>;
|
|
49
|
+
getIssue(key: string): Promise<{
|
|
50
|
+
ok: true;
|
|
51
|
+
issue: JiraIssue;
|
|
52
|
+
}>;
|
|
53
|
+
}
|
|
54
|
+
export interface ResolveWorkAnchorOptions {
|
|
55
|
+
cwd: string;
|
|
56
|
+
explicitIssueKey?: string;
|
|
57
|
+
pickedIssueKey?: string;
|
|
58
|
+
/** Test seam. Default: resolve credentials + new JiraClient. */
|
|
59
|
+
makeClient?: () => AnchorJiraClient;
|
|
60
|
+
}
|
|
61
|
+
export declare function resolveWorkAnchor(opts: ResolveWorkAnchorOptions): Promise<AnchorResolution>;
|
|
62
|
+
/** Canonical human-facing refusal message for CLI/MCP callers. */
|
|
63
|
+
export declare function formatRefusal(res: AnchorRefused): string;
|
|
64
|
+
//# sourceMappingURL=board-first.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"board-first.d.ts","sourceRoot":"","sources":["../../src/jira/board-first.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAKH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,SAAS,CAAC;AAEzC,MAAM,MAAM,UAAU,GAAG;IACvB,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,UAAU,GAAG,QAAQ,GAAG,YAAY,CAAC;CAC9C,CAAC;AAEF,MAAM,MAAM,aAAa,GAAG;IAAE,MAAM,EAAE,SAAS,CAAA;CAAE,CAAC;AAClD,MAAM,MAAM,cAAc,GAAG;IAAE,MAAM,EAAE,UAAU,CAAC;IAAC,MAAM,EAAE,UAAU,CAAA;CAAE,CAAC;AACxE,MAAM,MAAM,aAAa,GAAG;IAC1B,MAAM,EAAE,SAAS,CAAC;IAClB,MAAM,EAAE,SAAS,GAAG,iBAAiB,CAAC;IACtC,OAAO,EAAE,MAAM,CAAC;IAChB;mDAC+C;IAC/C,QAAQ,CAAC,EAAE,SAAS,EAAE,CAAC;CACxB,CAAC;AACF,MAAM,MAAM,gBAAgB,GAAG,aAAa,GAAG,cAAc,GAAG,aAAa,CAAC;AAE9E,8EAA8E;AAC9E,MAAM,WAAW,gBAAgB;IAC/B,YAAY,CAAC,IAAI,CAAC,EAAE;QAClB,OAAO,CAAC,EAAE,MAAM,CAAC;QACjB,MAAM,CAAC,EAAE,QAAQ,CAAC;QAClB,OAAO,CAAC,EAAE,MAAM,CAAC;KAClB,GAAG,OAAO,CAAC;QAAE,EAAE,EAAE,IAAI,CAAC;QAAC,MAAM,EAAE,SAAS,EAAE,CAAA;KAAE,CAAC,CAAC;IAC/C,QAAQ,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;QAAE,EAAE,EAAE,IAAI,CAAC;QAAC,KAAK,EAAE,SAAS,CAAA;KAAE,CAAC,CAAC;CAChE;AAED,MAAM,WAAW,wBAAwB;IACvC,GAAG,EAAE,MAAM,CAAC;IACZ,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,gEAAgE;IAChE,UAAU,CAAC,EAAE,MAAM,gBAAgB,CAAC;CACrC;AAOD,wBAAsB,iBAAiB,CACrC,IAAI,EAAE,wBAAwB,GAC7B,OAAO,CAAC,gBAAgB,CAAC,CAoD3B;AAED,kEAAkE;AAClE,wBAAgB,aAAa,CAAC,GAAG,EAAE,aAAa,GAAG,MAAM,CAaxD"}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Board-first anchor resolution (REQ-JIRATEAM-007).
|
|
4
|
+
*
|
|
5
|
+
* Every work-creating flow on a JIRA-bound repo MUST be anchored to a JIRA
|
|
6
|
+
* issue. This module answers ONE question for the CLI, MCP tool, and slash
|
|
7
|
+
* command that gate work: "given cwd + optional explicit/picked issueKey,
|
|
8
|
+
* do we have an anchor, refuse, or should we no-op (unbound)?"
|
|
9
|
+
*
|
|
10
|
+
* The return is a THREE-way discriminated union — `unbound` is first-class
|
|
11
|
+
* so callers on an unbound repo just proceed unchanged.
|
|
12
|
+
*
|
|
13
|
+
* When a bound repo refuses because no issue was picked, we ALSO surface the
|
|
14
|
+
* epic-scoped pickable work in the refusal payload so the pick can happen in
|
|
15
|
+
* the same round-trip (spec correction: A1 requires the refusal to LIST the
|
|
16
|
+
* pickable work, not just hint at it).
|
|
17
|
+
*/
|
|
18
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
19
|
+
exports.resolveWorkAnchor = resolveWorkAnchor;
|
|
20
|
+
exports.formatRefusal = formatRefusal;
|
|
21
|
+
const repo_config_1 = require("./repo-config");
|
|
22
|
+
const config_1 = require("./config");
|
|
23
|
+
const client_1 = require("./client");
|
|
24
|
+
function defaultMakeClient() {
|
|
25
|
+
const creds = (0, config_1.resolveJiraCredentials)();
|
|
26
|
+
return new client_1.JiraClient(creds);
|
|
27
|
+
}
|
|
28
|
+
async function resolveWorkAnchor(opts) {
|
|
29
|
+
const { binding } = (0, repo_config_1.loadRepoJiraBinding)(opts.cwd);
|
|
30
|
+
if (!binding)
|
|
31
|
+
return { status: 'unbound' };
|
|
32
|
+
const makeClient = opts.makeClient ?? defaultMakeClient;
|
|
33
|
+
const explicit = opts.explicitIssueKey?.trim();
|
|
34
|
+
const picked = opts.pickedIssueKey?.trim();
|
|
35
|
+
const key = explicit || picked;
|
|
36
|
+
if (key) {
|
|
37
|
+
const client = makeClient();
|
|
38
|
+
const res = await client.getIssue(key);
|
|
39
|
+
return {
|
|
40
|
+
status: 'anchored',
|
|
41
|
+
anchor: {
|
|
42
|
+
issueKey: res.issue.key,
|
|
43
|
+
summary: res.issue.summary,
|
|
44
|
+
source: explicit ? 'explicit' : 'picked',
|
|
45
|
+
},
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
if (!binding.epicKey) {
|
|
49
|
+
return {
|
|
50
|
+
status: 'refused',
|
|
51
|
+
reason: 'no-epic-no-pick',
|
|
52
|
+
fixHint: 'Set jira.epicKey in specship.config.json (or pick an issue with specship_jira_pick <KEY>).',
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
// Bound + epic + no pick → list the epic's open stories/tasks so the caller
|
|
56
|
+
// can render the pick in the same round-trip (A1).
|
|
57
|
+
let pickable = [];
|
|
58
|
+
try {
|
|
59
|
+
const client = makeClient();
|
|
60
|
+
const res = await client.listMyIssues({
|
|
61
|
+
project: binding.projectKey,
|
|
62
|
+
epicKey: binding.epicKey,
|
|
63
|
+
});
|
|
64
|
+
pickable = res.issues;
|
|
65
|
+
}
|
|
66
|
+
catch {
|
|
67
|
+
// Degrade to a refusal without pickable list — the fixHint still names
|
|
68
|
+
// the exact next step.
|
|
69
|
+
}
|
|
70
|
+
return {
|
|
71
|
+
status: 'refused',
|
|
72
|
+
reason: 'no-pick',
|
|
73
|
+
fixHint: 'Run specship_jira_issues to list the epic\'s open work, then specship_jira_pick <KEY> to anchor this run.',
|
|
74
|
+
pickable,
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
/** Canonical human-facing refusal message for CLI/MCP callers. */
|
|
78
|
+
function formatRefusal(res) {
|
|
79
|
+
const header = res.reason === 'no-epic-no-pick'
|
|
80
|
+
? 'Work refused: no JIRA anchor — no epic bound and no issue picked.'
|
|
81
|
+
: 'Work refused: no JIRA anchor — pick an issue under the bound epic.';
|
|
82
|
+
const lines = [header, res.fixHint];
|
|
83
|
+
if (res.pickable && res.pickable.length) {
|
|
84
|
+
lines.push('', 'Pickable issues:');
|
|
85
|
+
for (const i of res.pickable) {
|
|
86
|
+
lines.push(`- ${i.key} — ${i.summary} [${i.status}]`);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
return lines.join('\n');
|
|
90
|
+
}
|
|
91
|
+
//# sourceMappingURL=board-first.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"board-first.js","sourceRoot":"","sources":["../../src/jira/board-first.ts"],"names":[],"mappings":"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"}
|