@cluesmith/codev 3.3.3 → 3.3.5
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/dashboard-dist/assets/index-BuflRUi6.js +220 -0
- package/dashboard-dist/assets/index-BuflRUi6.js.map +1 -0
- package/dashboard-dist/assets/index-r3bZzLmX.css +32 -0
- package/dashboard-dist/index.html +2 -2
- package/dist/agent-farm/cli.d.ts.map +1 -1
- package/dist/agent-farm/cli.js +48 -0
- package/dist/agent-farm/cli.js.map +1 -1
- package/dist/agent-farm/commands/ide.d.ts +20 -0
- package/dist/agent-farm/commands/ide.d.ts.map +1 -0
- package/dist/agent-farm/commands/ide.js +138 -0
- package/dist/agent-farm/commands/ide.js.map +1 -0
- package/dist/agent-farm/commands/inbox.d.ts.map +1 -1
- package/dist/agent-farm/commands/inbox.js +15 -3
- package/dist/agent-farm/commands/inbox.js.map +1 -1
- package/dist/agent-farm/commands/send.d.ts +3 -2
- package/dist/agent-farm/commands/send.d.ts.map +1 -1
- package/dist/agent-farm/commands/send.js +40 -6
- package/dist/agent-farm/commands/send.js.map +1 -1
- package/dist/agent-farm/commands/spawn-worktree.d.ts.map +1 -1
- package/dist/agent-farm/commands/spawn-worktree.js +28 -9
- package/dist/agent-farm/commands/spawn-worktree.js.map +1 -1
- package/dist/agent-farm/commands/tower.d.ts +21 -9
- package/dist/agent-farm/commands/tower.d.ts.map +1 -1
- package/dist/agent-farm/commands/tower.js +90 -30
- package/dist/agent-farm/commands/tower.js.map +1 -1
- package/dist/agent-farm/db/tower-owner.d.ts +168 -0
- package/dist/agent-farm/db/tower-owner.d.ts.map +1 -0
- package/dist/agent-farm/db/tower-owner.js +369 -0
- package/dist/agent-farm/db/tower-owner.js.map +1 -0
- package/dist/agent-farm/db/types.d.ts +30 -5
- package/dist/agent-farm/db/types.d.ts.map +1 -1
- package/dist/agent-farm/db/types.js.map +1 -1
- package/dist/agent-farm/lib/ide-record.d.ts +65 -0
- package/dist/agent-farm/lib/ide-record.d.ts.map +1 -0
- package/dist/agent-farm/lib/ide-record.js +118 -0
- package/dist/agent-farm/lib/ide-record.js.map +1 -0
- package/dist/agent-farm/lib/tunnel-client.d.ts +22 -0
- package/dist/agent-farm/lib/tunnel-client.d.ts.map +1 -1
- package/dist/agent-farm/lib/tunnel-client.js +53 -5
- package/dist/agent-farm/lib/tunnel-client.js.map +1 -1
- package/dist/agent-farm/servers/decisions/file.d.ts +50 -0
- package/dist/agent-farm/servers/decisions/file.d.ts.map +1 -0
- package/dist/agent-farm/servers/decisions/file.js +146 -0
- package/dist/agent-farm/servers/decisions/file.js.map +1 -0
- package/dist/agent-farm/servers/decisions/grammar.d.ts +36 -0
- package/dist/agent-farm/servers/decisions/grammar.d.ts.map +1 -0
- package/dist/agent-farm/servers/decisions/grammar.js +109 -0
- package/dist/agent-farm/servers/decisions/grammar.js.map +1 -0
- package/dist/agent-farm/servers/decisions/notice.d.ts +11 -0
- package/dist/agent-farm/servers/decisions/notice.d.ts.map +1 -0
- package/dist/agent-farm/servers/decisions/notice.js +18 -0
- package/dist/agent-farm/servers/decisions/notice.js.map +1 -0
- package/dist/agent-farm/servers/decisions/parse.d.ts +36 -0
- package/dist/agent-farm/servers/decisions/parse.d.ts.map +1 -0
- package/dist/agent-farm/servers/decisions/parse.js +308 -0
- package/dist/agent-farm/servers/decisions/parse.js.map +1 -0
- package/dist/agent-farm/servers/decisions/routes.d.ts +17 -0
- package/dist/agent-farm/servers/decisions/routes.d.ts.map +1 -0
- package/dist/agent-farm/servers/decisions/routes.js +160 -0
- package/dist/agent-farm/servers/decisions/routes.js.map +1 -0
- package/dist/agent-farm/servers/decisions/splice.d.ts +26 -0
- package/dist/agent-farm/servers/decisions/splice.d.ts.map +1 -0
- package/dist/agent-farm/servers/decisions/splice.js +120 -0
- package/dist/agent-farm/servers/decisions/splice.js.map +1 -0
- package/dist/agent-farm/servers/decisions/stamp.d.ts +4 -0
- package/dist/agent-farm/servers/decisions/stamp.d.ts.map +1 -0
- package/dist/agent-farm/servers/decisions/stamp.js +171 -0
- package/dist/agent-farm/servers/decisions/stamp.js.map +1 -0
- package/dist/agent-farm/servers/gate-profiles.d.ts +23 -2
- package/dist/agent-farm/servers/gate-profiles.d.ts.map +1 -1
- package/dist/agent-farm/servers/gate-profiles.js +43 -2
- package/dist/agent-farm/servers/gate-profiles.js.map +1 -1
- package/dist/agent-farm/servers/ide-forward.d.ts +62 -0
- package/dist/agent-farm/servers/ide-forward.d.ts.map +1 -0
- package/dist/agent-farm/servers/ide-forward.js +274 -0
- package/dist/agent-farm/servers/ide-forward.js.map +1 -0
- package/dist/agent-farm/servers/ide-server.d.ts +68 -0
- package/dist/agent-farm/servers/ide-server.d.ts.map +1 -0
- package/dist/agent-farm/servers/ide-server.js +281 -0
- package/dist/agent-farm/servers/ide-server.js.map +1 -0
- package/dist/agent-farm/servers/mailbox-delivery.d.ts +213 -4
- package/dist/agent-farm/servers/mailbox-delivery.d.ts.map +1 -1
- package/dist/agent-farm/servers/mailbox-delivery.js +489 -68
- package/dist/agent-farm/servers/mailbox-delivery.js.map +1 -1
- package/dist/agent-farm/servers/mailbox-wiring.d.ts +18 -4
- package/dist/agent-farm/servers/mailbox-wiring.d.ts.map +1 -1
- package/dist/agent-farm/servers/mailbox-wiring.js +94 -21
- package/dist/agent-farm/servers/mailbox-wiring.js.map +1 -1
- package/dist/agent-farm/servers/message-write.d.ts +138 -11
- package/dist/agent-farm/servers/message-write.d.ts.map +1 -1
- package/dist/agent-farm/servers/message-write.js +144 -35
- package/dist/agent-farm/servers/message-write.js.map +1 -1
- package/dist/agent-farm/servers/overview.d.ts +32 -15
- package/dist/agent-farm/servers/overview.d.ts.map +1 -1
- package/dist/agent-farm/servers/overview.js +181 -103
- package/dist/agent-farm/servers/overview.js.map +1 -1
- package/dist/agent-farm/servers/render-gate.d.ts +53 -0
- package/dist/agent-farm/servers/render-gate.d.ts.map +1 -1
- package/dist/agent-farm/servers/render-gate.js +135 -24
- package/dist/agent-farm/servers/render-gate.js.map +1 -1
- package/dist/agent-farm/servers/session-submit.d.ts +7 -4
- package/dist/agent-farm/servers/session-submit.d.ts.map +1 -1
- package/dist/agent-farm/servers/session-submit.js +7 -4
- package/dist/agent-farm/servers/session-submit.js.map +1 -1
- package/dist/agent-farm/servers/tower-cron.d.ts.map +1 -1
- package/dist/agent-farm/servers/tower-cron.js +4 -1
- package/dist/agent-farm/servers/tower-cron.js.map +1 -1
- package/dist/agent-farm/servers/tower-instances.d.ts.map +1 -1
- package/dist/agent-farm/servers/tower-instances.js +6 -7
- package/dist/agent-farm/servers/tower-instances.js.map +1 -1
- package/dist/agent-farm/servers/tower-routes.d.ts.map +1 -1
- package/dist/agent-farm/servers/tower-routes.js +116 -18
- package/dist/agent-farm/servers/tower-routes.js.map +1 -1
- package/dist/agent-farm/servers/tower-server.js +64 -7
- package/dist/agent-farm/servers/tower-server.js.map +1 -1
- package/dist/agent-farm/servers/tower-terminals.d.ts.map +1 -1
- package/dist/agent-farm/servers/tower-terminals.js +53 -8
- package/dist/agent-farm/servers/tower-terminals.js.map +1 -1
- package/dist/agent-farm/servers/tower-websocket.d.ts.map +1 -1
- package/dist/agent-farm/servers/tower-websocket.js +18 -1
- package/dist/agent-farm/servers/tower-websocket.js.map +1 -1
- package/dist/agent-farm/utils/message-format.d.ts +8 -0
- package/dist/agent-farm/utils/message-format.d.ts.map +1 -1
- package/dist/agent-farm/utils/message-format.js +21 -6
- package/dist/agent-farm/utils/message-format.js.map +1 -1
- package/dist/agent-farm/utils/port.d.ts +22 -0
- package/dist/agent-farm/utils/port.d.ts.map +1 -0
- package/dist/agent-farm/utils/port.js +35 -0
- package/dist/agent-farm/utils/port.js.map +1 -0
- package/dist/agent-farm/utils/server-utils.d.ts +26 -0
- package/dist/agent-farm/utils/server-utils.d.ts.map +1 -1
- package/dist/agent-farm/utils/server-utils.js +36 -0
- package/dist/agent-farm/utils/server-utils.js.map +1 -1
- package/dist/cli.js +2 -2
- package/dist/cli.js.map +1 -1
- package/dist/commands/consult/index.d.ts +20 -1
- package/dist/commands/consult/index.d.ts.map +1 -1
- package/dist/commands/consult/index.js +117 -3
- package/dist/commands/consult/index.js.map +1 -1
- package/dist/commands/consult/tree-tripwire.d.ts +177 -0
- package/dist/commands/consult/tree-tripwire.d.ts.map +1 -0
- package/dist/commands/consult/tree-tripwire.js +356 -0
- package/dist/commands/consult/tree-tripwire.js.map +1 -0
- package/dist/commands/doctor.d.ts +69 -0
- package/dist/commands/doctor.d.ts.map +1 -1
- package/dist/commands/doctor.js +163 -0
- package/dist/commands/doctor.js.map +1 -1
- package/dist/commands/generate-image.d.ts +5 -0
- package/dist/commands/generate-image.d.ts.map +1 -1
- package/dist/commands/generate-image.js +225 -4
- package/dist/commands/generate-image.js.map +1 -1
- package/dist/commands/porch/checks.d.ts +7 -1
- package/dist/commands/porch/checks.d.ts.map +1 -1
- package/dist/commands/porch/checks.js +77 -14
- package/dist/commands/porch/checks.js.map +1 -1
- package/dist/commands/porch/index.d.ts +38 -1
- package/dist/commands/porch/index.d.ts.map +1 -1
- package/dist/commands/porch/index.js +79 -43
- package/dist/commands/porch/index.js.map +1 -1
- package/dist/commands/porch/protocol.d.ts +4 -3
- package/dist/commands/porch/protocol.d.ts.map +1 -1
- package/dist/commands/porch/protocol.js +32 -5
- package/dist/commands/porch/protocol.js.map +1 -1
- package/dist/commands/porch/types.d.ts +2 -0
- package/dist/commands/porch/types.d.ts.map +1 -1
- package/dist/lib/agent-env.d.ts +95 -0
- package/dist/lib/agent-env.d.ts.map +1 -0
- package/dist/lib/agent-env.js +137 -0
- package/dist/lib/agent-env.js.map +1 -0
- package/dist/lib/config.d.ts +8 -0
- package/dist/lib/config.d.ts.map +1 -1
- package/dist/lib/config.js.map +1 -1
- package/dist/lib/forge-contracts.d.ts +10 -0
- package/dist/lib/forge-contracts.d.ts.map +1 -1
- package/dist/lib/forge-contracts.js.map +1 -1
- package/dist/lib/forge-rate-limit.d.ts +45 -0
- package/dist/lib/forge-rate-limit.d.ts.map +1 -0
- package/dist/lib/forge-rate-limit.js +89 -0
- package/dist/lib/forge-rate-limit.js.map +1 -0
- package/dist/lib/forge.d.ts +28 -0
- package/dist/lib/forge.d.ts.map +1 -1
- package/dist/lib/forge.js +63 -10
- package/dist/lib/forge.js.map +1 -1
- package/dist/lib/github.d.ts +13 -1
- package/dist/lib/github.d.ts.map +1 -1
- package/dist/lib/github.js +58 -15
- package/dist/lib/github.js.map +1 -1
- package/dist/lib/test-env.d.ts +17 -0
- package/dist/lib/test-env.d.ts.map +1 -1
- package/dist/lib/test-env.js +20 -0
- package/dist/lib/test-env.js.map +1 -1
- package/dist/terminal/pty-session.d.ts +122 -4
- package/dist/terminal/pty-session.d.ts.map +1 -1
- package/dist/terminal/pty-session.js +142 -7
- package/dist/terminal/pty-session.js.map +1 -1
- package/dist/terminal/session-manager.d.ts +23 -2
- package/dist/terminal/session-manager.d.ts.map +1 -1
- package/dist/terminal/session-manager.js +115 -36
- package/dist/terminal/session-manager.js.map +1 -1
- package/dist/terminal/session-screen.d.ts +34 -5
- package/dist/terminal/session-screen.d.ts.map +1 -1
- package/dist/terminal/session-screen.js +52 -6
- package/dist/terminal/session-screen.js.map +1 -1
- package/dist/terminal/terminal-replies.d.ts +102 -0
- package/dist/terminal/terminal-replies.d.ts.map +1 -0
- package/dist/terminal/terminal-replies.js +181 -0
- package/dist/terminal/terminal-replies.js.map +1 -0
- package/package.json +4 -4
- package/scripts/forge/github/pr-list.sh +6 -2
- package/scripts/forge/github/pr-search.sh +4 -2
- package/scripts/forge/gitlab/issue-search.sh +1 -0
- package/scripts/forge/gitlab/pr-search.sh +35 -2
- package/scripts/forge/linear/auth-status.sh +1 -0
- package/scripts/forge/linear/issue-comment.sh +1 -0
- package/scripts/forge/linear/issue-list.sh +1 -0
- package/scripts/forge/linear/issue-search.sh +1 -0
- package/scripts/forge/linear/issue-view.sh +1 -0
- package/scripts/forge/linear/recently-closed.sh +1 -0
- package/scripts/forge/linear/user-identity.sh +1 -0
- package/skeleton/.claude/skills/afx/SKILL.md +15 -0
- package/skeleton/.claude/skills/arch-init/SKILL.md +56 -9
- package/skeleton/.claude/skills/arch-save/SKILL.md +100 -9
- package/skeleton/.claude/skills/consult/SKILL.md +9 -0
- package/skeleton/.claude/skills/generate-image/SKILL.md +19 -2
- package/skeleton/.claude/skills/porch/SKILL.md +18 -0
- package/skeleton/.codex/skills/afx/SKILL.md +15 -0
- package/skeleton/.codex/skills/arch-init/SKILL.md +56 -9
- package/skeleton/.codex/skills/arch-save/SKILL.md +100 -9
- package/skeleton/.codex/skills/consult/SKILL.md +9 -0
- package/skeleton/.codex/skills/generate-image/SKILL.md +19 -2
- package/skeleton/.codex/skills/porch/SKILL.md +18 -0
- package/skeleton/protocol-schema.json +6 -0
- package/skeleton/protocols/maintain/protocol.md +5 -0
- package/skeleton/protocols/pir/prompts/review.md +0 -1
- package/skeleton/protocols/protocol-schema.json +6 -0
- package/skeleton/resources/commands/agent-farm.md +71 -2
- package/skeleton/resources/commands/consult.md +9 -2
- package/skeleton/roles/consultant.md +32 -0
- package/skeleton/templates/AGENTS.md +1 -1
- package/skeleton/templates/CLAUDE.md +1 -1
- package/skeleton/templates/cheatsheet.md +1 -1
- package/dashboard-dist/assets/index-CUIjS_Vb.js +0 -199
- package/dashboard-dist/assets/index-CUIjS_Vb.js.map +0 -1
- package/dashboard-dist/assets/index-DnYLy5C_.css +0 -32
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Working-tree tripwire for consultation lanes (#1649).
|
|
3
|
+
*
|
|
4
|
+
* A reviewer is supposed to read the tree, never write to it. `consultant.md`
|
|
5
|
+
* now says so explicitly, but instruction is a probabilistic control: the claude
|
|
6
|
+
* lane runs with `permissionMode: 'bypassPermissions'`, so `Edit`/`Write`/`Bash`
|
|
7
|
+
* remain *reachable* even though nothing asks for them. In #1649 a review lane
|
|
8
|
+
* reverted three guards in a builder's worktree to check whether the tests were
|
|
9
|
+
* vacuous, and the only reason anyone found out is that the builder happened to
|
|
10
|
+
* diff against HEAD afterwards.
|
|
11
|
+
*
|
|
12
|
+
* This module is the detection half. It snapshots the tree before a lane runs
|
|
13
|
+
* and again after, and reports what moved. It does not block anything — its job
|
|
14
|
+
* is to turn a silent mutation into a loud one.
|
|
15
|
+
*
|
|
16
|
+
* ## Why not just diff `git status --porcelain` output
|
|
17
|
+
*
|
|
18
|
+
* Because the interesting case is a tree that was *already dirty*. A builder
|
|
19
|
+
* mid-phase has uncommitted work; porcelain reports ` M src/foo.ts` before the
|
|
20
|
+
* review and ` M src/foo.ts` after it, identical strings, while the file's
|
|
21
|
+
* contents changed underneath. So the snapshot pairs each path porcelain
|
|
22
|
+
* reports with a hash of its contents on disk.
|
|
23
|
+
*
|
|
24
|
+
* ## What it cannot see
|
|
25
|
+
*
|
|
26
|
+
* Files git ignores. That is deliberate — `.consult/` and `codev/projects/*/*.txt`
|
|
27
|
+
* are ignored, which is exactly why consult's own review output does not trip
|
|
28
|
+
* its own wire — but it does mean a lane writing into an ignored path goes
|
|
29
|
+
* unnoticed. Naming the tree under review is the goal here, not sandboxing.
|
|
30
|
+
*
|
|
31
|
+
* Edits *inside* a submodule, for the same reason: `git status` reports the
|
|
32
|
+
* submodule as one directory entry, so a changed file within it moves nothing
|
|
33
|
+
* this module can see.
|
|
34
|
+
*
|
|
35
|
+
* And an edit the lane restores byte-for-byte, which by construction leaves a
|
|
36
|
+
* before/after comparison nothing to compare. `consultant.md` is the only
|
|
37
|
+
* control for that case; there is a test asserting this silence, so the limit is
|
|
38
|
+
* pinned rather than assumed.
|
|
39
|
+
*/
|
|
40
|
+
/**
|
|
41
|
+
* Where the repository itself is pointing.
|
|
42
|
+
*
|
|
43
|
+
* The working tree is only half the state a lane can move. `git commit` takes a
|
|
44
|
+
* dirty tree clean, and `git checkout` between two refs with identical content
|
|
45
|
+
* changes nothing a file comparison can see — in both cases the snapshot of
|
|
46
|
+
* *paths* is a poor witness, and in the clean-ref-switch case it is no witness
|
|
47
|
+
* at all. The reviewer is then reviewing a different commit than the builder
|
|
48
|
+
* thinks, which is worse than an edited file because it leaves no local trace.
|
|
49
|
+
*
|
|
50
|
+
* Either field is `null` when git cannot answer — most usefully in a repository
|
|
51
|
+
* with no commits yet, where `rev-parse HEAD` fails and the tree is still worth
|
|
52
|
+
* watching.
|
|
53
|
+
*/
|
|
54
|
+
export interface RepositoryIdentity {
|
|
55
|
+
/** HEAD's object id. */
|
|
56
|
+
head: string | null;
|
|
57
|
+
/** Branch name, or `HEAD` when detached. */
|
|
58
|
+
branch: string | null;
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* A point-in-time view of every path git considers changed, plus where the
|
|
62
|
+
* repository was pointing when it was taken.
|
|
63
|
+
*
|
|
64
|
+
* `available: false` means the tripwire could not run at all — most often
|
|
65
|
+
* because the workspace is not a git repository. Callers say so once on stderr
|
|
66
|
+
* rather than pretending the tree was clean.
|
|
67
|
+
*/
|
|
68
|
+
export interface TreeSnapshot {
|
|
69
|
+
available: boolean;
|
|
70
|
+
/** Reason the snapshot is unavailable; only set when `available` is false. */
|
|
71
|
+
reason?: string;
|
|
72
|
+
/** Repo-relative path → `"<status code>:<content hash>"`. */
|
|
73
|
+
entries: Map<string, string>;
|
|
74
|
+
/** HEAD and branch at the moment of the snapshot. */
|
|
75
|
+
identity: RepositoryIdentity;
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Snapshot the working tree of `workspaceRoot`.
|
|
79
|
+
*
|
|
80
|
+
* `-z` (NUL-separated) rather than plain `--porcelain` because git C-quotes
|
|
81
|
+
* paths containing spaces or non-ASCII in the human-readable form, and a quoted
|
|
82
|
+
* path cannot be handed back to `fs.readFileSync`. `--untracked-files=all` so
|
|
83
|
+
* a newly created file is listed individually instead of being collapsed into
|
|
84
|
+
* a single `?? dir/` entry.
|
|
85
|
+
*
|
|
86
|
+
* Records HEAD and the branch alongside the paths, so a lane that commits or
|
|
87
|
+
* switches refs is caught even when no file ends up looking different.
|
|
88
|
+
*/
|
|
89
|
+
export declare function snapshotTree(workspaceRoot: string): TreeSnapshot;
|
|
90
|
+
/**
|
|
91
|
+
* Resolve a lane's `--output` to a repo-relative path, or null if it lands
|
|
92
|
+
* outside the workspace.
|
|
93
|
+
*
|
|
94
|
+
* `--output` arrives exactly as the user typed it, which may be relative
|
|
95
|
+
* (`consult -o review.txt`), so a raw `startsWith(workspaceRoot)` test misses it
|
|
96
|
+
* and the lane ends up reporting its own review file. Resolving first also kills
|
|
97
|
+
* the opposite error: a plain prefix test counts `/repo-2/x` as living inside
|
|
98
|
+
* `/repo`.
|
|
99
|
+
*
|
|
100
|
+
* The resolution base is **`cwd`, not `workspaceRoot`**, because that is where
|
|
101
|
+
* the file actually lands: every lane writes with a bare
|
|
102
|
+
* `fs.writeFileSync(outputPath, …)`, and `consult` never chdirs. Resolving
|
|
103
|
+
* against the workspace root instead would agree with reality only when the two
|
|
104
|
+
* happen to coincide — run `consult -o review.txt` from a subdirectory and the
|
|
105
|
+
* file is written to `<subdir>/review.txt` while the exclusion names
|
|
106
|
+
* `review.txt`, so the lane reports its own output as a mutation.
|
|
107
|
+
*/
|
|
108
|
+
export declare function relativeOutputPath(workspaceRoot: string, outputPath?: string, cwd?: string): string | null;
|
|
109
|
+
/**
|
|
110
|
+
* Repo-relative paths that differ between two snapshots, sorted.
|
|
111
|
+
*
|
|
112
|
+
* `ignore` takes the extra paths this particular run owns — the lane's review
|
|
113
|
+
* output file, whose location depends on the project and phase. A tripwire that
|
|
114
|
+
* cries wolf about its own output would be trained away within a week.
|
|
115
|
+
*/
|
|
116
|
+
export declare function diffTreeSnapshots(before: TreeSnapshot, after: TreeSnapshot, ignore?: string[]): string[];
|
|
117
|
+
/** One field of the repository's identity that moved during a review. */
|
|
118
|
+
export interface IdentityChange {
|
|
119
|
+
field: 'HEAD' | 'branch';
|
|
120
|
+
before: string;
|
|
121
|
+
after: string;
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* Repository identity fields that differ between two snapshots.
|
|
125
|
+
*
|
|
126
|
+
* A field that was unreadable at either end is skipped rather than reported as
|
|
127
|
+
* a change: "we could not tell" and "it moved" are different claims, and only
|
|
128
|
+
* one of them is evidence.
|
|
129
|
+
*/
|
|
130
|
+
export declare function diffRepositoryIdentity(before: TreeSnapshot, after: TreeSnapshot): IdentityChange[];
|
|
131
|
+
/**
|
|
132
|
+
* Render a string safe to print to a terminal.
|
|
133
|
+
*
|
|
134
|
+
* Git path names are bytes, and almost anything that is not `/` or NUL is legal
|
|
135
|
+
* in one — including newlines and ESC. Printing such a name raw lets it forge
|
|
136
|
+
* the rest of the banner: a file called `\n - harmless.ts` adds a line to the
|
|
137
|
+
* list, and one containing a CSI sequence can repaint or erase what is above it.
|
|
138
|
+
* The warning exists to be trusted, so nothing it prints gets to move the
|
|
139
|
+
* cursor. C0, DEL and C1 are escaped; ordinary Unicode is left alone.
|
|
140
|
+
*/
|
|
141
|
+
export declare function escapeControlChars(value: string): string;
|
|
142
|
+
/**
|
|
143
|
+
* The warning for a repository that moved under the reviewer.
|
|
144
|
+
*
|
|
145
|
+
* Separate from the changed-files banner because it is a different fact with a
|
|
146
|
+
* different remedy: no file need look any different, and `git status` will not
|
|
147
|
+
* show you what happened — `git reflog` will.
|
|
148
|
+
*/
|
|
149
|
+
export declare function formatRepositoryMovedWarning(model: string, changes: IdentityChange[]): string;
|
|
150
|
+
/**
|
|
151
|
+
* The warning text, shared by the stderr banner and the review-output footer.
|
|
152
|
+
*
|
|
153
|
+
* Deliberately says the tree changed *while the lane ran*, not that the lane
|
|
154
|
+
* changed it. Under `cmap` all three lanes run as concurrent processes in one
|
|
155
|
+
* worktree, so each one sees the others' writes; asserting a culprit we cannot
|
|
156
|
+
* identify would be a claim the evidence does not support.
|
|
157
|
+
*/
|
|
158
|
+
export declare function formatTreeChangeWarning(model: string, files: string[]): string;
|
|
159
|
+
/**
|
|
160
|
+
* The warning for a post-review snapshot that could not be taken at all.
|
|
161
|
+
*
|
|
162
|
+
* Distinct from "the tree changed" because the failure is different in kind: we
|
|
163
|
+
* know something happened, and we cannot say what. Reported rather than
|
|
164
|
+
* swallowed — a comparison that silently returns "no changes" when it could not
|
|
165
|
+
* look is the failure mode this whole module exists to avoid.
|
|
166
|
+
*/
|
|
167
|
+
export declare function formatSnapshotLostWarning(model: string, reason?: string): string;
|
|
168
|
+
/**
|
|
169
|
+
* Append the warning to a lane's review output so it survives into the file
|
|
170
|
+
* porch and the builder actually read — stderr scrolls past, review files don't.
|
|
171
|
+
*
|
|
172
|
+
* Written as a plain paragraph with no `VERDICT:` line, so `parseVerdict()`
|
|
173
|
+
* (which scans last→first for that prefix) still finds the reviewer's own
|
|
174
|
+
* verdict above it.
|
|
175
|
+
*/
|
|
176
|
+
export declare function appendTreeChangeWarningToOutput(outputPath: string | undefined, warning: string): void;
|
|
177
|
+
//# sourceMappingURL=tree-tripwire.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tree-tripwire.d.ts","sourceRoot":"","sources":["../../../src/commands/consult/tree-tripwire.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAsCG;AAOH;;;;;;;;;;;;;GAaG;AACH,MAAM,WAAW,kBAAkB;IACjC,wBAAwB;IACxB,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACpB,4CAA4C;IAC5C,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;CACvB;AAED;;;;;;;GAOG;AACH,MAAM,WAAW,YAAY;IAC3B,SAAS,EAAE,OAAO,CAAC;IACnB,8EAA8E;IAC9E,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,6DAA6D;IAC7D,OAAO,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC7B,qDAAqD;IACrD,QAAQ,EAAE,kBAAkB,CAAC;CAC9B;AAmDD;;;;;;;;;;;GAWG;AACH,wBAAgB,YAAY,CAAC,aAAa,EAAE,MAAM,GAAG,YAAY,CAmDhE;AAiCD;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,kBAAkB,CAChC,aAAa,EAAE,MAAM,EACrB,UAAU,CAAC,EAAE,MAAM,EACnB,GAAG,GAAE,MAAsB,GAC1B,MAAM,GAAG,IAAI,CAMf;AAED;;;;;;GAMG;AACH,wBAAgB,iBAAiB,CAC/B,MAAM,EAAE,YAAY,EACpB,KAAK,EAAE,YAAY,EACnB,MAAM,GAAE,MAAM,EAAO,GACpB,MAAM,EAAE,CAqBV;AAED,yEAAyE;AACzE,MAAM,WAAW,cAAc;IAC7B,KAAK,EAAE,MAAM,GAAG,QAAQ,CAAC;IACzB,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;CACf;AAED;;;;;;GAMG;AACH,wBAAgB,sBAAsB,CAAC,MAAM,EAAE,YAAY,EAAE,KAAK,EAAE,YAAY,GAAG,cAAc,EAAE,CAiBlG;AAED;;;;;;;;;GASG;AACH,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAUxD;AAED;;;;;;GAMG;AACH,wBAAgB,4BAA4B,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,cAAc,EAAE,GAAG,MAAM,CAU7F;AAKD;;;;;;;GAOG;AACH,wBAAgB,uBAAuB,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,MAAM,CAe9E;AAED;;;;;;;GAOG;AACH,wBAAgB,yBAAyB,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,MAAM,CAQhF;AAED;;;;;;;GAOG;AACH,wBAAgB,+BAA+B,CAC7C,UAAU,EAAE,MAAM,GAAG,SAAS,EAC9B,OAAO,EAAE,MAAM,GACd,IAAI,CAQN"}
|
|
@@ -0,0 +1,356 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Working-tree tripwire for consultation lanes (#1649).
|
|
3
|
+
*
|
|
4
|
+
* A reviewer is supposed to read the tree, never write to it. `consultant.md`
|
|
5
|
+
* now says so explicitly, but instruction is a probabilistic control: the claude
|
|
6
|
+
* lane runs with `permissionMode: 'bypassPermissions'`, so `Edit`/`Write`/`Bash`
|
|
7
|
+
* remain *reachable* even though nothing asks for them. In #1649 a review lane
|
|
8
|
+
* reverted three guards in a builder's worktree to check whether the tests were
|
|
9
|
+
* vacuous, and the only reason anyone found out is that the builder happened to
|
|
10
|
+
* diff against HEAD afterwards.
|
|
11
|
+
*
|
|
12
|
+
* This module is the detection half. It snapshots the tree before a lane runs
|
|
13
|
+
* and again after, and reports what moved. It does not block anything — its job
|
|
14
|
+
* is to turn a silent mutation into a loud one.
|
|
15
|
+
*
|
|
16
|
+
* ## Why not just diff `git status --porcelain` output
|
|
17
|
+
*
|
|
18
|
+
* Because the interesting case is a tree that was *already dirty*. A builder
|
|
19
|
+
* mid-phase has uncommitted work; porcelain reports ` M src/foo.ts` before the
|
|
20
|
+
* review and ` M src/foo.ts` after it, identical strings, while the file's
|
|
21
|
+
* contents changed underneath. So the snapshot pairs each path porcelain
|
|
22
|
+
* reports with a hash of its contents on disk.
|
|
23
|
+
*
|
|
24
|
+
* ## What it cannot see
|
|
25
|
+
*
|
|
26
|
+
* Files git ignores. That is deliberate — `.consult/` and `codev/projects/*/*.txt`
|
|
27
|
+
* are ignored, which is exactly why consult's own review output does not trip
|
|
28
|
+
* its own wire — but it does mean a lane writing into an ignored path goes
|
|
29
|
+
* unnoticed. Naming the tree under review is the goal here, not sandboxing.
|
|
30
|
+
*
|
|
31
|
+
* Edits *inside* a submodule, for the same reason: `git status` reports the
|
|
32
|
+
* submodule as one directory entry, so a changed file within it moves nothing
|
|
33
|
+
* this module can see.
|
|
34
|
+
*
|
|
35
|
+
* And an edit the lane restores byte-for-byte, which by construction leaves a
|
|
36
|
+
* before/after comparison nothing to compare. `consultant.md` is the only
|
|
37
|
+
* control for that case; there is a test asserting this silence, so the limit is
|
|
38
|
+
* pinned rather than assumed.
|
|
39
|
+
*/
|
|
40
|
+
import * as fs from 'node:fs';
|
|
41
|
+
import * as path from 'node:path';
|
|
42
|
+
import * as crypto from 'node:crypto';
|
|
43
|
+
import { execFileSync } from 'node:child_process';
|
|
44
|
+
const NO_IDENTITY = { head: null, branch: null };
|
|
45
|
+
const UNAVAILABLE = (reason) => ({
|
|
46
|
+
available: false,
|
|
47
|
+
reason,
|
|
48
|
+
entries: new Map(),
|
|
49
|
+
identity: NO_IDENTITY,
|
|
50
|
+
});
|
|
51
|
+
/**
|
|
52
|
+
* Read HEAD and the branch name in one `git rev-parse`.
|
|
53
|
+
*
|
|
54
|
+
* Failure is not fatal to the snapshot. A freshly `git init`ed repository has no
|
|
55
|
+
* HEAD to resolve, and the file-level half of the tripwire works there perfectly
|
|
56
|
+
* well; refusing to watch the tree because the repo has no commits would trade a
|
|
57
|
+
* real check for a missing one.
|
|
58
|
+
*/
|
|
59
|
+
function readIdentity(workspaceRoot) {
|
|
60
|
+
try {
|
|
61
|
+
const raw = execFileSync('git', ['rev-parse', 'HEAD', '--abbrev-ref', 'HEAD'], {
|
|
62
|
+
cwd: workspaceRoot,
|
|
63
|
+
encoding: 'utf-8',
|
|
64
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
65
|
+
});
|
|
66
|
+
if (typeof raw !== 'string')
|
|
67
|
+
return NO_IDENTITY;
|
|
68
|
+
const [head, branch] = raw.trim().split('\n');
|
|
69
|
+
return { head: head || null, branch: branch || null };
|
|
70
|
+
}
|
|
71
|
+
catch {
|
|
72
|
+
return NO_IDENTITY;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Hash a working-tree file, or mark it absent.
|
|
77
|
+
*
|
|
78
|
+
* Absent is a real state, not an error: a path can appear in porcelain output
|
|
79
|
+
* as a staged deletion, and a lane that *deletes* a file is precisely what we
|
|
80
|
+
* are watching for.
|
|
81
|
+
*/
|
|
82
|
+
function hashPath(absolute) {
|
|
83
|
+
try {
|
|
84
|
+
const stat = fs.lstatSync(absolute);
|
|
85
|
+
if (!stat.isFile())
|
|
86
|
+
return `<${stat.isDirectory() ? 'dir' : 'special'}>`;
|
|
87
|
+
return crypto.createHash('sha256').update(fs.readFileSync(absolute)).digest('hex');
|
|
88
|
+
}
|
|
89
|
+
catch {
|
|
90
|
+
return '<absent>';
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Snapshot the working tree of `workspaceRoot`.
|
|
95
|
+
*
|
|
96
|
+
* `-z` (NUL-separated) rather than plain `--porcelain` because git C-quotes
|
|
97
|
+
* paths containing spaces or non-ASCII in the human-readable form, and a quoted
|
|
98
|
+
* path cannot be handed back to `fs.readFileSync`. `--untracked-files=all` so
|
|
99
|
+
* a newly created file is listed individually instead of being collapsed into
|
|
100
|
+
* a single `?? dir/` entry.
|
|
101
|
+
*
|
|
102
|
+
* Records HEAD and the branch alongside the paths, so a lane that commits or
|
|
103
|
+
* switches refs is caught even when no file ends up looking different.
|
|
104
|
+
*/
|
|
105
|
+
export function snapshotTree(workspaceRoot) {
|
|
106
|
+
// Typed `unknown` rather than `string` so the guard below is a real check and
|
|
107
|
+
// not a comparison TypeScript has already narrowed away.
|
|
108
|
+
let raw;
|
|
109
|
+
try {
|
|
110
|
+
raw = execFileSync('git', ['status', '--porcelain', '-z', '--untracked-files=all'], {
|
|
111
|
+
cwd: workspaceRoot,
|
|
112
|
+
encoding: 'utf-8',
|
|
113
|
+
maxBuffer: 32 * 1024 * 1024,
|
|
114
|
+
// Capture git's stderr instead of inheriting it. Otherwise a workspace
|
|
115
|
+
// that is not a repository prints a bare `fatal: not a git repository`
|
|
116
|
+
// over the top of the message we actually mean to show.
|
|
117
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
catch (err) {
|
|
121
|
+
return UNAVAILABLE(err instanceof Error ? err.message.split('\n')[0] : String(err));
|
|
122
|
+
}
|
|
123
|
+
// The tripwire is a diagnostic wrapped around every lane, so it must never be
|
|
124
|
+
// the thing that breaks a consultation. If `git status` came back as anything
|
|
125
|
+
// other than text — a stubbed `execFileSync` under a test, a Buffer if the
|
|
126
|
+
// encoding option is ever dropped — report the snapshot as unavailable rather
|
|
127
|
+
// than throwing out of the wrapper and taking the review down with it.
|
|
128
|
+
if (typeof raw !== 'string') {
|
|
129
|
+
return UNAVAILABLE(`git status returned ${typeof raw}, not text`);
|
|
130
|
+
}
|
|
131
|
+
const entries = new Map();
|
|
132
|
+
// Each record is `XY <path>`; rename/copy records are followed by a second
|
|
133
|
+
// NUL-terminated field holding the ORIGINAL path, which must be consumed so
|
|
134
|
+
// it is not mistaken for the next record's status code.
|
|
135
|
+
const fields = raw.split('\0').filter((f) => f.length > 0);
|
|
136
|
+
for (let i = 0; i < fields.length; i++) {
|
|
137
|
+
const field = fields[i];
|
|
138
|
+
const status = field.slice(0, 2);
|
|
139
|
+
const rel = field.slice(3);
|
|
140
|
+
if (!rel)
|
|
141
|
+
continue;
|
|
142
|
+
entries.set(rel, `${status}:${hashPath(path.join(workspaceRoot, rel))}`);
|
|
143
|
+
if (status[0] === 'R' || status[0] === 'C') {
|
|
144
|
+
const origin = fields[++i];
|
|
145
|
+
if (origin !== undefined) {
|
|
146
|
+
entries.set(origin, `${status}-from:${hashPath(path.join(workspaceRoot, origin))}`);
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
return { available: true, entries, identity: readIdentity(workspaceRoot) };
|
|
151
|
+
}
|
|
152
|
+
/**
|
|
153
|
+
* Paths `consult` writes itself on every run, and which therefore are not
|
|
154
|
+
* evidence of anything.
|
|
155
|
+
*
|
|
156
|
+
* `.consult/history.log` is appended by `logQuery()` after the lane returns, so
|
|
157
|
+
* it lands inside the window this module measures. In this repo `.consult/` is
|
|
158
|
+
* gitignored and never surfaces — which is exactly why it was not obvious. A
|
|
159
|
+
* live run against a scratch repo without that entry reported the log file as a
|
|
160
|
+
* mutation on the very first try. An adopter's `.gitignore` is not something to
|
|
161
|
+
* depend on for correctness here.
|
|
162
|
+
*/
|
|
163
|
+
const CONSULT_OWNED_PREFIXES = ['.consult/'];
|
|
164
|
+
/**
|
|
165
|
+
* Review files written by consultation lanes, which are not evidence either.
|
|
166
|
+
*
|
|
167
|
+
* `computePersistentOutputPath()` here and `getReviewFilePath()` in porch share
|
|
168
|
+
* one naming convention — `<project>/<id>-<phase>-iter<N>-<lane>.txt` under
|
|
169
|
+
* `codev/projects/` — and porch's verify step runs all three lanes in parallel
|
|
170
|
+
* against the same worktree. Each lane can only exclude its *own* output path,
|
|
171
|
+
* so without this every cmap round would have each lane reporting its two
|
|
172
|
+
* siblings' review files as mutations.
|
|
173
|
+
*
|
|
174
|
+
* Invisible in this repo, again, because `.gitignore` here carries
|
|
175
|
+
* `codev/projects/*/*.txt` — but `CODEV_GITIGNORE_ENTRIES` does not ship that
|
|
176
|
+
* rule, so every adopter would have got a red banner on every cmap round. Same
|
|
177
|
+
* shape as the `.consult/history.log` false positive, and the reason this is a
|
|
178
|
+
* rule in code rather than a line in someone's `.gitignore`.
|
|
179
|
+
*/
|
|
180
|
+
const LANE_REVIEW_FILE = /^codev\/projects\/[^/]+\/[^/]+-iter\d+-[^/]+\.txt$/;
|
|
181
|
+
/**
|
|
182
|
+
* Resolve a lane's `--output` to a repo-relative path, or null if it lands
|
|
183
|
+
* outside the workspace.
|
|
184
|
+
*
|
|
185
|
+
* `--output` arrives exactly as the user typed it, which may be relative
|
|
186
|
+
* (`consult -o review.txt`), so a raw `startsWith(workspaceRoot)` test misses it
|
|
187
|
+
* and the lane ends up reporting its own review file. Resolving first also kills
|
|
188
|
+
* the opposite error: a plain prefix test counts `/repo-2/x` as living inside
|
|
189
|
+
* `/repo`.
|
|
190
|
+
*
|
|
191
|
+
* The resolution base is **`cwd`, not `workspaceRoot`**, because that is where
|
|
192
|
+
* the file actually lands: every lane writes with a bare
|
|
193
|
+
* `fs.writeFileSync(outputPath, …)`, and `consult` never chdirs. Resolving
|
|
194
|
+
* against the workspace root instead would agree with reality only when the two
|
|
195
|
+
* happen to coincide — run `consult -o review.txt` from a subdirectory and the
|
|
196
|
+
* file is written to `<subdir>/review.txt` while the exclusion names
|
|
197
|
+
* `review.txt`, so the lane reports its own output as a mutation.
|
|
198
|
+
*/
|
|
199
|
+
export function relativeOutputPath(workspaceRoot, outputPath, cwd = process.cwd()) {
|
|
200
|
+
if (!outputPath)
|
|
201
|
+
return null;
|
|
202
|
+
const rel = path.relative(workspaceRoot, path.resolve(cwd, outputPath));
|
|
203
|
+
if (rel === '' || rel.startsWith('..') || path.isAbsolute(rel))
|
|
204
|
+
return null;
|
|
205
|
+
// Snapshot keys come from git, which always uses forward slashes.
|
|
206
|
+
return rel.split(path.sep).join('/');
|
|
207
|
+
}
|
|
208
|
+
/**
|
|
209
|
+
* Repo-relative paths that differ between two snapshots, sorted.
|
|
210
|
+
*
|
|
211
|
+
* `ignore` takes the extra paths this particular run owns — the lane's review
|
|
212
|
+
* output file, whose location depends on the project and phase. A tripwire that
|
|
213
|
+
* cries wolf about its own output would be trained away within a week.
|
|
214
|
+
*/
|
|
215
|
+
export function diffTreeSnapshots(before, after, ignore = []) {
|
|
216
|
+
if (!before.available || !after.available)
|
|
217
|
+
return [];
|
|
218
|
+
const ignored = new Set(ignore);
|
|
219
|
+
const isOurs = (rel) => ignored.has(rel) ||
|
|
220
|
+
CONSULT_OWNED_PREFIXES.some(prefix => rel.startsWith(prefix)) ||
|
|
221
|
+
LANE_REVIEW_FILE.test(rel);
|
|
222
|
+
const changed = new Set();
|
|
223
|
+
for (const [rel, sig] of after.entries) {
|
|
224
|
+
if (isOurs(rel))
|
|
225
|
+
continue;
|
|
226
|
+
if (before.entries.get(rel) !== sig)
|
|
227
|
+
changed.add(rel);
|
|
228
|
+
}
|
|
229
|
+
for (const rel of before.entries.keys()) {
|
|
230
|
+
if (isOurs(rel))
|
|
231
|
+
continue;
|
|
232
|
+
if (!after.entries.has(rel))
|
|
233
|
+
changed.add(rel);
|
|
234
|
+
}
|
|
235
|
+
return [...changed].sort();
|
|
236
|
+
}
|
|
237
|
+
/**
|
|
238
|
+
* Repository identity fields that differ between two snapshots.
|
|
239
|
+
*
|
|
240
|
+
* A field that was unreadable at either end is skipped rather than reported as
|
|
241
|
+
* a change: "we could not tell" and "it moved" are different claims, and only
|
|
242
|
+
* one of them is evidence.
|
|
243
|
+
*/
|
|
244
|
+
export function diffRepositoryIdentity(before, after) {
|
|
245
|
+
if (!before.available || !after.available)
|
|
246
|
+
return [];
|
|
247
|
+
const changes = [];
|
|
248
|
+
const fields = [
|
|
249
|
+
['HEAD', 'head'],
|
|
250
|
+
['branch', 'branch'],
|
|
251
|
+
];
|
|
252
|
+
for (const [label, key] of fields) {
|
|
253
|
+
const a = before.identity[key];
|
|
254
|
+
const b = after.identity[key];
|
|
255
|
+
if (a === null || b === null)
|
|
256
|
+
continue;
|
|
257
|
+
if (a !== b)
|
|
258
|
+
changes.push({ field: label, before: a, after: b });
|
|
259
|
+
}
|
|
260
|
+
return changes;
|
|
261
|
+
}
|
|
262
|
+
/**
|
|
263
|
+
* Render a string safe to print to a terminal.
|
|
264
|
+
*
|
|
265
|
+
* Git path names are bytes, and almost anything that is not `/` or NUL is legal
|
|
266
|
+
* in one — including newlines and ESC. Printing such a name raw lets it forge
|
|
267
|
+
* the rest of the banner: a file called `\n - harmless.ts` adds a line to the
|
|
268
|
+
* list, and one containing a CSI sequence can repaint or erase what is above it.
|
|
269
|
+
* The warning exists to be trusted, so nothing it prints gets to move the
|
|
270
|
+
* cursor. C0, DEL and C1 are escaped; ordinary Unicode is left alone.
|
|
271
|
+
*/
|
|
272
|
+
export function escapeControlChars(value) {
|
|
273
|
+
const named = {
|
|
274
|
+
'\n': '\\n',
|
|
275
|
+
'\r': '\\r',
|
|
276
|
+
'\t': '\\t',
|
|
277
|
+
};
|
|
278
|
+
// eslint-disable-next-line no-control-regex
|
|
279
|
+
return value.replace(/[\x00-\x1f\x7f-\x9f]/g, ch => named[ch] ?? `\\x${ch.charCodeAt(0).toString(16).padStart(2, '0')}`);
|
|
280
|
+
}
|
|
281
|
+
/**
|
|
282
|
+
* The warning for a repository that moved under the reviewer.
|
|
283
|
+
*
|
|
284
|
+
* Separate from the changed-files banner because it is a different fact with a
|
|
285
|
+
* different remedy: no file need look any different, and `git status` will not
|
|
286
|
+
* show you what happened — `git reflog` will.
|
|
287
|
+
*/
|
|
288
|
+
export function formatRepositoryMovedWarning(model, changes) {
|
|
289
|
+
return [
|
|
290
|
+
`REPOSITORY MOVED during the ${model} review (#1649).`,
|
|
291
|
+
'A consultant reviews the commit in front of it; it must never commit, or switch refs.',
|
|
292
|
+
...changes.map(c => ` - ${c.field}: ${escapeControlChars(c.before)} -> ${escapeControlChars(c.after)}`),
|
|
293
|
+
'The files on disk may look untouched and still not be the ones this review describes.',
|
|
294
|
+
'Check `git reflog` and `git status` before trusting this review or shipping the branch.',
|
|
295
|
+
].join('\n');
|
|
296
|
+
}
|
|
297
|
+
/** Cap the file list so a lane that touched hundreds of paths stays readable. */
|
|
298
|
+
const MAX_LISTED = 20;
|
|
299
|
+
/**
|
|
300
|
+
* The warning text, shared by the stderr banner and the review-output footer.
|
|
301
|
+
*
|
|
302
|
+
* Deliberately says the tree changed *while the lane ran*, not that the lane
|
|
303
|
+
* changed it. Under `cmap` all three lanes run as concurrent processes in one
|
|
304
|
+
* worktree, so each one sees the others' writes; asserting a culprit we cannot
|
|
305
|
+
* identify would be a claim the evidence does not support.
|
|
306
|
+
*/
|
|
307
|
+
export function formatTreeChangeWarning(model, files) {
|
|
308
|
+
const listed = files.slice(0, MAX_LISTED);
|
|
309
|
+
const rest = files.length - listed.length;
|
|
310
|
+
const lines = [
|
|
311
|
+
`WORKING TREE CHANGED during the ${model} review (#1649).`,
|
|
312
|
+
`A consultant reviews the tree; it must never write to it. ${files.length} path${files.length === 1 ? '' : 's'} changed while this lane was running:`,
|
|
313
|
+
...listed.map(f => ` - ${escapeControlChars(f)}`),
|
|
314
|
+
];
|
|
315
|
+
if (rest > 0)
|
|
316
|
+
lines.push(` … and ${rest} more`);
|
|
317
|
+
lines.push('Check `git status` and `git diff` before trusting this review or shipping the branch.', 'Note: under cmap the lanes run concurrently in one worktree, so another lane — or you —', 'may be the writer. This warning names what moved, not who moved it.');
|
|
318
|
+
return lines.join('\n');
|
|
319
|
+
}
|
|
320
|
+
/**
|
|
321
|
+
* The warning for a post-review snapshot that could not be taken at all.
|
|
322
|
+
*
|
|
323
|
+
* Distinct from "the tree changed" because the failure is different in kind: we
|
|
324
|
+
* know something happened, and we cannot say what. Reported rather than
|
|
325
|
+
* swallowed — a comparison that silently returns "no changes" when it could not
|
|
326
|
+
* look is the failure mode this whole module exists to avoid.
|
|
327
|
+
*/
|
|
328
|
+
export function formatSnapshotLostWarning(model, reason) {
|
|
329
|
+
return [
|
|
330
|
+
`WORKING TREE UNREADABLE after the ${model} review (#1649).`,
|
|
331
|
+
'The tree was readable before this lane ran and `git status` fails now, so the tripwire',
|
|
332
|
+
'cannot say whether anything changed. This is not the same as "nothing changed".',
|
|
333
|
+
`Reason: ${escapeControlChars(reason ?? 'unknown')}`,
|
|
334
|
+
'Check `git status` and `git diff` before trusting this review or shipping the branch.',
|
|
335
|
+
].join('\n');
|
|
336
|
+
}
|
|
337
|
+
/**
|
|
338
|
+
* Append the warning to a lane's review output so it survives into the file
|
|
339
|
+
* porch and the builder actually read — stderr scrolls past, review files don't.
|
|
340
|
+
*
|
|
341
|
+
* Written as a plain paragraph with no `VERDICT:` line, so `parseVerdict()`
|
|
342
|
+
* (which scans last→first for that prefix) still finds the reviewer's own
|
|
343
|
+
* verdict above it.
|
|
344
|
+
*/
|
|
345
|
+
export function appendTreeChangeWarningToOutput(outputPath, warning) {
|
|
346
|
+
if (!outputPath || !fs.existsSync(outputPath))
|
|
347
|
+
return;
|
|
348
|
+
try {
|
|
349
|
+
fs.appendFileSync(outputPath, `\n\n---\n\n## ⚠️ ${warning}\n`);
|
|
350
|
+
}
|
|
351
|
+
catch {
|
|
352
|
+
// The stderr banner already carried the warning; failing to annotate the
|
|
353
|
+
// file must not turn a successful review into a failed command.
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
//# sourceMappingURL=tree-tripwire.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tree-tripwire.js","sourceRoot":"","sources":["../../../src/commands/consult/tree-tripwire.ts"],"names":[],"mappings":"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"}
|
|
@@ -31,6 +31,75 @@ export interface SessionLogCheck {
|
|
|
31
31
|
* hermetic; this function is where the logic lives and is pinned by tests.
|
|
32
32
|
*/
|
|
33
33
|
export declare function checkSessionLogs(logDir: string, env?: NodeJS.ProcessEnv): SessionLogCheck;
|
|
34
|
+
export interface TowerEnvCheck {
|
|
35
|
+
status: 'ok' | 'warn' | 'skipped';
|
|
36
|
+
/** Claude Code session markers found on the running Tower daemon. */
|
|
37
|
+
markers: string[];
|
|
38
|
+
/** Running shellper daemons whose own env still carries markers. */
|
|
39
|
+
contaminatedSessions: number;
|
|
40
|
+
/** Human-readable one-liner. No colour codes. */
|
|
41
|
+
summary: string;
|
|
42
|
+
/** Present only when `status === 'warn'`. */
|
|
43
|
+
recommendation?: string;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Remediation for a Tower daemon that inherited the markers (#1219).
|
|
47
|
+
*
|
|
48
|
+
* A plain restart is enough for the *daemon*, but deliberately not for what it
|
|
49
|
+
* already spawned: `towerStop` leaves shellpers running on purpose
|
|
50
|
+
* (`commands/tower.ts` — they are detached so terminals survive a restart), so
|
|
51
|
+
* their agent processes keep the markers until each is restarted. Saying only
|
|
52
|
+
* "restart Tower" would be a half-truth that reads as a full fix.
|
|
53
|
+
*/
|
|
54
|
+
export declare const TOWER_ENV_RESTART_HINT: string;
|
|
55
|
+
/**
|
|
56
|
+
* Remediation when Tower itself is clean but sessions it spawned earlier are not.
|
|
57
|
+
*
|
|
58
|
+
* The force-kill flag is named but not recommended outright: it ends every
|
|
59
|
+
* running agent on the machine, which is a bigger hammer than most cases need.
|
|
60
|
+
*/
|
|
61
|
+
export declare const TOWER_ENV_SESSION_HINT: string;
|
|
62
|
+
/**
|
|
63
|
+
* Decide what the "Tower environment" doctor section should say (#1219).
|
|
64
|
+
*
|
|
65
|
+
* A Tower started from inside a Claude Code session inherits that session's
|
|
66
|
+
* markers and hands them to every agent it spawns, which turns transcript
|
|
67
|
+
* saving — and therefore resume — off for all of them. Newly started Towers
|
|
68
|
+
* scrub themselves, but a *long-running* daemon started before this fix (or by
|
|
69
|
+
* something other than `afx tower start`) can still be carrying them, and that
|
|
70
|
+
* is invisible until a crash-recovery attempt fails.
|
|
71
|
+
*
|
|
72
|
+
* The readers are injected so the decision is unit-testable without a live
|
|
73
|
+
* daemon; `doctor()` passes the real `ps eww` ones. A `null` Tower env means no
|
|
74
|
+
* Tower is running (or its env could not be read) — reported as `skipped`, never
|
|
75
|
+
* a warning, since "no Tower" is not a fault.
|
|
76
|
+
*
|
|
77
|
+
* `readSessionEnvs` covers what a daemon-only check would miss (CMAP review):
|
|
78
|
+
* shellpers are detached and survive `afx tower stop` by design, so after a
|
|
79
|
+
* plain restart Tower reads clean while the agents it spawned earlier are still
|
|
80
|
+
* carrying the markers — and still unresumable.
|
|
81
|
+
*/
|
|
82
|
+
export declare function checkTowerEnv(readTowerEnv: () => Record<string, string> | null, readSessionEnvs?: () => Array<Record<string, string>>): TowerEnvCheck;
|
|
83
|
+
/**
|
|
84
|
+
* PIDs of the running shellper daemons — the processes that outlive Tower and
|
|
85
|
+
* so can still be carrying markers after Tower itself has been restarted clean.
|
|
86
|
+
*/
|
|
87
|
+
export declare function findShellperPids(): number[];
|
|
88
|
+
/**
|
|
89
|
+
* Read a running process's environment via `ps eww`.
|
|
90
|
+
*
|
|
91
|
+
* `ps eww -o command=` prints the argv followed by the environment on one line,
|
|
92
|
+
* and `ps ww -o command=` prints the argv alone — so subtracting the second from
|
|
93
|
+
* the first leaves the environment with no argv tokens to misparse. Returns
|
|
94
|
+
* `null` when the process is gone or the two readings disagree (a race), rather
|
|
95
|
+
* than guessing. macOS has no `/proc`, so `ps` is the portable reader here.
|
|
96
|
+
*
|
|
97
|
+
* `ps` separates environment entries with spaces and does not quote them, so a
|
|
98
|
+
* *value* containing a space is truncated at that space. Names are unaffected,
|
|
99
|
+
* and this check only ever asks which variables are present — but that is why
|
|
100
|
+
* the result is not a general-purpose environment reader.
|
|
101
|
+
*/
|
|
102
|
+
export declare function readProcessEnv(pid: number): Record<string, string> | null;
|
|
34
103
|
/**
|
|
35
104
|
* Main doctor function
|
|
36
105
|
*/
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"doctor.d.ts","sourceRoot":"","sources":["../../src/commands/doctor.ts"],"names":[],"mappings":"AAAA;;;;GAIG;
|
|
1
|
+
{"version":3,"file":"doctor.d.ts","sourceRoot":"","sources":["../../src/commands/doctor.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AA8DH;;;;GAIG;AACH,eAAO,MAAM,sBAAsB,QAAyB,CAAC;AAE7D,MAAM,WAAW,eAAe;IAC9B,MAAM,EAAE,IAAI,GAAG,MAAM,CAAC;IACtB,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,aAAa,EAAE,MAAM,CAAC;IACtB,6EAA6E;IAC7E,OAAO,EAAE,MAAM,CAAC;IAChB,mDAAmD;IACnD,aAAa,EAAE,MAAM,CAAC;IACtB,6CAA6C;IAC7C,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB;AAED;;;;;;;;GAQG;AACH,wBAAgB,gBAAgB,CAC9B,MAAM,EAAE,MAAM,EACd,GAAG,GAAE,MAAM,CAAC,UAAwB,GACnC,eAAe,CAgCjB;AAED,MAAM,WAAW,aAAa;IAC5B,MAAM,EAAE,IAAI,GAAG,MAAM,GAAG,SAAS,CAAC;IAClC,qEAAqE;IACrE,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,oEAAoE;IACpE,oBAAoB,EAAE,MAAM,CAAC;IAC7B,iDAAiD;IACjD,OAAO,EAAE,MAAM,CAAC;IAChB,6CAA6C;IAC7C,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB;AAED;;;;;;;;GAQG;AACH,eAAO,MAAM,sBAAsB,QAE6C,CAAC;AAEjF;;;;;GAKG;AACH,eAAO,MAAM,sBAAsB,QAE2C,CAAC;AAE/E;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,aAAa,CAC3B,YAAY,EAAE,MAAM,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,IAAI,EACjD,eAAe,GAAE,MAAM,KAAK,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAY,GAC9D,aAAa,CAwCf;AAED;;;GAGG;AACH,wBAAgB,gBAAgB,IAAI,MAAM,EAAE,CAO3C;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,cAAc,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,IAAI,CAoBzE;AAuhBD;;GAEG;AACH,wBAAsB,MAAM,IAAI,OAAO,CAAC,MAAM,CAAC,CA4hB9C"}
|