@miller-tech/uap 1.172.15 → 1.174.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 (31) hide show
  1. package/dist/.tsbuildinfo +1 -1
  2. package/dist/delivery/agentic-executor.d.ts +2 -1
  3. package/dist/delivery/agentic-executor.d.ts.map +1 -1
  4. package/dist/delivery/agentic-executor.js +102 -4
  5. package/dist/delivery/agentic-executor.js.map +1 -1
  6. package/dist/delivery/applier.d.ts.map +1 -1
  7. package/dist/delivery/applier.js +53 -0
  8. package/dist/delivery/applier.js.map +1 -1
  9. package/dist/delivery/bash-sweep.d.ts +188 -0
  10. package/dist/delivery/bash-sweep.d.ts.map +1 -0
  11. package/dist/delivery/bash-sweep.js +610 -0
  12. package/dist/delivery/bash-sweep.js.map +1 -0
  13. package/dist/delivery/contract-extractor.d.ts +16 -0
  14. package/dist/delivery/contract-extractor.d.ts.map +1 -1
  15. package/dist/delivery/contract-extractor.js +136 -3
  16. package/dist/delivery/contract-extractor.js.map +1 -1
  17. package/dist/delivery/mission-acceptance.d.ts.map +1 -1
  18. package/dist/delivery/mission-acceptance.js +48 -3
  19. package/dist/delivery/mission-acceptance.js.map +1 -1
  20. package/dist/delivery/stub-detector.d.ts +150 -0
  21. package/dist/delivery/stub-detector.d.ts.map +1 -0
  22. package/dist/delivery/stub-detector.js +474 -0
  23. package/dist/delivery/stub-detector.js.map +1 -0
  24. package/dist/delivery/visual-gate.d.ts +58 -0
  25. package/dist/delivery/visual-gate.d.ts.map +1 -1
  26. package/dist/delivery/visual-gate.js +87 -3
  27. package/dist/delivery/visual-gate.js.map +1 -1
  28. package/package.json +1 -1
  29. package/src/policies/enforcers/__pycache__/_common.cpython-312.pyc +0 -0
  30. package/templates/hooks/__pycache__/deliver_autoroute.cpython-312.pyc +0 -0
  31. package/tools/agents/scripts/__pycache__/toolcall_path_normalizer.cpython-312.pyc +0 -0
@@ -0,0 +1,188 @@
1
+ /**
2
+ * Turn-end substance sweep for files written through `run_bash`.
3
+ *
4
+ * The stub guard covers `write_file` and `edit_file`. `run_bash` bypasses both:
5
+ * `cat > f <<'EOF' … EOF`, `sed -i` and `python -c "open(...).write(...)"` all
6
+ * land content without passing through a tool handler. That was recorded as a
7
+ * known limit when the guard shipped (PR #611) rather than papered over, and
8
+ * this closes it.
9
+ *
10
+ * WHY A SWEEP AND NOT A CHECK IN THE HANDLER
11
+ * The other run_bash protections work by SNAPSHOTTING a known set — protected
12
+ * tests, locked contracts, gate configs — and restoring whatever the command
13
+ * touched. That works because those sets are enumerable up front. The set of
14
+ * files a shell command might write is not, so there is nothing to snapshot by
15
+ * name. The sweep instead takes a baseline of the tree at the first command and
16
+ * compares at turn end, which is the only way to attribute an arbitrary write.
17
+ *
18
+ * THE COST MODEL IS INVERTED FROM THE WRITE PATH, AND THAT DRIVES EVERYTHING
19
+ * On the write path a false positive costs one retry: the write is refused and
20
+ * the model tries again. Here a false positive touches a file that already
21
+ * exists, so it costs WORK. Four consequences, each of which an earlier version
22
+ * of this file got wrong and each of which now has a test:
23
+ *
24
+ * 1. Absence of baseline CONTENT never implies absence of the FILE. Existence
25
+ * is recorded for every path with no read and no byte budget, so a file the
26
+ * content baseline skipped — capped, oversized, unreadable — is still known
27
+ * to have existed. Only a path absent from `known` may be treated as created
28
+ * this turn. Without that split, a file the baseline had skipped was DELETED
29
+ * when the shell shrank it, and on a tree past the cap files the shell never
30
+ * touched became deletion candidates.
31
+ * 2. Nothing the harness has SEEN is destroyed, and nothing leaves the project.
32
+ * Every removal and every revert preserves the current content under
33
+ * `.uap/bash-sweep-backup/` first, a failed preserve aborts the action, and
34
+ * every destination is proven to resolve inside that directory. The limit is
35
+ * worth stating precisely, because a broader claim would be false: a revert
36
+ * restores the last content the harness AUTHORISED, so if one shell command
37
+ * writes real code and a later command in the SAME turn hollows it, the
38
+ * intermediate version — which no tool handler ever saw — is not recovered.
39
+ * The rejected content is preserved; the unobserved good version is not. `relative()` used to be run through
40
+ * `.split('\\')`, which is correct on Windows and WRONG on POSIX where a
41
+ * backslash is a legal filename character: one directory entry named
42
+ * `..\..\..\..\x.js` produced a traversing key and the sweep wrote and
43
+ * RENAMED outside the project root. (Reproduced; test below. The harness
44
+ * already documents that the small model mangles paths, so this needed no
45
+ * adversary.)
46
+ * 3. An existing file is only reverted when the change made it STRICTLY WORSE —
47
+ * more empty bodies than the content we authorised. A formatter or codemod
48
+ * that rewrites an already-stubby file without emptying anything further is
49
+ * left alone, because reverting it would be both wrong and unexplainable
50
+ * ("your shell command wrote SKELETONS" about `prettier --write`).
51
+ * 4. Silence must mean "nothing changed", not "we could not look". Coverage
52
+ * gaps are reported — but ONLY for files that actually changed. Reporting
53
+ * every unreadable path made a single `logo.png` emit a coverage warning on
54
+ * every bash turn of every asset-bearing project, into a prompt slot that is
55
+ * head-truncated.
56
+ *
57
+ * WHAT COUNTS AS THE BASELINE
58
+ * Per file, the last content this harness knows it authorised: what a guarded
59
+ * tool wrote this turn if one did, otherwise the content at the first command.
60
+ * Using "was it written by a guarded tool" as a plain skip would leave a hole —
61
+ * write the real implementation with write_file, then hollow it out with sed,
62
+ * and the path is on the allow-list. Comparing against the authorised CONTENT
63
+ * closes it. A file CREATED this turn is judged on its own content regardless of
64
+ * authorisation, which closes the mirror hole: the shell writes a skeleton, then
65
+ * a single edit_file fills one body and the monotone-progress rule — measuring
66
+ * against the skeleton itself — stamps the rest as authorised.
67
+ *
68
+ * SCOPE, STATED PLAINLY
69
+ * This is a correctness guard against confused generation, not a security
70
+ * boundary. A model holding a shell can do anything a shell can do, including
71
+ * removing this check; run_bash is gated on kernel containment for that reason.
72
+ * The sweep judges SUBSTANCE only — a command that deletes a real file is a
73
+ * different failure and is not restored here. And a quarantine can leave the
74
+ * tree in a state neither the model nor the gates have seen: a `main.js` written
75
+ * by write_file may import a `player.js` the sweep removed. The gates see that
76
+ * and the note names the repair, which is the intended outcome, but it is a real
77
+ * intermediate state rather than a clean rollback.
78
+ */
79
+ export interface SweepLimits {
80
+ /** Max files whose CONTENT is baselined. Existence is never capped. */
81
+ maxFiles: number;
82
+ /** Max total bytes of baselined content. */
83
+ maxTotalBytes: number;
84
+ /** Per-file read ceiling; matches the detector's own scan cap. */
85
+ maxFileBytes: number;
86
+ }
87
+ /**
88
+ * A delivery target is an application, not a monorepo, so these sit far above
89
+ * any real project — they exist so a mission pointed at something huge degrades
90
+ * predictably instead of reading gigabytes inside a turn boundary. Injectable so
91
+ * tests exercise the capped paths for real rather than setting a flag by hand.
92
+ */
93
+ export declare const DEFAULT_LIMITS: SweepLimits;
94
+ /** Where displaced content goes. Inside `.uap`, so the sweep never re-walks it. */
95
+ export declare const BACKUP_DIR: string;
96
+ export interface BashSweep {
97
+ /** False when bash is disabled for the session: no baseline, no sweep. */
98
+ readonly enabled: boolean;
99
+ /** Root of both walks. */
100
+ readonly projectRoot: string;
101
+ /** Set true once a command has run — the sweep is a no-op until then. */
102
+ bashRan: boolean;
103
+ /** Whether the baseline walk has happened (it is lazy). */
104
+ baselined: boolean;
105
+ /**
106
+ * Whether the sweep has already run for this turn. The wrapper calls
107
+ * finishBashSweep on the success path and again from `finally`; making the
108
+ * second call a no-op here — rather than relying on the caller to track it —
109
+ * keeps the double walk out of every bash turn no matter who calls.
110
+ */
111
+ swept: boolean;
112
+ /** Project-relative path -> content a guarded tool wrote this turn. */
113
+ readonly authorised: Map<string, string>;
114
+ /** Project-relative path -> content at the first command. May be partial. */
115
+ readonly baseline: Map<string, string>;
116
+ /** EVERY path that existed at the first command. Never capped — see doc. */
117
+ readonly known: Set<string>;
118
+ /**
119
+ * Size and mtime of every known path. Two jobs: an unbaselined file's CHANGE
120
+ * stays visible (so coverage warnings name files that actually moved), and the
121
+ * sweep can skip re-reading a file that demonstrably did not move — otherwise
122
+ * it re-reads the entire content baseline on a turn where the shell touched
123
+ * one file.
124
+ */
125
+ readonly statAt: Map<string, {
126
+ size: number;
127
+ mtimeMs: number;
128
+ }>;
129
+ /** True when the file-count/byte budget cut the content baseline short. */
130
+ truncated: boolean;
131
+ readonly limits: SweepLimits;
132
+ }
133
+ export interface SweepOutcome {
134
+ /** Paths reverted to their authorised content (backed up first). */
135
+ reverted: string[];
136
+ /** Paths moved into the backup — they did not exist before the turn. */
137
+ removed: string[];
138
+ /** Paths that CHANGED but could not be attributed; left untouched. */
139
+ uncovered: string[];
140
+ /** Paths the sweep decided to act on but could not. */
141
+ failed: string[];
142
+ /** Model-facing note, '' when there is nothing to say. */
143
+ note: string;
144
+ }
145
+ /** A sweep that never fires — for callers with bash disabled. */
146
+ export declare function disabledSweep(): BashSweep;
147
+ /**
148
+ * Arm the sweep for a turn. Deliberately does NOT walk anything.
149
+ *
150
+ * The baseline is captured lazily, at the first command, so a bash-enabled
151
+ * session that does not actually shell out in a given turn pays nothing.
152
+ * Deferring is safe because of what `authorised` holds: anything that changed
153
+ * BEFORE the first command can only have come from a guarded tool, and those
154
+ * writes are recorded by content as they happen.
155
+ */
156
+ export declare function beginBashSweep(projectRoot: string, enabled: boolean, limits?: SweepLimits): BashSweep;
157
+ /**
158
+ * Arm for a command: mark that the shell ran, and capture the baseline once.
159
+ *
160
+ * Called from the run_bash handler BEFORE the spawn — a command that times out
161
+ * or is killed may still have written files, and a sweep with nothing to compare
162
+ * against would be worse than none, reporting "checked" over the messiest case.
163
+ */
164
+ export declare function armSweepForCommand(sweep: BashSweep): void;
165
+ /** Record what a guarded tool wrote, so a later shell write is distinguishable. */
166
+ export declare function recordAuthorisedWrite(sweep: BashSweep, rel: string, content: string): void;
167
+ /**
168
+ * Compare the tree against the baseline and undo unattributed stub writes.
169
+ * Returns an empty outcome when bash never ran, so the common path is a boolean.
170
+ */
171
+ export declare function finishBashSweep(sweep: BashSweep): SweepOutcome;
172
+ /**
173
+ * Put the note FIRST — but never ahead of a protocol marker.
174
+ *
175
+ * The retry prompt includes the previous output through `truncateHead`, which
176
+ * keeps the leading 3000 characters, so a note appended after a long turn summary
177
+ * is exactly the part that gets cut. That argues for prepending.
178
+ *
179
+ * Against it: `decodeBudgetStop` only recognises the context-budget marker within
180
+ * the first 512 characters, and this note routinely exceeds that on its own. A
181
+ * budget-stopped turn that was also swept would therefore stop being recognised
182
+ * as budget-stopped — which silently disables the epic controller's rail-sizing
183
+ * split, on exactly the long shell-using sessions that blow the rail. So when the
184
+ * output carries that marker, the note goes after the marker's own line: the
185
+ * marker keeps position 0, and the note is still near the front.
186
+ */
187
+ export declare function prependSweepNote(output: string, outcome: SweepOutcome): string;
188
+ //# sourceMappingURL=bash-sweep.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bash-sweep.d.ts","sourceRoot":"","sources":["../../src/delivery/bash-sweep.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6EG;AA+DH,MAAM,WAAW,WAAW;IAC1B,uEAAuE;IACvE,QAAQ,EAAE,MAAM,CAAC;IACjB,4CAA4C;IAC5C,aAAa,EAAE,MAAM,CAAC;IACtB,kEAAkE;IAClE,YAAY,EAAE,MAAM,CAAC;CACtB;AAED;;;;;GAKG;AACH,eAAO,MAAM,cAAc,EAAE,WAI5B,CAAC;AAEF,mFAAmF;AACnF,eAAO,MAAM,UAAU,QAAoC,CAAC;AAE5D,MAAM,WAAW,SAAS;IACxB,0EAA0E;IAC1E,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;IAC1B,0BAA0B;IAC1B,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,yEAAyE;IACzE,OAAO,EAAE,OAAO,CAAC;IACjB,2DAA2D;IAC3D,SAAS,EAAE,OAAO,CAAC;IACnB;;;;;OAKG;IACH,KAAK,EAAE,OAAO,CAAC;IACf,uEAAuE;IACvE,QAAQ,CAAC,UAAU,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACzC,6EAA6E;IAC7E,QAAQ,CAAC,QAAQ,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACvC,4EAA4E;IAC5E,QAAQ,CAAC,KAAK,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;IAC5B;;;;;;OAMG;IACH,QAAQ,CAAC,MAAM,EAAE,GAAG,CAAC,MAAM,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAChE,2EAA2E;IAC3E,SAAS,EAAE,OAAO,CAAC;IACnB,QAAQ,CAAC,MAAM,EAAE,WAAW,CAAC;CAC9B;AAED,MAAM,WAAW,YAAY;IAC3B,oEAAoE;IACpE,QAAQ,EAAE,MAAM,EAAE,CAAC;IACnB,wEAAwE;IACxE,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,sEAAsE;IACtE,SAAS,EAAE,MAAM,EAAE,CAAC;IACpB,uDAAuD;IACvD,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,0DAA0D;IAC1D,IAAI,EAAE,MAAM,CAAC;CACd;AAOD,iEAAiE;AACjE,wBAAgB,aAAa,IAAI,SAAS,CAczC;AAsGD;;;;;;;;GAQG;AACH,wBAAgB,cAAc,CAC5B,WAAW,EAAE,MAAM,EACnB,OAAO,EAAE,OAAO,EAChB,MAAM,GAAE,WAA4B,GACnC,SAAS,CAuBX;AAED;;;;;;GAMG;AACH,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,SAAS,GAAG,IAAI,CAuBzD;AAED,mFAAmF;AACnF,wBAAgB,qBAAqB,CAAC,KAAK,EAAE,SAAS,EAAE,GAAG,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,IAAI,CAE1F;AA+ED;;;GAGG;AACH,wBAAgB,eAAe,CAAC,KAAK,EAAE,SAAS,GAAG,YAAY,CAgH9D;AA+DD;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,YAAY,GAAG,MAAM,CAS9E"}