@specforge/canary-cli 0.1.9 → 0.1.11

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 (51) hide show
  1. package/dist/cli/commands/scaffold/agent-types.js +1 -1
  2. package/dist/cli/commands/scaffold/agent-types.js.map +1 -1
  3. package/dist/cli/config/agent-teams.types.d.ts +35 -12
  4. package/dist/cli/config/agent-teams.types.d.ts.map +1 -1
  5. package/dist/cli/config/agent-teams.types.js.map +1 -1
  6. package/dist/cli/templates/agents/content/core/sfag-orchestrator.d.ts +21 -3
  7. package/dist/cli/templates/agents/content/core/sfag-orchestrator.d.ts.map +1 -1
  8. package/dist/cli/templates/agents/content/core/sfag-orchestrator.js +114 -48
  9. package/dist/cli/templates/agents/content/core/sfag-orchestrator.js.map +1 -1
  10. package/dist/cli/templates/agents/content/core/sfag-ticket-implementer.d.ts +22 -3
  11. package/dist/cli/templates/agents/content/core/sfag-ticket-implementer.d.ts.map +1 -1
  12. package/dist/cli/templates/agents/content/core/sfag-ticket-implementer.js +225 -160
  13. package/dist/cli/templates/agents/content/core/sfag-ticket-implementer.js.map +1 -1
  14. package/dist/cli/templates/agents/content/core/sfag-work-resolver.d.ts +16 -0
  15. package/dist/cli/templates/agents/content/core/sfag-work-resolver.d.ts.map +1 -0
  16. package/dist/cli/templates/agents/content/core/sfag-work-resolver.js +199 -0
  17. package/dist/cli/templates/agents/content/core/sfag-work-resolver.js.map +1 -0
  18. package/dist/cli/templates/agents/index.d.ts.map +1 -1
  19. package/dist/cli/templates/agents/index.js +2 -0
  20. package/dist/cli/templates/agents/index.js.map +1 -1
  21. package/dist/cli/templates/content/sf-reset.d.ts +5 -2
  22. package/dist/cli/templates/content/sf-reset.d.ts.map +1 -1
  23. package/dist/cli/templates/content/sf-reset.js +41 -27
  24. package/dist/cli/templates/content/sf-reset.js.map +1 -1
  25. package/dist/lib/prompt-generator.d.ts +15 -3
  26. package/dist/lib/prompt-generator.d.ts.map +1 -1
  27. package/dist/lib/prompt-generator.js +14 -6
  28. package/dist/lib/prompt-generator.js.map +1 -1
  29. package/dist/tools/core/__tests__/git-injection.test.d.ts +2 -0
  30. package/dist/tools/core/__tests__/git-injection.test.d.ts.map +1 -0
  31. package/dist/tools/core/git-injection.d.ts +50 -0
  32. package/dist/tools/core/git-injection.d.ts.map +1 -0
  33. package/dist/tools/core/git-injection.js +74 -0
  34. package/dist/tools/core/git-injection.js.map +1 -0
  35. package/dist/tools/index.d.ts.map +1 -1
  36. package/dist/tools/index.js +97 -69
  37. package/dist/tools/index.js.map +1 -1
  38. package/dist/types/index.d.ts +1 -20
  39. package/dist/types/index.d.ts.map +1 -1
  40. package/dist/types/index.js.map +1 -1
  41. package/dist/validation/index.d.ts.map +1 -1
  42. package/dist/validation/index.js +0 -6
  43. package/dist/validation/index.js.map +1 -1
  44. package/package.json +4 -3
  45. package/src/cli/templates/agents/content/core/sfag-orchestrator.ts +135 -51
  46. package/src/cli/templates/agents/content/core/sfag-ticket-implementer.ts +247 -163
  47. package/src/cli/templates/agents/content/core/sfag-work-resolver.ts +211 -0
  48. package/src/cli/templates/agents/index.ts +2 -0
  49. package/src/cli/templates/content/sf-reset.ts +45 -28
  50. package/src/cli/templates/skills/specforge-orchestrator.md +14 -9
  51. package/src/cli/templates/skills/specforge-worker.md +3 -2
@@ -0,0 +1,50 @@
1
+ /** Runs a `git` sub-command; returns trimmed stdout, or `null` on any failure. */
2
+ export type GitRunner = (args: string[]) => string | null;
3
+ /** Default runner: spawn `git`, return trimmed stdout, `null` on any failure. */
4
+ export declare const defaultGitRunner: GitRunner;
5
+ export interface WorktreeGitEvidence {
6
+ /**
7
+ * `true` when the worktree has no uncommitted changes — OR when the caller is
8
+ * not inside a git repo (a non-git caller is never "dirty", so SWS still runs).
9
+ */
10
+ gitClean: boolean;
11
+ /** Worktree root (`git rev-parse --show-toplevel`); `null` when not in a worktree. */
12
+ worktreePath: string | null;
13
+ /** Current branch (`git rev-parse --abbrev-ref HEAD`); `null` when not in a worktree. */
14
+ branch: string | null;
15
+ }
16
+ /**
17
+ * SWS git evidence — the clean-status precondition input + the worktree
18
+ * provenance (M23.2). Emits `null` provenance (and `gitClean: true`) when the
19
+ * agent is not inside a git worktree, so SWS still records the WorkSession.
20
+ */
21
+ export declare function readWorktreeGitEvidence(run?: GitRunner): WorktreeGitEvidence;
22
+ /** One injected file-change action (a subset of the AWS `record_file_change` payload). */
23
+ export interface LocalFileChangeAction {
24
+ action: 'record_file_change';
25
+ workSessionId: string;
26
+ ticketId: string;
27
+ expectedPath: string;
28
+ expectedAction: 'create' | 'modify' | 'delete';
29
+ actualAction: 'created' | 'modified' | 'deleted';
30
+ status: 'matched';
31
+ }
32
+ interface DeriveCtx {
33
+ workSessionId: string;
34
+ ticketId: string;
35
+ }
36
+ /**
37
+ * Parse `git status --porcelain` into the `record_file_change` set the MCP-local
38
+ * injects in `validateFiles: 'local'` mode — one action per created/modified/
39
+ * deleted path (the agent reports the `referenced` files itself). A rename (`R`)
40
+ * surfaces as a create of the new path.
41
+ */
42
+ export declare function deriveLocalFileChanges(statusPorcelain: string | null, ctx: DeriveCtx): LocalFileChangeAction[];
43
+ /**
44
+ * Read the worktree's changed-file set (`git status --porcelain`) and map it to
45
+ * the `record_file_change` actions the MCP-local injects (AWS `validateFiles:
46
+ * 'local'`). Empty when not inside a git worktree.
47
+ */
48
+ export declare function readLocalFileChanges(ctx: DeriveCtx, run?: GitRunner): LocalFileChangeAction[];
49
+ export {};
50
+ //# sourceMappingURL=git-injection.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"git-injection.d.ts","sourceRoot":"","sources":["../../../src/tools/core/git-injection.ts"],"names":[],"mappings":"AA2BA,kFAAkF;AAClF,MAAM,MAAM,SAAS,GAAG,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,MAAM,GAAG,IAAI,CAAC;AAE1D,iFAAiF;AACjF,eAAO,MAAM,gBAAgB,EAAE,SAU9B,CAAC;AAEF,MAAM,WAAW,mBAAmB;IAClC;;;OAGG;IACH,QAAQ,EAAE,OAAO,CAAC;IAClB,sFAAsF;IACtF,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,yFAAyF;IACzF,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;CACvB;AAED;;;;GAIG;AACH,wBAAgB,uBAAuB,CAAC,GAAG,GAAE,SAA4B,GAAG,mBAAmB,CAc9F;AAED,0FAA0F;AAC1F,MAAM,WAAW,qBAAqB;IACpC,MAAM,EAAE,oBAAoB,CAAC;IAC7B,aAAa,EAAE,MAAM,CAAC;IACtB,QAAQ,EAAE,MAAM,CAAC;IACjB,YAAY,EAAE,MAAM,CAAC;IACrB,cAAc,EAAE,QAAQ,GAAG,QAAQ,GAAG,QAAQ,CAAC;IAC/C,YAAY,EAAE,SAAS,GAAG,UAAU,GAAG,SAAS,CAAC;IACjD,MAAM,EAAE,SAAS,CAAC;CACnB;AAED,UAAU,SAAS;IACjB,aAAa,EAAE,MAAM,CAAC;IACtB,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED;;;;;GAKG;AACH,wBAAgB,sBAAsB,CACpC,eAAe,EAAE,MAAM,GAAG,IAAI,EAC9B,GAAG,EAAE,SAAS,GACb,qBAAqB,EAAE,CA4BzB;AAED;;;;GAIG;AACH,wBAAgB,oBAAoB,CAAC,GAAG,EAAE,SAAS,EAAE,GAAG,GAAE,SAA4B,GAAG,qBAAqB,EAAE,CAE/G"}
@@ -0,0 +1,74 @@
1
+ import { execFileSync } from "node:child_process";
2
+ const defaultGitRunner = (args) => {
3
+ try {
4
+ const out = execFileSync("git", args, {
5
+ encoding: "utf8",
6
+ stdio: ["ignore", "pipe", "ignore"]
7
+ });
8
+ return out.trim();
9
+ } catch {
10
+ return null;
11
+ }
12
+ };
13
+ function readWorktreeGitEvidence(run = defaultGitRunner) {
14
+ const worktreePath = run(["rev-parse", "--show-toplevel"]);
15
+ if (worktreePath === null || worktreePath === "") {
16
+ return { gitClean: true, worktreePath: null, branch: null };
17
+ }
18
+ const branch = run(["rev-parse", "--abbrev-ref", "HEAD"]);
19
+ const status = run(["status", "--porcelain"]);
20
+ return {
21
+ // A failed `git status` (null) must not block SWS → treat as clean.
22
+ gitClean: status === null ? true : status.trim() === "",
23
+ worktreePath,
24
+ branch: branch === null || branch === "" ? null : branch
25
+ };
26
+ }
27
+ function deriveLocalFileChanges(statusPorcelain, ctx) {
28
+ if (!statusPorcelain) return [];
29
+ const actions = [];
30
+ for (const raw of statusPorcelain.split("\n")) {
31
+ const line = raw.replace(/\r$/, "");
32
+ if (line.trim() === "") continue;
33
+ const xy = line.slice(0, 2);
34
+ let path = line.slice(3).trim();
35
+ const arrow = path.indexOf(" -> ");
36
+ if (arrow >= 0) path = path.slice(arrow + 4).trim();
37
+ if (path.startsWith('"') && path.endsWith('"')) path = path.slice(1, -1);
38
+ if (path === "") continue;
39
+ const kind = classifyPorcelain(xy);
40
+ if (!kind) continue;
41
+ actions.push({
42
+ action: "record_file_change",
43
+ workSessionId: ctx.workSessionId,
44
+ ticketId: ctx.ticketId,
45
+ expectedPath: path,
46
+ expectedAction: kind.expected,
47
+ actualAction: kind.actual,
48
+ status: "matched"
49
+ });
50
+ }
51
+ return actions;
52
+ }
53
+ function readLocalFileChanges(ctx, run = defaultGitRunner) {
54
+ return deriveLocalFileChanges(run(["status", "--porcelain"]), ctx);
55
+ }
56
+ function classifyPorcelain(xy) {
57
+ const s = xy.trim();
58
+ if (s === "") return null;
59
+ if (s === "??" || s.includes("A") || s.includes("R") || s.includes("C")) {
60
+ return { expected: "create", actual: "created" };
61
+ }
62
+ if (s.includes("D")) return { expected: "delete", actual: "deleted" };
63
+ if (s.includes("M") || s.includes("T") || s.includes("U")) {
64
+ return { expected: "modify", actual: "modified" };
65
+ }
66
+ return null;
67
+ }
68
+ export {
69
+ defaultGitRunner,
70
+ deriveLocalFileChanges,
71
+ readLocalFileChanges,
72
+ readWorktreeGitEvidence
73
+ };
74
+ //# sourceMappingURL=git-injection.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../src/tools/core/git-injection.ts"],"sourcesContent":["/**\n * MCP-local git-evidence injection (M23.2).\n *\n * The work lifecycle's SWS/AWS verbs need worktree git evidence that only the\n * MCP-LOCAL (this CLI, running on the agent's machine — the deployed Lambda has\n * NO worktree) can observe. Per `docs/architecture/work-lifecycle-flow.md` §0 +\n * [[project_coherence_score]]:\n *\n * - **SWS** (`start_work_session`): runs `git status` for the clean-workspace\n * precondition (`gitClean`) and reports the worktree provenance —\n * `worktreePath` via `git rev-parse --show-toplevel` + `branch` via\n * `git rev-parse --abbrev-ref HEAD` — so SWS persists\n * `WorkSession.worktreePath`/`branch` (M16.2). When the agent is NOT inside a\n * git worktree (\"caso esteja em uma\") the provenance is `null` (SWS still\n * records the WorkSession) and `gitClean` defaults to `true` (a non-git caller\n * is never \"dirty\" — it must not be blocked by the precondition).\n * - **AWS** (`action_work_session`, `validateFiles: 'local'`): runs\n * `git status --porcelain` and injects one `record_file_change` action per\n * created/modified/deleted file (the agent only reports the `referenced`\n * files git cannot see).\n *\n * The git commands run through an injectable `GitRunner` (defaults to a real,\n * non-throwing `git` spawn) so the layer is unit-testable without a live repo and\n * never throws inside a tool handler.\n */\nimport { execFileSync } from 'node:child_process';\n\n/** Runs a `git` sub-command; returns trimmed stdout, or `null` on any failure. */\nexport type GitRunner = (args: string[]) => string | null;\n\n/** Default runner: spawn `git`, return trimmed stdout, `null` on any failure. */\nexport const defaultGitRunner: GitRunner = (args) => {\n try {\n const out = execFileSync('git', args, {\n encoding: 'utf8',\n stdio: ['ignore', 'pipe', 'ignore'],\n });\n return out.trim();\n } catch {\n return null;\n }\n};\n\nexport interface WorktreeGitEvidence {\n /**\n * `true` when the worktree has no uncommitted changes — OR when the caller is\n * not inside a git repo (a non-git caller is never \"dirty\", so SWS still runs).\n */\n gitClean: boolean;\n /** Worktree root (`git rev-parse --show-toplevel`); `null` when not in a worktree. */\n worktreePath: string | null;\n /** Current branch (`git rev-parse --abbrev-ref HEAD`); `null` when not in a worktree. */\n branch: string | null;\n}\n\n/**\n * SWS git evidence — the clean-status precondition input + the worktree\n * provenance (M23.2). Emits `null` provenance (and `gitClean: true`) when the\n * agent is not inside a git worktree, so SWS still records the WorkSession.\n */\nexport function readWorktreeGitEvidence(run: GitRunner = defaultGitRunner): WorktreeGitEvidence {\n const worktreePath = run(['rev-parse', '--show-toplevel']);\n if (worktreePath === null || worktreePath === '') {\n // Not inside a git worktree — SWS still records the WorkSession.\n return { gitClean: true, worktreePath: null, branch: null };\n }\n const branch = run(['rev-parse', '--abbrev-ref', 'HEAD']);\n const status = run(['status', '--porcelain']);\n return {\n // A failed `git status` (null) must not block SWS → treat as clean.\n gitClean: status === null ? true : status.trim() === '',\n worktreePath,\n branch: branch === null || branch === '' ? null : branch,\n };\n}\n\n/** One injected file-change action (a subset of the AWS `record_file_change` payload). */\nexport interface LocalFileChangeAction {\n action: 'record_file_change';\n workSessionId: string;\n ticketId: string;\n expectedPath: string;\n expectedAction: 'create' | 'modify' | 'delete';\n actualAction: 'created' | 'modified' | 'deleted';\n status: 'matched';\n}\n\ninterface DeriveCtx {\n workSessionId: string;\n ticketId: string;\n}\n\n/**\n * Parse `git status --porcelain` into the `record_file_change` set the MCP-local\n * injects in `validateFiles: 'local'` mode — one action per created/modified/\n * deleted path (the agent reports the `referenced` files itself). A rename (`R`)\n * surfaces as a create of the new path.\n */\nexport function deriveLocalFileChanges(\n statusPorcelain: string | null,\n ctx: DeriveCtx,\n): LocalFileChangeAction[] {\n if (!statusPorcelain) return [];\n const actions: LocalFileChangeAction[] = [];\n for (const raw of statusPorcelain.split('\\n')) {\n const line = raw.replace(/\\r$/, '');\n if (line.trim() === '') continue;\n // porcelain v1: `XY<space>path` — XY is the 2-char status, path starts at col 3.\n const xy = line.slice(0, 2);\n let path = line.slice(3).trim();\n // Rename/copy renders as `old -> new`; record the new path.\n const arrow = path.indexOf(' -> ');\n if (arrow >= 0) path = path.slice(arrow + 4).trim();\n // git quotes paths with special chars.\n if (path.startsWith('\"') && path.endsWith('\"')) path = path.slice(1, -1);\n if (path === '') continue;\n const kind = classifyPorcelain(xy);\n if (!kind) continue;\n actions.push({\n action: 'record_file_change',\n workSessionId: ctx.workSessionId,\n ticketId: ctx.ticketId,\n expectedPath: path,\n expectedAction: kind.expected,\n actualAction: kind.actual,\n status: 'matched',\n });\n }\n return actions;\n}\n\n/**\n * Read the worktree's changed-file set (`git status --porcelain`) and map it to\n * the `record_file_change` actions the MCP-local injects (AWS `validateFiles:\n * 'local'`). Empty when not inside a git worktree.\n */\nexport function readLocalFileChanges(ctx: DeriveCtx, run: GitRunner = defaultGitRunner): LocalFileChangeAction[] {\n return deriveLocalFileChanges(run(['status', '--porcelain']), ctx);\n}\n\nfunction classifyPorcelain(\n xy: string,\n): { expected: 'create' | 'modify' | 'delete'; actual: 'created' | 'modified' | 'deleted' } | null {\n const s = xy.trim();\n if (s === '') return null;\n // Untracked / added / renamed / copied → a new (created) file.\n if (s === '??' || s.includes('A') || s.includes('R') || s.includes('C')) {\n return { expected: 'create', actual: 'created' };\n }\n if (s.includes('D')) return { expected: 'delete', actual: 'deleted' };\n if (s.includes('M') || s.includes('T') || s.includes('U')) {\n return { expected: 'modify', actual: 'modified' };\n }\n return null;\n}\n"],"mappings":"AAyBA,SAAS,oBAAoB;AAMtB,MAAM,mBAA8B,CAAC,SAAS;AACnD,MAAI;AACF,UAAM,MAAM,aAAa,OAAO,MAAM;AAAA,MACpC,UAAU;AAAA,MACV,OAAO,CAAC,UAAU,QAAQ,QAAQ;AAAA,IACpC,CAAC;AACD,WAAO,IAAI,KAAK;AAAA,EAClB,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAmBO,SAAS,wBAAwB,MAAiB,kBAAuC;AAC9F,QAAM,eAAe,IAAI,CAAC,aAAa,iBAAiB,CAAC;AACzD,MAAI,iBAAiB,QAAQ,iBAAiB,IAAI;AAEhD,WAAO,EAAE,UAAU,MAAM,cAAc,MAAM,QAAQ,KAAK;AAAA,EAC5D;AACA,QAAM,SAAS,IAAI,CAAC,aAAa,gBAAgB,MAAM,CAAC;AACxD,QAAM,SAAS,IAAI,CAAC,UAAU,aAAa,CAAC;AAC5C,SAAO;AAAA;AAAA,IAEL,UAAU,WAAW,OAAO,OAAO,OAAO,KAAK,MAAM;AAAA,IACrD;AAAA,IACA,QAAQ,WAAW,QAAQ,WAAW,KAAK,OAAO;AAAA,EACpD;AACF;AAwBO,SAAS,uBACd,iBACA,KACyB;AACzB,MAAI,CAAC,gBAAiB,QAAO,CAAC;AAC9B,QAAM,UAAmC,CAAC;AAC1C,aAAW,OAAO,gBAAgB,MAAM,IAAI,GAAG;AAC7C,UAAM,OAAO,IAAI,QAAQ,OAAO,EAAE;AAClC,QAAI,KAAK,KAAK,MAAM,GAAI;AAExB,UAAM,KAAK,KAAK,MAAM,GAAG,CAAC;AAC1B,QAAI,OAAO,KAAK,MAAM,CAAC,EAAE,KAAK;AAE9B,UAAM,QAAQ,KAAK,QAAQ,MAAM;AACjC,QAAI,SAAS,EAAG,QAAO,KAAK,MAAM,QAAQ,CAAC,EAAE,KAAK;AAElD,QAAI,KAAK,WAAW,GAAG,KAAK,KAAK,SAAS,GAAG,EAAG,QAAO,KAAK,MAAM,GAAG,EAAE;AACvE,QAAI,SAAS,GAAI;AACjB,UAAM,OAAO,kBAAkB,EAAE;AACjC,QAAI,CAAC,KAAM;AACX,YAAQ,KAAK;AAAA,MACX,QAAQ;AAAA,MACR,eAAe,IAAI;AAAA,MACnB,UAAU,IAAI;AAAA,MACd,cAAc;AAAA,MACd,gBAAgB,KAAK;AAAA,MACrB,cAAc,KAAK;AAAA,MACnB,QAAQ;AAAA,IACV,CAAC;AAAA,EACH;AACA,SAAO;AACT;AAOO,SAAS,qBAAqB,KAAgB,MAAiB,kBAA2C;AAC/G,SAAO,uBAAuB,IAAI,CAAC,UAAU,aAAa,CAAC,GAAG,GAAG;AACnE;AAEA,SAAS,kBACP,IACiG;AACjG,QAAM,IAAI,GAAG,KAAK;AAClB,MAAI,MAAM,GAAI,QAAO;AAErB,MAAI,MAAM,QAAQ,EAAE,SAAS,GAAG,KAAK,EAAE,SAAS,GAAG,KAAK,EAAE,SAAS,GAAG,GAAG;AACvE,WAAO,EAAE,UAAU,UAAU,QAAQ,UAAU;AAAA,EACjD;AACA,MAAI,EAAE,SAAS,GAAG,EAAG,QAAO,EAAE,UAAU,UAAU,QAAQ,UAAU;AACpE,MAAI,EAAE,SAAS,GAAG,KAAK,EAAE,SAAS,GAAG,KAAK,EAAE,SAAS,GAAG,GAAG;AACzD,WAAO,EAAE,UAAU,UAAU,QAAQ,WAAW;AAAA,EAClD;AACA,SAAO;AACT;","names":[]}
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/tools/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,SAAS,EAAE,MAAM,yBAAyB,CAAC;AACpD,OAAO,EAML,gBAAgB,EACjB,MAAM,wBAAwB,CAAC;AAShC;;GAEG;AACH,MAAM,WAAW,IAAI;IACnB,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,EAAE;QACX,IAAI,EAAE,QAAQ,CAAC;QACf,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;QACpC,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC;KACrB,CAAC;CACH;AAED;;;;GAIG;AACH,wBAAgB,QAAQ,IAAI,IAAI,EAAE,CAslCjC;AAED;;GAEG;AACH,KAAK,WAAW,GAAG,CACjB,SAAS,EAAE,SAAS,EACpB,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAC1B,OAAO,CAAC,OAAO,CAAC,CAAC;AA6EtB;;;;;GAKG;AACH,wBAAgB,kBAAkB,CAChC,SAAS,EAAE,SAAS,GACnB,MAAM,CAAC,MAAM,EAAE,WAAW,CAAC,CAkU7B;AAiBD;;GAEG;AACH,wBAAgB,iBAAiB,IAAI,IAAI,CAExC;AAED;;;;;;;;GAQG;AACH,wBAAsB,cAAc,CAClC,SAAS,EAAE,SAAS,EACpB,QAAQ,EAAE,MAAM,EAChB,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC7B,KAAK,GAAE,OAAe,GACrB,OAAO,CAAC,OAAO,CAAC,CA4DlB;AAED;;;GAGG;AACH,wBAAsB,kBAAkB,CACtC,SAAS,EAAE,SAAS,EACpB,QAAQ,EAAE,MAAM,EAChB,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC7B,KAAK,GAAE,OAAe,GACrB,OAAO,CAAC,OAAO,GAAG,gBAAgB,CAAC,CAOrC;AAGD,OAAO,EACL,eAAe,EACf,QAAQ,EACR,gBAAgB,EAChB,cAAc,EACd,cAAc,EACd,KAAK,gBAAgB,GACtB,MAAM,wBAAwB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/tools/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,SAAS,EAAE,MAAM,yBAAyB,CAAC;AACpD,OAAO,EAML,gBAAgB,EACjB,MAAM,wBAAwB,CAAC;AAUhC;;GAEG;AACH,MAAM,WAAW,IAAI;IACnB,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,EAAE;QACX,IAAI,EAAE,QAAQ,CAAC;QACf,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;QACpC,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC;KACrB,CAAC;CACH;AAED;;;;GAIG;AACH,wBAAgB,QAAQ,IAAI,IAAI,EAAE,CA6nCjC;AAED;;GAEG;AACH,KAAK,WAAW,GAAG,CACjB,SAAS,EAAE,SAAS,EACpB,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAC1B,OAAO,CAAC,OAAO,CAAC,CAAC;AA6EtB;;;;;GAKG;AACH,wBAAgB,kBAAkB,CAChC,SAAS,EAAE,SAAS,GACnB,MAAM,CAAC,MAAM,EAAE,WAAW,CAAC,CAmU7B;AAiBD;;GAEG;AACH,wBAAgB,iBAAiB,IAAI,IAAI,CAExC;AAED;;;;;;;;GAQG;AACH,wBAAsB,cAAc,CAClC,SAAS,EAAE,SAAS,EACpB,QAAQ,EAAE,MAAM,EAChB,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC7B,KAAK,GAAE,OAAe,GACrB,OAAO,CAAC,OAAO,CAAC,CA4DlB;AAED;;;GAGG;AACH,wBAAsB,kBAAkB,CACtC,SAAS,EAAE,SAAS,EACpB,QAAQ,EAAE,MAAM,EAChB,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC7B,KAAK,GAAE,OAAe,GACrB,OAAO,CAAC,OAAO,GAAG,gBAAgB,CAAC,CAOrC;AAGD,OAAO,EACL,eAAe,EACf,QAAQ,EACR,gBAAgB,EAChB,cAAc,EACd,cAAc,EACd,KAAK,gBAAgB,GACtB,MAAM,wBAAwB,CAAC"}
@@ -7,6 +7,7 @@ import {
7
7
  } from "../validation/index.js";
8
8
  import { injectContext, injectContextRequired } from "./core/context-helper.js";
9
9
  import { probeExistingFiles } from "./core/file-existence-injection.js";
10
+ import { readWorktreeGitEvidence } from "./core/git-injection.js";
10
11
  import { tryLoadProjectConfig, saveProjectConfig } from "../cli/config/index.js";
11
12
  import {
12
13
  appendPlanningSessionRegistry,
@@ -105,7 +106,7 @@ At least one scope filter (projectId, specificationId, or epicId) is required.`,
105
106
  type: "array",
106
107
  items: {
107
108
  type: "string",
108
- enum: ["pending", "ready", "active", "done"]
109
+ enum: ["pending", "ready", "active", "done", "blocked"]
109
110
  },
110
111
  description: "Filter by status"
111
112
  },
@@ -136,6 +137,37 @@ At least one scope filter (projectId, specificationId, or epicId) is required.`,
136
137
  offset: {
137
138
  type: "number",
138
139
  description: "Pagination offset (default: 0)"
140
+ },
141
+ fields: {
142
+ type: "array",
143
+ items: {
144
+ type: "string",
145
+ enum: [
146
+ "id",
147
+ "epicId",
148
+ "ticketNumber",
149
+ "title",
150
+ "description",
151
+ "status",
152
+ "priority",
153
+ "complexity",
154
+ "estimatedHours",
155
+ "actualHours",
156
+ "acceptanceCriteria",
157
+ "implementation",
158
+ "technicalDetails",
159
+ "notes",
160
+ "tags",
161
+ "progress",
162
+ "testsPassed",
163
+ "order",
164
+ "startedAt",
165
+ "completedAt",
166
+ "createdAt",
167
+ "updatedAt"
168
+ ]
169
+ },
170
+ description: "Select specific fields to return. Returns all fields if not specified. id is always included."
139
171
  }
140
172
  }
141
173
  }
@@ -273,6 +305,8 @@ Format options:
273
305
  "unlink_blueprint_to_tickets",
274
306
  "create_dependencies",
275
307
  "delete_dependencies",
308
+ "justify",
309
+ "unjustify",
276
310
  "get_planning_status"
277
311
  ],
278
312
  description: "The planning operation to perform (lifecycle vocabulary). The type IS the backend PlanningOperationName; remaining fields are the operation payload."
@@ -283,7 +317,7 @@ Format options:
283
317
  {
284
318
  properties: {
285
319
  type: { const: "update_spec" },
286
- fields: { type: "object", description: "Partial spec update \u2014 only the keys you send are changed (e.g. background, goals, nonGoals, constraints, successCriteria)." }
320
+ fields: { type: "object", description: "Partial spec update \u2014 only the keys you send are changed (e.g. background, goals, nonGoals, constraints, successCriteria). Fields that do not apply are declared with the `justify` op (undo with `unjustify`), never by writing values here." }
287
321
  },
288
322
  required: ["type", "fields"]
289
323
  },
@@ -305,7 +339,7 @@ Format options:
305
339
  id: { type: "string", description: "Epic id (use list_epics / lookup_epic to find)." },
306
340
  fields: {
307
341
  type: "object",
308
- description: "Partial epic update \u2014 only the keys you send are changed.",
342
+ description: "Partial epic update \u2014 only the keys you send are changed. Fields that do not apply are declared with the `justify` op (undo with `unjustify`), never by writing values here.",
309
343
  properties: {
310
344
  title: { type: "string" },
311
345
  description: { type: "string" },
@@ -346,7 +380,7 @@ Format options:
346
380
  id: { type: "string", description: "Ticket id (use list_tickets / lookup_ticket to find)." },
347
381
  fields: {
348
382
  type: "object",
349
- description: "Partial ticket update \u2014 only the keys you send are changed. Child-backed arrays (acceptanceCriteria, implementationSteps, filesToBe*, testSpecification.testTypes, codeSnippets, typeSnippets) replace the whole set. Blueprint links are NOT settable here \u2014 use link_blueprint_to_tickets (from ticket_decomposition onward), the sole writer of the blueprint\u2194ticket relation.",
383
+ description: "Partial ticket update \u2014 only the keys you send are changed. Child-backed arrays (acceptanceCriteria, implementationSteps, filesToBe*, testSpecification.testTypes, codeSnippets, typeSnippets) replace the whole set. Blueprint links are NOT settable here \u2014 use link_blueprint_to_tickets (from ticket_decomposition onward), the sole writer of the blueprint\u2194ticket relation. Fields that do not apply are declared with the `justify` op (undo with `unjustify`), never by writing values here.",
350
384
  properties: {
351
385
  title: { type: "string" },
352
386
  description: { type: "string" },
@@ -466,6 +500,30 @@ Format options:
466
500
  },
467
501
  required: ["type", "blueprintId", "ticketIds"]
468
502
  },
503
+ // justify — declare a field N/A (the paired declare op; unjustify removes it).
504
+ // Structural-neutral: it writes only the fieldDeclarations[scope] declaration,
505
+ // so it is native in EVERY planning phase and never rolls back. `scope` is the
506
+ // canonical field name the checks read (e.g. dependencies, codeReferences,
507
+ // apiContracts) — NOT a `fieldDeclarations`-shaped payload on update_*.
508
+ {
509
+ properties: {
510
+ type: { const: "justify" },
511
+ scope: { type: "string", description: "Canonical field name to mark not-applicable (e.g. 'dependencies', 'codeReferences', 'apiContracts')." },
512
+ entityId: { type: "string", description: "The spec/epic/ticket id the field belongs to (resolved server-side; ids are unique across the spec)." },
513
+ reason: { type: "string", description: "Why the field does not apply (must be at least 20 characters)." }
514
+ },
515
+ required: ["type", "scope", "entityId", "reason"]
516
+ },
517
+ // unjustify — remove a previously declared N/A (clears fieldDeclarations[scope]).
518
+ // A no-op if the scope was never justified. No reason (a delete is unconditional).
519
+ {
520
+ properties: {
521
+ type: { const: "unjustify" },
522
+ scope: { type: "string", description: "Canonical field name whose not-applicable declaration to remove." },
523
+ entityId: { type: "string", description: "The spec/epic/ticket id the field belongs to." }
524
+ },
525
+ required: ["type", "scope", "entityId"]
526
+ },
469
527
  {
470
528
  // Read-only poll/resume. To read a single ticket, use the `get` tool (type:'ticket').
471
529
  properties: {
@@ -509,8 +567,8 @@ Format options:
509
567
  description: `Update checklist state during an active WorkSession. Combines step completion, AC validation, test result reporting, file tracking, reference review confirmation, and discovery reporting into a single atomic operation. All state changes are recorded on the WorkSession and its related validation/completion records.
510
568
 
511
569
  Use this instead of calling update_ticket for checklist changes. Provides:
512
- - Step completion (individual or bulk via WorkSessionStepCompletion)
513
- - Acceptance criteria validation (individual or bulk via WorkSessionACValidation)
570
+ - Step completion (individual or bulk via WorkSessionImplStepCompletion)
571
+ - Acceptance criteria validation (individual or bulk via WorkSessionAcceptanceCheck)
514
572
  - Test result reporting (merged by test type via WorkSessionTestResult)
515
573
  - File tracking (created, modified, deleted on WorkSession)
516
574
  - Discovery reporting (create discoveries for blockers, bugs, tech debt)
@@ -531,6 +589,33 @@ Set getTicket: true to fetch full ticket details in the response \u2014 useful f
531
589
  type: "string",
532
590
  description: "The ID of the ticket being worked on (must be in active status)"
533
591
  },
592
+ action: {
593
+ type: "object",
594
+ description: "The assay action to record \u2014 the AWS action vocabulary. `action` (the discriminator) is one of: mark_implementation_step_completion, mark_acceptance_check, record_test_result, add_skip_justification, amend_skip_justification, add_failure_justification, amend_failure_justification, record_file_change, add_file_change_justification, amend_file_change_justification, create_discovery (with `blocking`), update_discovery, set_discovery_resolved. Include `workSessionId` and the per-action fields. Forwarded verbatim as the lifecycle inner payload.",
595
+ properties: {
596
+ action: {
597
+ type: "string",
598
+ enum: [
599
+ "mark_implementation_step_completion",
600
+ "mark_acceptance_check",
601
+ "record_test_result",
602
+ "add_skip_justification",
603
+ "amend_skip_justification",
604
+ "add_failure_justification",
605
+ "amend_failure_justification",
606
+ "record_file_change",
607
+ "add_file_change_justification",
608
+ "amend_file_change_justification",
609
+ "create_discovery",
610
+ "update_discovery",
611
+ "set_discovery_resolved"
612
+ ],
613
+ description: "The AWS action discriminator."
614
+ },
615
+ workSessionId: { type: "string", description: "The active WorkSession id (from start_work_session)." }
616
+ },
617
+ required: ["action"]
618
+ },
534
619
  steps: {
535
620
  type: "array",
536
621
  description: "Individual step completion updates",
@@ -974,53 +1059,6 @@ Set getTicket: true to fetch full ticket details in the response \u2014 useful f
974
1059
  required: ["operation"]
975
1060
  }
976
1061
  },
977
- {
978
- name: "reset_work_session",
979
- description: "Reset tickets to pending/ready status (calculated from dependencies). Returns statusCalculation showing how many became pending vs ready.",
980
- inputSchema: {
981
- type: "object",
982
- properties: {
983
- specificationId: {
984
- type: "string",
985
- description: "The specification containing the tickets"
986
- },
987
- ticketIds: {
988
- type: "array",
989
- items: { type: "string" },
990
- description: "Specific ticket IDs to reset"
991
- },
992
- fromTicketId: {
993
- type: "string",
994
- description: "Reset this ticket and all its dependents"
995
- },
996
- epicId: {
997
- type: "string",
998
- description: "Reset all tickets in this epic"
999
- },
1000
- allTickets: {
1001
- type: "boolean",
1002
- description: "Reset all tickets in the specification"
1003
- },
1004
- resetDependents: {
1005
- type: "boolean",
1006
- description: "Also reset tickets that depend on the specified tickets (default: false)"
1007
- },
1008
- includeCompleted: {
1009
- type: "boolean",
1010
- description: "Include tickets with done status in the reset (default: false)"
1011
- },
1012
- preserveNotes: {
1013
- type: "boolean",
1014
- description: "Keep existing notes on tickets (default: true)"
1015
- },
1016
- clearTestResults: {
1017
- type: "boolean",
1018
- description: "Clear test result history (default: false)"
1019
- }
1020
- },
1021
- required: ["specificationId"]
1022
- }
1023
- },
1024
1062
  {
1025
1063
  name: "link_pull_request",
1026
1064
  description: "Associate a pull request with a ticket",
@@ -1300,29 +1338,19 @@ function createToolHandlers(apiClient) {
1300
1338
  },
1301
1339
  start_work_session: async (_client, args) => {
1302
1340
  validateRequired(args, "ticketId");
1341
+ const git = readWorktreeGitEvidence();
1303
1342
  return await callLocal("start_work_session", {
1304
- ticketId: args.ticketId
1343
+ ticketId: args.ticketId,
1344
+ gitClean: git.gitClean,
1345
+ worktreePath: git.worktreePath,
1346
+ branch: git.branch
1305
1347
  });
1306
1348
  },
1307
1349
  action_work_session: async (_client, args) => {
1308
1350
  validateRequired(args, "ticketId");
1309
1351
  return await callLocal("action_work_session", {
1310
1352
  ticketId: args.ticketId,
1311
- steps: args.steps,
1312
- allStepsDone: args.allStepsDone,
1313
- acceptanceCriteria: args.acceptanceCriteria,
1314
- allACValidated: args.allACValidated,
1315
- testResults: args.testResults,
1316
- notes: args.notes,
1317
- filesCreated: args.filesCreated,
1318
- filesModified: args.filesModified,
1319
- filesDeleted: args.filesDeleted,
1320
- discovery: args.discovery,
1321
- blockReason: args.blockReason,
1322
- getTicket: args.getTicket,
1323
- includeFullContext: args.includeFullContext,
1324
- codeSnippetsReviewed: args.codeSnippetsReviewed,
1325
- typeReferencesReviewed: args.typeReferencesReviewed
1353
+ payload: args.action ?? args.payload
1326
1354
  });
1327
1355
  },
1328
1356
  complete_work_session: async (_client, args) => {