@momentiq/dark-factory-cli 0.7.0-alpha.9 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (79) hide show
  1. package/dist/cli.js +383 -29
  2. package/dist/cli.js.map +1 -1
  3. package/dist/handoff/accept-verb.d.ts +31 -0
  4. package/dist/handoff/accept-verb.d.ts.map +1 -0
  5. package/dist/handoff/accept-verb.js +165 -0
  6. package/dist/handoff/accept-verb.js.map +1 -0
  7. package/dist/handoff/args.d.ts +19 -0
  8. package/dist/handoff/args.d.ts.map +1 -0
  9. package/dist/handoff/args.js +68 -0
  10. package/dist/handoff/args.js.map +1 -0
  11. package/dist/handoff/assignees.d.ts +37 -0
  12. package/dist/handoff/assignees.d.ts.map +1 -0
  13. package/dist/handoff/assignees.js +46 -0
  14. package/dist/handoff/assignees.js.map +1 -0
  15. package/dist/handoff/handoff-verb.d.ts +34 -0
  16. package/dist/handoff/handoff-verb.d.ts.map +1 -0
  17. package/dist/handoff/handoff-verb.js +451 -0
  18. package/dist/handoff/handoff-verb.js.map +1 -0
  19. package/dist/handoff/handoffs-verb.d.ts +29 -0
  20. package/dist/handoff/handoffs-verb.d.ts.map +1 -0
  21. package/dist/handoff/handoffs-verb.js +84 -0
  22. package/dist/handoff/handoffs-verb.js.map +1 -0
  23. package/dist/handoff/index.d.ts +14 -143
  24. package/dist/handoff/index.d.ts.map +1 -1
  25. package/dist/handoff/index.js +28 -647
  26. package/dist/handoff/index.js.map +1 -1
  27. package/dist/handoff/iso.d.ts +9 -0
  28. package/dist/handoff/iso.d.ts.map +1 -0
  29. package/dist/handoff/iso.js +47 -0
  30. package/dist/handoff/iso.js.map +1 -0
  31. package/dist/handoff/links.d.ts +48 -0
  32. package/dist/handoff/links.d.ts.map +1 -0
  33. package/dist/handoff/links.js +211 -0
  34. package/dist/handoff/links.js.map +1 -0
  35. package/dist/handoff/markers.d.ts +50 -0
  36. package/dist/handoff/markers.d.ts.map +1 -0
  37. package/dist/handoff/markers.js +121 -0
  38. package/dist/handoff/markers.js.map +1 -0
  39. package/dist/handoff/ports.d.ts +126 -0
  40. package/dist/handoff/ports.d.ts.map +1 -0
  41. package/dist/handoff/ports.js +34 -0
  42. package/dist/handoff/ports.js.map +1 -0
  43. package/dist/handoff/real-clients.d.ts +43 -0
  44. package/dist/handoff/real-clients.d.ts.map +1 -0
  45. package/dist/handoff/real-clients.js +291 -0
  46. package/dist/handoff/real-clients.js.map +1 -0
  47. package/dist/handoff/rehydrate-core.d.ts +55 -0
  48. package/dist/handoff/rehydrate-core.d.ts.map +1 -0
  49. package/dist/handoff/rehydrate-core.js +243 -0
  50. package/dist/handoff/rehydrate-core.js.map +1 -0
  51. package/dist/handoff/rehydrate-render.d.ts +3 -0
  52. package/dist/handoff/rehydrate-render.d.ts.map +1 -0
  53. package/dist/handoff/rehydrate-render.js +107 -0
  54. package/dist/handoff/rehydrate-render.js.map +1 -0
  55. package/dist/handoff/rehydrate-verb.d.ts +31 -0
  56. package/dist/handoff/rehydrate-verb.d.ts.map +1 -0
  57. package/dist/handoff/rehydrate-verb.js +111 -0
  58. package/dist/handoff/rehydrate-verb.js.map +1 -0
  59. package/dist/handoff/scrub.d.ts +99 -0
  60. package/dist/handoff/scrub.d.ts.map +1 -0
  61. package/dist/handoff/scrub.js +127 -0
  62. package/dist/handoff/scrub.js.map +1 -0
  63. package/dist/handoff/strip-control.d.ts +4 -0
  64. package/dist/handoff/strip-control.d.ts.map +1 -0
  65. package/dist/handoff/strip-control.js +16 -0
  66. package/dist/handoff/strip-control.js.map +1 -0
  67. package/dist/mcp/server.d.ts +0 -8
  68. package/dist/mcp/server.d.ts.map +1 -1
  69. package/dist/mcp/server.js +1 -5
  70. package/dist/mcp/server.js.map +1 -1
  71. package/dist/mcp/tools/handoff.d.ts +16 -8
  72. package/dist/mcp/tools/handoff.d.ts.map +1 -1
  73. package/dist/mcp/tools/handoff.js +253 -303
  74. package/dist/mcp/tools/handoff.js.map +1 -1
  75. package/package.json +2 -2
  76. package/dist/handoff/cli.d.ts +0 -5
  77. package/dist/handoff/cli.d.ts.map +0 -1
  78. package/dist/handoff/cli.js +0 -227
  79. package/dist/handoff/cli.js.map +0 -1
@@ -0,0 +1,126 @@
1
+ /**
2
+ * Raised by the core for every operator-facing refusal/abort (the bash
3
+ * `die`). Carries an optional `savedNotePath` so the CLI can echo where a
4
+ * composed note was preserved when a push/gate blocked it (Decision D5 —
5
+ * the reasoning is the precious artifact and is never discarded).
6
+ */
7
+ export declare class HandoffError extends Error {
8
+ readonly savedNotePath: string | undefined;
9
+ constructor(message: string, savedNotePath?: string);
10
+ }
11
+ export interface IssueView {
12
+ readonly number: number;
13
+ readonly title: string;
14
+ readonly body: string;
15
+ readonly state: "OPEN" | "CLOSED";
16
+ readonly assignees: ReadonlyArray<{
17
+ readonly login: string;
18
+ }>;
19
+ readonly labels: ReadonlyArray<{
20
+ readonly name: string;
21
+ }>;
22
+ readonly updatedAt: string;
23
+ readonly closedAt: string | null;
24
+ }
25
+ /**
26
+ * Subset of IssueView fields for drift/race checks (assignees + state +
27
+ * updatedAt; body omitted). Separate seam from issueView so the fake's
28
+ * ordinal counters split: _body-bearing_ vs _drift-only_ calls increment
29
+ * independently (mirrors bash's `issue_view_body` vs `issue_view` counter
30
+ * namespaces in tests/bin/gh).
31
+ */
32
+ export interface IssueViewSlim {
33
+ readonly state: "OPEN" | "CLOSED";
34
+ readonly assignees: ReadonlyArray<{
35
+ readonly login: string;
36
+ }>;
37
+ readonly updatedAt: string;
38
+ /** body is optional — the drift check may skip it. */
39
+ readonly body?: string;
40
+ }
41
+ export interface PrView {
42
+ readonly title: string;
43
+ readonly state: "OPEN" | "CLOSED" | "MERGED";
44
+ readonly mergeStateStatus: string;
45
+ readonly reviewDecision: string;
46
+ readonly statusCheckRollup: ReadonlyArray<{
47
+ readonly conclusion?: string;
48
+ readonly state?: string;
49
+ readonly status?: string;
50
+ }>;
51
+ }
52
+ export interface IssueListItem {
53
+ readonly number: number;
54
+ readonly title: string;
55
+ readonly assignees: ReadonlyArray<{
56
+ readonly login: string;
57
+ }>;
58
+ readonly body?: string;
59
+ readonly createdAt?: string;
60
+ readonly updatedAt?: string;
61
+ readonly closedAt?: string;
62
+ }
63
+ export interface IssueCreated {
64
+ readonly number: number;
65
+ readonly url: string;
66
+ }
67
+ export interface GhClient {
68
+ /** `gh auth status` — throws on non-zero. */
69
+ authStatus(): Promise<void>;
70
+ /** `gh api user --jq .login` */
71
+ apiUserLogin(): Promise<string>;
72
+ /** `gh label create handoff …` (idempotent — failure not fatal) */
73
+ ensureHandoffLabel(): Promise<void>;
74
+ /** `gh issue view <n> --json <full set>` — full IssueView (body included) */
75
+ issueView(num: number, opts?: {
76
+ repo?: string;
77
+ }): Promise<IssueView>;
78
+ /** Same fields minus body (separate seam for drift checks). */
79
+ issueViewSlim(num: number, opts?: {
80
+ repo?: string;
81
+ }): Promise<IssueViewSlim>;
82
+ /** `gh issue list --label handoff --state <s> ...` */
83
+ issueList(opts: {
84
+ state: "open" | "closed";
85
+ assignee?: "@me" | string;
86
+ search?: string;
87
+ }): Promise<readonly IssueListItem[]>;
88
+ /** `gh issue create --title <t> --body-file - --label handoff` */
89
+ issueCreate(opts: {
90
+ title: string;
91
+ bodyMd: string;
92
+ label: string;
93
+ }): Promise<IssueCreated>;
94
+ /** `gh issue edit <n> --body-file -` */
95
+ issueEditBody(num: number, bodyMd: string): Promise<void>;
96
+ /** `gh issue edit <n> --add-label <l>` */
97
+ issueAddLabel(num: number, label: string): Promise<void>;
98
+ /** `gh issue edit <n> --add-assignee @me` */
99
+ issueAssignMe(num: number): Promise<void>;
100
+ /** `gh issue edit <n> --remove-assignee @me` — idempotent; throws only on REAL gh errors. */
101
+ issueUnassignMe(num: number): Promise<void>;
102
+ /** `gh issue close <n>` */
103
+ issueClose(num: number): Promise<void>;
104
+ /** `gh pr view <n> --json state,mergeStateStatus,reviewDecision,statusCheckRollup,title` */
105
+ prView(num: number, opts?: {
106
+ repo?: string;
107
+ }): Promise<PrView>;
108
+ /** `gh pr list --head <branch> --state open --json number,title` */
109
+ prListByHead(branch: string): Promise<ReadonlyArray<{
110
+ number: number;
111
+ title: string;
112
+ }>>;
113
+ }
114
+ export interface GitClient {
115
+ /** `git rev-parse --abbrev-ref HEAD` — returns "HEAD" on detached. */
116
+ currentBranch(): Promise<string>;
117
+ /** True iff `git diff --quiet` OR `git diff --cached --quiet` returns non-zero. */
118
+ isDirty(): Promise<boolean>;
119
+ }
120
+ export interface Clock {
121
+ /** Epoch seconds (Unix). */
122
+ nowEpoch(): number;
123
+ /** YYYY-MM-DD in UTC. */
124
+ todayYmd(): string;
125
+ }
126
+ //# sourceMappingURL=ports.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ports.d.ts","sourceRoot":"","sources":["../../src/handoff/ports.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,34 @@
1
+ // packages/cli/src/handoff/ports.ts
2
+ //
3
+ // Hexagonal ports: GhClient + GitClient + Clock. All side-effecting verbs
4
+ // take these as parameters; tests inject queue-based fakes (see
5
+ // tests/handoff/fixtures/stubs/), production uses real-clients.ts which
6
+ // spawns gh/git.
7
+ //
8
+ // The GhClient surface is fine-grained per gh idiom (one method per gh
9
+ // verb pattern the bash uses) so the test fake can ordinal-key responses
10
+ // per-method, mirroring bash's _bump_counter + STUB_ISSUE_BODY_N seam.
11
+ // The verb impl is constrained to call these in the SAME ORDER the bash
12
+ // did — drift/race tests assert that order (see advisor finding #2).
13
+ //
14
+ // HandoffError is the SINGLE class identity used across every v2 throw
15
+ // site (verbs + ports + clients). Defined here (the lowest leaf in the
16
+ // handoff module graph) so the new public surface (./index.js, Task 23)
17
+ // can re-export from here without a cycle. The v1 monolith used to own
18
+ // this class — Task 22 inverted the re-export when the v1 index.ts was
19
+ // deleted, so this is now the canonical definition.
20
+ /**
21
+ * Raised by the core for every operator-facing refusal/abort (the bash
22
+ * `die`). Carries an optional `savedNotePath` so the CLI can echo where a
23
+ * composed note was preserved when a push/gate blocked it (Decision D5 —
24
+ * the reasoning is the precious artifact and is never discarded).
25
+ */
26
+ export class HandoffError extends Error {
27
+ savedNotePath;
28
+ constructor(message, savedNotePath) {
29
+ super(message);
30
+ this.name = "HandoffError";
31
+ this.savedNotePath = savedNotePath;
32
+ }
33
+ }
34
+ //# sourceMappingURL=ports.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ports.js","sourceRoot":"","sources":["../../src/handoff/ports.ts"],"names":[],"mappings":"AAAA,oCAAoC;AACpC,EAAE;AACF,0EAA0E;AAC1E,gEAAgE;AAChE,wEAAwE;AACxE,iBAAiB;AACjB,EAAE;AACF,uEAAuE;AACvE,yEAAyE;AACzE,uEAAuE;AACvE,wEAAwE;AACxE,qEAAqE;AACrE,EAAE;AACF,uEAAuE;AACvE,uEAAuE;AACvE,wEAAwE;AACxE,uEAAuE;AACvE,uEAAuE;AACvE,oDAAoD;AAEpD;;;;;GAKG;AACH,MAAM,OAAO,YAAa,SAAQ,KAAK;IAC5B,aAAa,CAAqB;IAC3C,YAAY,OAAe,EAAE,aAAsB;QACjD,KAAK,CAAC,OAAO,CAAC,CAAC;QACf,IAAI,CAAC,IAAI,GAAG,cAAc,CAAC;QAC3B,IAAI,CAAC,aAAa,GAAG,aAAa,CAAC;IACrC,CAAC;CACF"}
@@ -0,0 +1,43 @@
1
+ import type { Clock, GhClient, GitClient, IssueCreated, IssueListItem, IssueView, IssueViewSlim, PrView } from "./ports.js";
2
+ export declare class SpawnGhClient implements GhClient {
3
+ authStatus(): Promise<void>;
4
+ apiUserLogin(): Promise<string>;
5
+ ensureHandoffLabel(): Promise<void>;
6
+ issueView(num: number, opts?: {
7
+ repo?: string;
8
+ }): Promise<IssueView>;
9
+ issueViewSlim(num: number, opts?: {
10
+ repo?: string;
11
+ }): Promise<IssueViewSlim>;
12
+ issueList(opts: {
13
+ state: "open" | "closed";
14
+ assignee?: "@me" | string;
15
+ search?: string;
16
+ }): Promise<readonly IssueListItem[]>;
17
+ issueCreate(opts: {
18
+ title: string;
19
+ bodyMd: string;
20
+ label: string;
21
+ }): Promise<IssueCreated>;
22
+ issueEditBody(num: number, bodyMd: string): Promise<void>;
23
+ issueAddLabel(num: number, label: string): Promise<void>;
24
+ issueAssignMe(num: number): Promise<void>;
25
+ issueUnassignMe(num: number): Promise<void>;
26
+ issueClose(num: number): Promise<void>;
27
+ prView(num: number, opts?: {
28
+ repo?: string;
29
+ }): Promise<PrView>;
30
+ prListByHead(branch: string): Promise<ReadonlyArray<{
31
+ number: number;
32
+ title: string;
33
+ }>>;
34
+ }
35
+ export declare class SpawnGitClient implements GitClient {
36
+ currentBranch(): Promise<string>;
37
+ isDirty(): Promise<boolean>;
38
+ }
39
+ export declare class SystemClock implements Clock {
40
+ nowEpoch(): number;
41
+ todayYmd(): string;
42
+ }
43
+ //# sourceMappingURL=real-clients.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"real-clients.d.ts","sourceRoot":"","sources":["../../src/handoff/real-clients.ts"],"names":[],"mappings":"AA6BA,OAAO,KAAK,EACV,KAAK,EACL,QAAQ,EACR,SAAS,EACT,YAAY,EACZ,aAAa,EACb,SAAS,EACT,aAAa,EACb,MAAM,EACP,MAAM,YAAY,CAAC;AAuGpB,qBAAa,aAAc,YAAW,QAAQ;IACtC,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC;IAS3B,YAAY,IAAI,OAAO,CAAC,MAAM,CAAC;IAc/B,kBAAkB,IAAI,OAAO,CAAC,IAAI,CAAC;IAanC,SAAS,CACb,GAAG,EAAE,MAAM,EACX,IAAI,GAAE;QAAE,IAAI,CAAC,EAAE,MAAM,CAAA;KAAO,GAC3B,OAAO,CAAC,SAAS,CAAC;IAWf,aAAa,CACjB,GAAG,EAAE,MAAM,EACX,IAAI,GAAE;QAAE,IAAI,CAAC,EAAE,MAAM,CAAA;KAAO,GAC3B,OAAO,CAAC,aAAa,CAAC;IAQnB,SAAS,CAAC,IAAI,EAAE;QACpB,KAAK,EAAE,MAAM,GAAG,QAAQ,CAAC;QACzB,QAAQ,CAAC,EAAE,KAAK,GAAG,MAAM,CAAC;QAC1B,MAAM,CAAC,EAAE,MAAM,CAAC;KACjB,GAAG,OAAO,CAAC,SAAS,aAAa,EAAE,CAAC;IAmB/B,WAAW,CAAC,IAAI,EAAE;QACtB,KAAK,EAAE,MAAM,CAAC;QACd,MAAM,EAAE,MAAM,CAAC;QACf,KAAK,EAAE,MAAM,CAAC;KACf,GAAG,OAAO,CAAC,YAAY,CAAC;IAiCnB,aAAa,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAUzD,aAAa,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAOxD,aAAa,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAOzC,eAAe,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IA2B3C,UAAU,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAOtC,MAAM,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,GAAE;QAAE,IAAI,CAAC,EAAE,MAAM,CAAA;KAAO,GAAG,OAAO,CAAC,MAAM,CAAC;IAWlE,YAAY,CAChB,MAAM,EAAE,MAAM,GACb,OAAO,CAAC,aAAa,CAAC;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;CAgB7D;AAMD,qBAAa,cAAe,YAAW,SAAS;IACxC,aAAa,IAAI,OAAO,CAAC,MAAM,CAAC;IAQhC,OAAO,IAAI,OAAO,CAAC,OAAO,CAAC;CAMlC;AAMD,qBAAa,WAAY,YAAW,KAAK;IACvC,QAAQ,IAAI,MAAM;IAIlB,QAAQ,IAAI,MAAM;CAOnB"}
@@ -0,0 +1,291 @@
1
+ // packages/cli/src/handoff/real-clients.ts
2
+ //
3
+ // Production spawn-based implementations of the GhClient + GitClient + Clock
4
+ // ports. Tests use the queue-based FakeGhClient/FakeGitClient/FixedClock
5
+ // from tests/handoff/fixtures/stubs/ (Task 8) — these clients are exercised
6
+ // end-to-end by the verb tests (Tasks 17-20) and live-smoke (Task 32).
7
+ //
8
+ // The spawn pattern mirrors the Cycle 8 v1 defaultExec in index.ts:50-120,
9
+ // consolidated here as the home for the production clients. Two deliberate
10
+ // divergences from v1:
11
+ //
12
+ // 1. v1 resolves {code:127} on ENOENT and a separate requireTools() layer
13
+ // translates 127 → HandoffError. The standalone clients here throw
14
+ // HandoffError DIRECTLY from exec() on ENOENT — there is no
15
+ // requireTools wrapper in the hexagonal seam. Consequence:
16
+ // currentBranch/isDirty/issueUnassignMe still swallow non-zero exit
17
+ // codes (their intended behavior), but throw if gh/git is entirely
18
+ // absent — which is the correct distinction (missing binary ≠ "clean
19
+ // tree"/"not assigned"). v1 is deleted in Task 22.
20
+ // 2. The bash-side issueUnassignMe was idempotent on "not assigned"
21
+ // stderr (handoff.sh, the `*"not assigned"*` case). Mirrored here.
22
+ //
23
+ // HandoffError is imported as a VALUE (since we throw it); everything else
24
+ // is type-only. ports.ts re-exports HandoffError from ./index.js for the
25
+ // Tasks 11-22 window — Task 22 inverts that to put the class here in ports.
26
+ import { spawn } from "node:child_process";
27
+ import { HandoffError } from "./ports.js";
28
+ const MAX_BUFFER = 64 * 1024 * 1024;
29
+ /**
30
+ * Spawn helper. Resolves with {code, stdout, stderr} regardless of process
31
+ * exit status. Rejects ONLY on spawn-time errors — and an ENOENT (binary
32
+ * not on PATH) is translated into a HandoffError with an actionable
33
+ * "install …" message so the operator sees the fix path, not a raw Node
34
+ * error.
35
+ *
36
+ * SECURITY: this uses `child_process.spawn` with an argument-array form
37
+ * (no shell, no string interpolation). NOT `child_process.exec` — the
38
+ * local helper is named `exec` because it returns an ExecResult, not
39
+ * because it shells out. There is no shell process; arguments go
40
+ * directly to the child binary, so shell metacharacters in user input
41
+ * are inert. The bin name itself is a fixed literal ("gh"/"git").
42
+ *
43
+ * `spawn` (not `execFile`/`promisify`) is mandatory: some calls pipe a
44
+ * body to `--body-file -` on the child's STDIN. promisify(execFile)'s
45
+ * `input` option is silently dropped (it's exec-only), which would send
46
+ * EMPTY input and hang the child waiting on EOF.
47
+ */
48
+ function exec(bin, args, options = {}) {
49
+ return new Promise((resolve, reject) => {
50
+ const child = spawn(bin, [...args], {
51
+ stdio: [options.input !== undefined ? "pipe" : "ignore", "pipe", "pipe"],
52
+ });
53
+ let stdout = "";
54
+ let stderr = "";
55
+ let overflow = false;
56
+ child.stdout?.on("data", (chunk) => {
57
+ if (stdout.length < MAX_BUFFER)
58
+ stdout += chunk.toString("utf8");
59
+ else
60
+ overflow = true;
61
+ });
62
+ child.stderr?.on("data", (chunk) => {
63
+ if (stderr.length < MAX_BUFFER)
64
+ stderr += chunk.toString("utf8");
65
+ });
66
+ child.on("error", (err) => {
67
+ const nodeErr = err;
68
+ if (nodeErr.code === "ENOENT") {
69
+ reject(new HandoffError(bin === "gh"
70
+ ? "gh not found — install GitHub CLI and run 'gh auth login'."
71
+ : `${bin} not found — install ${bin}.`));
72
+ return;
73
+ }
74
+ reject(err);
75
+ });
76
+ child.on("close", (code) => {
77
+ resolve({
78
+ code: overflow ? 1 : (code ?? 1),
79
+ stdout,
80
+ stderr,
81
+ });
82
+ });
83
+ if (options.input !== undefined && child.stdin) {
84
+ child.stdin.on("error", () => {
85
+ // EPIPE if the child exits before reading stdin — non-fatal; the
86
+ // close handler reports the real exit code.
87
+ });
88
+ child.stdin.end(options.input);
89
+ }
90
+ });
91
+ }
92
+ function gh(args, options = {}) {
93
+ return exec("gh", args, options);
94
+ }
95
+ function git(args) {
96
+ return exec("git", args);
97
+ }
98
+ /** Throw HandoffError with stderr text on non-zero exit; return on ok. */
99
+ function expectOk(result, context) {
100
+ if (result.code !== 0) {
101
+ const stderr = result.stderr.trim() || "(no stderr)";
102
+ throw new HandoffError(`${context} failed (exit ${result.code}): ${stderr}`);
103
+ }
104
+ return result;
105
+ }
106
+ // ---------------------------------------------------------------------------
107
+ // SpawnGhClient — 14 methods covering every gh verb pattern the bash uses.
108
+ // ---------------------------------------------------------------------------
109
+ export class SpawnGhClient {
110
+ async authStatus() {
111
+ const r = await gh(["auth", "status"]);
112
+ if (r.code !== 0) {
113
+ throw new HandoffError("gh not authenticated — run 'gh auth login'.");
114
+ }
115
+ }
116
+ async apiUserLogin() {
117
+ const r = expectOk(await gh(["api", "user", "--jq", ".login"]), "gh api user");
118
+ const login = r.stdout.trim();
119
+ if (!login) {
120
+ throw new HandoffError("could not determine @me's login (gh api user returned empty) — run 'gh auth status'.");
121
+ }
122
+ return login;
123
+ }
124
+ async ensureHandoffLabel() {
125
+ // Idempotent: never fatal. gh returns non-zero if the label exists.
126
+ await gh([
127
+ "label",
128
+ "create",
129
+ "handoff",
130
+ "--description",
131
+ "Agent handoff (Cycle 12 protocol)",
132
+ "--color",
133
+ "cccccc",
134
+ ]);
135
+ }
136
+ async issueView(num, opts = {}) {
137
+ const args = ["issue", "view", String(num)];
138
+ if (opts.repo)
139
+ args.push("--repo", opts.repo);
140
+ args.push("--json", "number,title,body,state,assignees,labels,updatedAt,closedAt");
141
+ const r = expectOk(await gh(args), `gh issue view ${num}`);
142
+ return JSON.parse(r.stdout);
143
+ }
144
+ async issueViewSlim(num, opts = {}) {
145
+ const args = ["issue", "view", String(num)];
146
+ if (opts.repo)
147
+ args.push("--repo", opts.repo);
148
+ args.push("--json", "state,assignees,updatedAt");
149
+ const r = expectOk(await gh(args), `gh issue view ${num} (slim)`);
150
+ return JSON.parse(r.stdout);
151
+ }
152
+ async issueList(opts) {
153
+ const args = [
154
+ "issue",
155
+ "list",
156
+ "--label",
157
+ "handoff",
158
+ "--state",
159
+ opts.state,
160
+ ];
161
+ if (opts.assignee)
162
+ args.push("--assignee", opts.assignee);
163
+ if (opts.search)
164
+ args.push("--search", opts.search);
165
+ args.push("--json", "number,title,assignees,body,createdAt,updatedAt,closedAt");
166
+ const r = expectOk(await gh(args), "gh issue list");
167
+ return JSON.parse(r.stdout);
168
+ }
169
+ async issueCreate(opts) {
170
+ const args = [
171
+ "issue",
172
+ "create",
173
+ "--title",
174
+ opts.title,
175
+ "--label",
176
+ opts.label,
177
+ "--body-file",
178
+ "-",
179
+ ];
180
+ const r = await gh(args, { input: opts.bodyMd });
181
+ if (r.code !== 0) {
182
+ throw new HandoffError(`gh issue create failed (exit ${r.code}): ${r.stderr.trim() || r.stdout.trim() || "(no output)"}`);
183
+ }
184
+ // gh issue create prints the URL on stdout (last non-empty line).
185
+ const lines = r.stdout
186
+ .trim()
187
+ .split("\n")
188
+ .map((line) => line.trim())
189
+ .filter((line) => line.length > 0);
190
+ const url = lines[lines.length - 1] ?? "";
191
+ const match = url.match(/\/issues\/(\d+)\b/);
192
+ if (!match) {
193
+ throw new HandoffError(`could not parse issue number from \`gh issue create\` output: ${r.stdout.trim()}`);
194
+ }
195
+ return { number: Number(match[1]), url };
196
+ }
197
+ async issueEditBody(num, bodyMd) {
198
+ const args = ["issue", "edit", String(num), "--body-file", "-"];
199
+ const r = await gh(args, { input: bodyMd });
200
+ if (r.code !== 0) {
201
+ throw new HandoffError(`gh issue edit ${num} --body-file failed (exit ${r.code}): ${r.stderr.trim() || "(no stderr)"}`);
202
+ }
203
+ }
204
+ async issueAddLabel(num, label) {
205
+ expectOk(await gh(["issue", "edit", String(num), "--add-label", label]), `gh issue edit ${num} --add-label ${label}`);
206
+ }
207
+ async issueAssignMe(num) {
208
+ expectOk(await gh(["issue", "edit", String(num), "--add-assignee", "@me"]), `gh issue edit ${num} --add-assignee @me`);
209
+ }
210
+ async issueUnassignMe(num) {
211
+ // Bash parity: handoff.sh treats "not assigned" stderr as idempotent
212
+ // success (same case-arm in the shell version). Mirror that here so
213
+ // /handoff on a PR that's no longer assigned to @me is a no-op rather
214
+ // than an error.
215
+ const r = await gh([
216
+ "issue",
217
+ "edit",
218
+ String(num),
219
+ "--remove-assignee",
220
+ "@me",
221
+ ]);
222
+ if (r.code !== 0) {
223
+ const stderr = r.stderr;
224
+ if (/not assigned/i.test(stderr) ||
225
+ /could not assign/i.test(stderr) ||
226
+ /cannot remove/i.test(stderr)) {
227
+ return; // idempotent no-op
228
+ }
229
+ throw new HandoffError(`gh issue edit ${num} --remove-assignee @me failed: ${stderr.trim() || "(no stderr)"}`);
230
+ }
231
+ }
232
+ async issueClose(num) {
233
+ expectOk(await gh(["issue", "close", String(num)]), `gh issue close ${num}`);
234
+ }
235
+ async prView(num, opts = {}) {
236
+ const args = ["pr", "view", String(num)];
237
+ if (opts.repo)
238
+ args.push("--repo", opts.repo);
239
+ args.push("--json", "state,mergeStateStatus,reviewDecision,statusCheckRollup,title");
240
+ const r = expectOk(await gh(args), `gh pr view ${num}`);
241
+ return JSON.parse(r.stdout);
242
+ }
243
+ async prListByHead(branch) {
244
+ const r = expectOk(await gh([
245
+ "pr",
246
+ "list",
247
+ "--head",
248
+ branch,
249
+ "--state",
250
+ "open",
251
+ "--json",
252
+ "number,title",
253
+ ]), `gh pr list --head ${branch}`);
254
+ return JSON.parse(r.stdout);
255
+ }
256
+ }
257
+ // ---------------------------------------------------------------------------
258
+ // SpawnGitClient — currentBranch + isDirty.
259
+ // ---------------------------------------------------------------------------
260
+ export class SpawnGitClient {
261
+ async currentBranch() {
262
+ const r = await git(["rev-parse", "--abbrev-ref", "HEAD"]);
263
+ // Non-zero (e.g., not in a repo) → fall back to "HEAD" so callers
264
+ // get a stable string rather than an exception. ENOENT (git missing)
265
+ // still throws from exec().
266
+ return r.code === 0 ? r.stdout.trim() || "HEAD" : "HEAD";
267
+ }
268
+ async isDirty() {
269
+ const unstaged = await git(["diff", "--quiet"]);
270
+ if (unstaged.code !== 0)
271
+ return true;
272
+ const staged = await git(["diff", "--cached", "--quiet"]);
273
+ return staged.code !== 0;
274
+ }
275
+ }
276
+ // ---------------------------------------------------------------------------
277
+ // SystemClock — wall clock for format_age + create-title date.
278
+ // ---------------------------------------------------------------------------
279
+ export class SystemClock {
280
+ nowEpoch() {
281
+ return Math.floor(Date.now() / 1000);
282
+ }
283
+ todayYmd() {
284
+ const d = new Date();
285
+ const y = d.getUTCFullYear();
286
+ const m = String(d.getUTCMonth() + 1).padStart(2, "0");
287
+ const day = String(d.getUTCDate()).padStart(2, "0");
288
+ return `${y}-${m}-${day}`;
289
+ }
290
+ }
291
+ //# sourceMappingURL=real-clients.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"real-clients.js","sourceRoot":"","sources":["../../src/handoff/real-clients.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,55 @@
1
+ import { type GhClient } from "./ports.js";
2
+ export interface LinkedItemDerivation {
3
+ readonly kind: "pr" | "issue" | "?";
4
+ readonly display: string;
5
+ readonly title: string;
6
+ readonly state: "OPEN" | "CLOSED" | "MERGED" | "UNREACHABLE";
7
+ /** Human annotation: "", "(merged)", "(closed)",
8
+ * "(unreachable: gh pr view failed)" / "(unreachable: gh issue view failed)",
9
+ * "[open]", "[open, assigned X]", "(unknown link type)". */
10
+ readonly annotation: string;
11
+ /** Present for OPEN PRs only — copy-pastable `gh pr checkout N [--repo owner/repo]`. */
12
+ readonly checkoutHint?: string;
13
+ /** Extra fields for OPEN PRs (mergeStateStatus/reviewDecision/checksSummary)
14
+ * or OPEN issues (assigneesCsv). Absent for MERGED/CLOSED/UNREACHABLE. */
15
+ readonly extra?: {
16
+ readonly mergeStateStatus?: string;
17
+ readonly reviewDecision?: string;
18
+ readonly checksSummary?: string;
19
+ readonly assigneesCsv?: string;
20
+ };
21
+ }
22
+ export interface RehydrateData {
23
+ readonly issueNumber: number;
24
+ /** Stripped of control chars (operator-editable title — ANSI defense). */
25
+ readonly title: string;
26
+ /** Value only — render adds the `state: ` prefix and indentation.
27
+ * One of:
28
+ * "open (unassigned — on the stack)"
29
+ * "open (assigned X[,Y...])"
30
+ * "closed (accepted YYYY-MM-DD)"
31
+ * "closed"
32
+ */
33
+ readonly stateLine: string;
34
+ readonly linkedItems: readonly LinkedItemDerivation[];
35
+ /** Count of items with state === "UNREACHABLE" (strict-mode trigger). */
36
+ readonly linkFailures: number;
37
+ /** Last marker block from `view.body`, INCLUDING the marker tokens.
38
+ * `null` if no well-formed block found. Control chars NOT yet stripped —
39
+ * the renderer strips them at print time (matches bash). */
40
+ readonly note: string | null;
41
+ }
42
+ /**
43
+ * Derive structured live state for handoff issue #N. Used by /accept (strict),
44
+ * /rehydrate (lenient), and df_rehydrate (MCP structuredContent).
45
+ *
46
+ * Linked items are processed SEQUENTIALLY (bash parity + FakeGhClient ordinal
47
+ * slot stability). The MAIN issue is the FIRST `issueView` call; any linked
48
+ * issues that follow share that counter, so test slots are 1 = handoff, 2+ =
49
+ * linked issues.
50
+ *
51
+ * @throws HandoffError if `gh.issueView(issueNum)` fails — the live-state
52
+ * anchor is required; don't proceed on the note alone.
53
+ */
54
+ export declare function deriveRehydrateData(issueNum: number, gh: GhClient): Promise<RehydrateData>;
55
+ //# sourceMappingURL=rehydrate-core.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rehydrate-core.d.ts","sourceRoot":"","sources":["../../src/handoff/rehydrate-core.ts"],"names":[],"mappings":"AAkCA,OAAO,EAAgB,KAAK,QAAQ,EAAe,MAAM,YAAY,CAAC;AAGtE,MAAM,WAAW,oBAAoB;IACnC,QAAQ,CAAC,IAAI,EAAE,IAAI,GAAG,OAAO,GAAG,GAAG,CAAC;IACpC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,KAAK,EAAE,MAAM,GAAG,QAAQ,GAAG,QAAQ,GAAG,aAAa,CAAC;IAC7D;;gEAE4D;IAC5D,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,wFAAwF;IACxF,QAAQ,CAAC,YAAY,CAAC,EAAE,MAAM,CAAC;IAC/B;8EAC0E;IAC1E,QAAQ,CAAC,KAAK,CAAC,EAAE;QACf,QAAQ,CAAC,gBAAgB,CAAC,EAAE,MAAM,CAAC;QACnC,QAAQ,CAAC,cAAc,CAAC,EAAE,MAAM,CAAC;QACjC,QAAQ,CAAC,aAAa,CAAC,EAAE,MAAM,CAAC;QAChC,QAAQ,CAAC,YAAY,CAAC,EAAE,MAAM,CAAC;KAChC,CAAC;CACH;AAED,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,0EAA0E;IAC1E,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB;;;;;;OAMG;IACH,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,WAAW,EAAE,SAAS,oBAAoB,EAAE,CAAC;IACtD,yEAAyE;IACzE,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;IAC9B;;gEAE4D;IAC5D,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;CAC9B;AAqJD;;;;;;;;;;;GAWG;AACH,wBAAsB,mBAAmB,CACvC,QAAQ,EAAE,MAAM,EAChB,EAAE,EAAE,QAAQ,GACX,OAAO,CAAC,aAAa,CAAC,CA0DxB"}