@miller-tech/uap 1.161.19 → 1.161.20

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.
@@ -8,6 +8,16 @@
8
8
  * no model involved — then the caller runs the project gates as usual. A
9
9
  * mismatched anchor fails loudly (the tree moved since the intent was
10
10
  * recorded); nothing is ever fuzzily applied.
11
+ *
12
+ * Replay is REPLAY-ONCE (2026-07-18): an intent that applies (or is detected
13
+ * as already applied) is CONSUMED — removed from the pending log and archived
14
+ * to `.uap/pending-deliver.applied.jsonl`. Before this, every replay run
15
+ * re-scanned the full log and re-applied any intent whose anchor still
16
+ * matched; for insertion-style edits (old_string surviving as a prefix of
17
+ * new_string) that duplicated the inserted hunk on EVERY run — observed
18
+ * 2026-07-18 as a 4x-duplicated block from one intent replayed by
19
+ * hook-detached + manual runs. Stale-anchor skips stay in the log so they
20
+ * remain loudly visible.
11
21
  */
12
22
  export interface PendingIntent {
13
23
  ts: number;
@@ -40,6 +50,10 @@ export declare function readPendingIntents(projectRoot: string): PendingIntent[]
40
50
  * the current content; content-intents overwrite the file whole (that is what
41
51
  * the blocked Write would have done). Multiple intents for one file apply in
42
52
  * recorded order, so a sequence of blocked Edits replays faithfully.
53
+ *
54
+ * Applied (and detected-already-applied) intents are consumed from the log —
55
+ * replay is idempotent across runs. Stale-anchor and pre-D1 skips are NOT
56
+ * consumed: they stay visible until an operator resolves or clears them.
43
57
  */
44
58
  export declare function applyPendingIntents(projectRoot: string, file?: string): PendingApplyResult;
45
59
  //# sourceMappingURL=pending-intents.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"pending-intents.d.ts","sourceRoot":"","sources":["../../src/delivery/pending-intents.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAKH,MAAM,WAAW,aAAa;IAC5B,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,MAAM,CAAC;IAClB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE;QAAE,UAAU,CAAC,EAAE,MAAM,CAAC;QAAC,UAAU,CAAC,EAAE,MAAM,CAAC;QAAC,OAAO,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;CACvE;AAED,MAAM,WAAW,kBAAkB;IACjC,OAAO,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,EAAE,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,SAAS,GAAG,OAAO,CAAA;KAAE,CAAC,CAAC;IACxE,OAAO,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,EAAE,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;CAC9D;AAID,8EAA8E;AAC9E,wBAAgB,kBAAkB,CAAC,WAAW,EAAE,MAAM,GAAG,aAAa,EAAE,CAevE;AAED;;;;;;GAMG;AACH,wBAAgB,mBAAmB,CAAC,WAAW,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,GAAG,kBAAkB,CA0C1F"}
1
+ {"version":3,"file":"pending-intents.d.ts","sourceRoot":"","sources":["../../src/delivery/pending-intents.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAKH,MAAM,WAAW,aAAa;IAC5B,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,MAAM,CAAC;IAClB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE;QAAE,UAAU,CAAC,EAAE,MAAM,CAAC;QAAC,UAAU,CAAC,EAAE,MAAM,CAAC;QAAC,OAAO,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;CACvE;AAED,MAAM,WAAW,kBAAkB;IACjC,OAAO,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,EAAE,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,SAAS,GAAG,OAAO,CAAA;KAAE,CAAC,CAAC;IACxE,OAAO,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,EAAE,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;CAC9D;AAKD,8EAA8E;AAC9E,wBAAgB,kBAAkB,CAAC,WAAW,EAAE,MAAM,GAAG,aAAa,EAAE,CAevE;AAuCD;;;;;;;;;;GAUG;AACH,wBAAgB,mBAAmB,CAAC,WAAW,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,GAAG,kBAAkB,CAgE1F"}
@@ -8,10 +8,21 @@
8
8
  * no model involved — then the caller runs the project gates as usual. A
9
9
  * mismatched anchor fails loudly (the tree moved since the intent was
10
10
  * recorded); nothing is ever fuzzily applied.
11
+ *
12
+ * Replay is REPLAY-ONCE (2026-07-18): an intent that applies (or is detected
13
+ * as already applied) is CONSUMED — removed from the pending log and archived
14
+ * to `.uap/pending-deliver.applied.jsonl`. Before this, every replay run
15
+ * re-scanned the full log and re-applied any intent whose anchor still
16
+ * matched; for insertion-style edits (old_string surviving as a prefix of
17
+ * new_string) that duplicated the inserted hunk on EVERY run — observed
18
+ * 2026-07-18 as a 4x-duplicated block from one intent replayed by
19
+ * hook-detached + manual runs. Stale-anchor skips stay in the log so they
20
+ * remain loudly visible.
11
21
  */
12
- import { existsSync, readFileSync, writeFileSync } from 'fs';
22
+ import { existsSync, readFileSync, writeFileSync, appendFileSync } from 'fs';
13
23
  import { join, resolve, relative, isAbsolute } from 'path';
14
24
  const PENDING_LOG = '.uap/pending-deliver.jsonl';
25
+ const APPLIED_LOG = '.uap/pending-deliver.applied.jsonl';
15
26
  /** Read all recorded intents, oldest first. Unparseable lines are ignored. */
16
27
  export function readPendingIntents(projectRoot) {
17
28
  const log = join(projectRoot, PENDING_LOG);
@@ -33,17 +44,58 @@ export function readPendingIntents(projectRoot) {
33
44
  }
34
45
  return intents;
35
46
  }
47
+ /** Stable identity for consume-filtering (ts + file + exact edit content). */
48
+ function intentKey(i) {
49
+ return JSON.stringify({ ts: i.ts, file_path: i.file_path, edit: i.edit ?? null });
50
+ }
51
+ /**
52
+ * Remove consumed intents from the pending log (re-reading it first, so lines
53
+ * appended by a concurrent gate hook during this run survive) and archive
54
+ * them to the applied log for audit.
55
+ */
56
+ function consumeIntents(root, consumed) {
57
+ if (consumed.length === 0)
58
+ return;
59
+ const keys = new Set(consumed.map(intentKey));
60
+ const log = join(root, PENDING_LOG);
61
+ const remaining = [];
62
+ if (existsSync(log)) {
63
+ for (const line of readFileSync(log, 'utf-8').split('\n')) {
64
+ const t = line.trim();
65
+ if (!t)
66
+ continue;
67
+ let keep = true;
68
+ try {
69
+ const parsed = JSON.parse(t);
70
+ if (parsed && typeof parsed.file_path === 'string' && keys.has(intentKey(parsed)))
71
+ keep = false;
72
+ }
73
+ catch {
74
+ /* keep garbage lines — readPendingIntents ignores them anyway */
75
+ }
76
+ if (keep)
77
+ remaining.push(t);
78
+ }
79
+ }
80
+ writeFileSync(log, remaining.length ? remaining.join('\n') + '\n' : '', 'utf-8');
81
+ appendFileSync(join(root, APPLIED_LOG), consumed.map((i) => JSON.stringify({ ...i, applied_at: Date.now() })).join('\n') + '\n', 'utf-8');
82
+ }
36
83
  /**
37
84
  * Deterministically apply the recorded intents for `file` (or every file when
38
85
  * omitted). Replace-intents require the old_string to match EXACTLY ONCE in
39
86
  * the current content; content-intents overwrite the file whole (that is what
40
87
  * the blocked Write would have done). Multiple intents for one file apply in
41
88
  * recorded order, so a sequence of blocked Edits replays faithfully.
89
+ *
90
+ * Applied (and detected-already-applied) intents are consumed from the log —
91
+ * replay is idempotent across runs. Stale-anchor and pre-D1 skips are NOT
92
+ * consumed: they stay visible until an operator resolves or clears them.
42
93
  */
43
94
  export function applyPendingIntents(projectRoot, file) {
44
95
  const root = resolve(projectRoot);
45
96
  const wanted = file ? resolve(root, file) : null;
46
97
  const result = { applied: [], skipped: [] };
98
+ const consumed = [];
47
99
  for (const intent of readPendingIntents(root)) {
48
100
  const abs = isAbsolute(intent.file_path) ? intent.file_path : resolve(root, intent.file_path);
49
101
  if (wanted && resolve(abs) !== wanted)
@@ -59,8 +111,14 @@ export function applyPendingIntents(projectRoot, file) {
59
111
  continue;
60
112
  }
61
113
  if (typeof edit.content === 'string') {
114
+ if (existsSync(abs) && readFileSync(abs, 'utf-8') === edit.content) {
115
+ result.skipped.push({ file: rel, ts: intent.ts, reason: 'already applied (content identical)' });
116
+ consumed.push(intent);
117
+ continue;
118
+ }
62
119
  writeFileSync(abs, edit.content, 'utf-8');
63
120
  result.applied.push({ file: rel, ts: intent.ts, kind: 'write' });
121
+ consumed.push(intent);
64
122
  continue;
65
123
  }
66
124
  if (!existsSync(abs)) {
@@ -69,6 +127,18 @@ export function applyPendingIntents(projectRoot, file) {
69
127
  }
70
128
  const current = readFileSync(abs, 'utf-8');
71
129
  const oldStr = String(edit.old_string);
130
+ const newStr = String(edit.new_string ?? '');
131
+ // Idempotency guard for insertion-style edits (old_string survives inside
132
+ // new_string): after application the anchor STILL matches, so a naive
133
+ // re-run would insert the hunk again. If the new content is already on
134
+ // disk, the intent has been applied — consume it. Edits whose application
135
+ // removes the anchor never reach this branch falsely (their old is not
136
+ // contained in new).
137
+ if (newStr && newStr.includes(oldStr) && current.includes(newStr)) {
138
+ result.skipped.push({ file: rel, ts: intent.ts, reason: 'already applied (new content present)' });
139
+ consumed.push(intent);
140
+ continue;
141
+ }
72
142
  const count = current.split(oldStr).length - 1;
73
143
  if (count !== 1) {
74
144
  result.skipped.push({
@@ -78,9 +148,11 @@ export function applyPendingIntents(projectRoot, file) {
78
148
  });
79
149
  continue;
80
150
  }
81
- writeFileSync(abs, current.replace(oldStr, String(edit.new_string ?? '')), 'utf-8');
151
+ writeFileSync(abs, current.replace(oldStr, newStr), 'utf-8');
82
152
  result.applied.push({ file: rel, ts: intent.ts, kind: 'replace' });
153
+ consumed.push(intent);
83
154
  }
155
+ consumeIntents(root, consumed);
84
156
  return result;
85
157
  }
86
158
  //# sourceMappingURL=pending-intents.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"pending-intents.js","sourceRoot":"","sources":["../../src/delivery/pending-intents.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"pending-intents.js","sourceRoot":"","sources":["../../src/delivery/pending-intents.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@miller-tech/uap",
3
- "version": "1.161.19",
3
+ "version": "1.161.20",
4
4
  "description": "Autonomous AI agent memory system with CLAUDE.md protocol enforcement",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -23,6 +23,7 @@ intent is logged AND routed. The hook still blocks (exit 2); this only enriches
23
23
  the block message and kicks off the sanctioned path in the background.
24
24
  """
25
25
  import argparse
26
+ import hashlib
26
27
  import json
27
28
  import os
28
29
  import re
@@ -109,12 +110,6 @@ def decide(out: dict, tool: str, args: dict, autoroute_on: bool, seen_files: set
109
110
  "file_path": file_path, "hint": hint, "dedup_key": "", "intent": None}
110
111
 
111
112
  intent = {"ts": int(time.time()), "tool": tool, "file_path": file_path, "hint": hint}
112
- # Dedup on the file when we have one, else on the hint itself. Requiring a
113
- # file_path made an entire class unspawnable: a BASH-routed source-write
114
- # (`cat > app.js <<EOF`) carries a `command`, not a path — so those intents
115
- # were blocked and then silently dropped. The hint is what deliver actually
116
- # runs, so it is the correct spawn key.
117
- dedup_key = file_path or hint
118
113
  # P1 (plan D1): persist the blocked edit's actual old/new content (from the
119
114
  # enforcer's editIntent, falling back to the raw tool args) so the intent
120
115
  # is REPLAYABLE via `uap deliver --pending <file>` instead of a blind hint.
@@ -133,6 +128,19 @@ def decide(out: dict, tool: str, args: dict, autoroute_on: bool, seen_files: set
133
128
  edit_intent = {"content": bash_content}
134
129
  if edit_intent:
135
130
  intent["edit"] = edit_intent
131
+ # Dedup PER CHANGE, not per file: base is the file when we have one, else
132
+ # the hint (a BASH-routed source-write carries a `command`, not a path, and
133
+ # requiring a path made that class unspawnable). When the edit's content is
134
+ # recorded, suffix a hash of it — a bare file-path key marked the FILE seen
135
+ # forever, so every LATER (different) blocked edit to the same file was
136
+ # silently swallowed: never recorded as replayable, never applied (observed
137
+ # 2026-07-18). Identical retries of the same edit still dedup exactly.
138
+ dedup_key = file_path or hint
139
+ if edit_intent:
140
+ change_hash = hashlib.sha1(
141
+ json.dumps(edit_intent, sort_keys=True).encode("utf-8", "replace")
142
+ ).hexdigest()[:12]
143
+ dedup_key = f"{dedup_key}#{change_hash}"
136
144
  # A REPLAYABLE intent (plan D1) carries the blocked edit's exact content, so
137
145
  # it can be applied to disk DETERMINISTICALLY via `uap deliver --pending`
138
146
  # (writeFileSync of the captured content — no model, no blind fan-out). This