@miller-tech/uap 1.175.11 → 1.175.13

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.
@@ -21,6 +21,39 @@ export interface PlanReviewOutcome {
21
21
  }
22
22
  declare function statePath(cwd: string): string;
23
23
  declare function windowSec(): number;
24
+ /**
25
+ * Shared state with the enforcer (validate_plan_on_change.py). The contract:
26
+ *
27
+ * pending { <repo-relative plan path>: <epoch seen> } written by the
28
+ * enforcer when a plan artifact is created or modified; cleared
29
+ * here once that plan has actually been validated.
30
+ * validated { <repo-relative plan path>: <sha256 of the reviewed bytes> }
31
+ *
32
+ * Keying on CONTENT is the point. The old gate keyed on a 300s timestamp and
33
+ * fired on the plan WRITE, which meant the agent was told to validate a plan
34
+ * before it existed: `uap plan validate` found no artifact (or an older one),
35
+ * stamped anyway, and every write for the next five minutes sailed through
36
+ * unread — with nothing gating the build at all. A hash says "these exact bytes
37
+ * were reviewed", so editing the plan afterwards re-arms the gate.
38
+ */
39
+ export interface PlanState {
40
+ validated_at?: number;
41
+ review?: PlanReviewOutcome;
42
+ pending?: Record<string, number>;
43
+ validated?: Record<string, string>;
44
+ }
45
+ /** Path key shared with the enforcer: repo-relative, forward slashes. */
46
+ export declare function planKey(cwd: string, file: string): string;
47
+ export declare function planHash(text: string): string;
48
+ /**
49
+ * Plans the enforcer recorded as touched but which have not been validated
50
+ * since, plus plans that were validated and have since DRIFTED on disk.
51
+ * Anything listed here is what the build gate is waiting on.
52
+ */
53
+ export declare function outstandingPlans(cwd: string): {
54
+ pending: string[];
55
+ drifted: string[];
56
+ };
24
57
  /** Most recently modified plan-like artifact: plans/ dirs first, then cwd. */
25
58
  export declare function findPlanArtifact(cwd: string): string | null;
26
59
  export declare function planCommand(action: string | undefined, options?: PlanOptions, cwd?: string, deps?: PlanDeps): Promise<void>;
@@ -1 +1 @@
1
- {"version":3,"file":"plan.d.ts","sourceRoot":"","sources":["../../src/cli/plan.ts"],"names":[],"mappings":"AA4BA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,iCAAiC,CAAC;AAGpE,MAAM,WAAW,WAAW;IAC1B,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,0EAA0E;IAC1E,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,gFAAgF;IAChF,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,gFAAgF;IAChF,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB;AAED,2EAA2E;AAC3E,MAAM,WAAW,QAAQ;IACvB,cAAc,CAAC,EAAE,YAAY,CAAC;IAC9B,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC;CACjB;AAED,MAAM,WAAW,iBAAiB;IAChC,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAAC;IACpC,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,MAAM,EAAE,CAAC;IACnB,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAKD,iBAAS,SAAS,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAEtC;AACD,iBAAS,SAAS,IAAI,MAAM,CAG3B;AAiBD,8EAA8E;AAC9E,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CA4B3D;AA0GD,wBAAsB,WAAW,CAC/B,MAAM,EAAE,MAAM,GAAG,SAAS,EAC1B,OAAO,GAAE,WAAgB,EACzB,GAAG,GAAE,MAAsB,EAC3B,IAAI,GAAE,QAAa,GAClB,OAAO,CAAC,IAAI,CAAC,CAgEf;AAED,OAAO,EAAE,SAAS,IAAI,aAAa,EAAE,SAAS,IAAI,aAAa,EAAE,CAAC"}
1
+ {"version":3,"file":"plan.d.ts","sourceRoot":"","sources":["../../src/cli/plan.ts"],"names":[],"mappings":"AA6BA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,iCAAiC,CAAC;AAGpE,MAAM,WAAW,WAAW;IAC1B,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,0EAA0E;IAC1E,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,gFAAgF;IAChF,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,gFAAgF;IAChF,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB;AAED,2EAA2E;AAC3E,MAAM,WAAW,QAAQ;IACvB,cAAc,CAAC,EAAE,YAAY,CAAC;IAC9B,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC;CACjB;AAED,MAAM,WAAW,iBAAiB;IAChC,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAAC;IACpC,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,MAAM,EAAE,CAAC;IACnB,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAKD,iBAAS,SAAS,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAEtC;AACD,iBAAS,SAAS,IAAI,MAAM,CAG3B;AAED;;;;;;;;;;;;;;GAcG;AACH,MAAM,WAAW,SAAS;IACxB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,MAAM,CAAC,EAAE,iBAAiB,CAAC;IAC3B,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACjC,SAAS,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CACpC;AAED,yEAAyE;AACzE,wBAAgB,OAAO,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,CAEzD;AAED,wBAAgB,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAE7C;AAWD;;;;GAIG;AACH,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,MAAM,GAAG;IAAE,OAAO,EAAE,MAAM,EAAE,CAAC;IAAC,OAAO,EAAE,MAAM,EAAE,CAAA;CAAE,CActF;AASD,8EAA8E;AAC9E,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CA4B3D;AA0GD,wBAAsB,WAAW,CAC/B,MAAM,EAAE,MAAM,GAAG,SAAS,EAC1B,OAAO,GAAE,WAAgB,EACzB,GAAG,GAAE,MAAsB,EAC3B,IAAI,GAAE,QAAa,GAClB,OAAO,CAAC,IAAI,CAAC,CA8Gf;AAED,OAAO,EAAE,SAAS,IAAI,aAAa,EAAE,SAAS,IAAI,aAAa,EAAE,CAAC"}
package/dist/cli/plan.js CHANGED
@@ -24,8 +24,9 @@
24
24
  * endpoint stamps with a note — the plan gate must never deadlock offline.
25
25
  * A completed review that returns "fail" is the only thing that blocks.
26
26
  */
27
+ import { createHash } from 'crypto';
27
28
  import { mkdirSync, readdirSync, readFileSync, statSync, writeFileSync } from 'fs';
28
- import { join, resolve, sep } from 'path';
29
+ import { join, relative, resolve, sep } from 'path';
29
30
  import { reviewPlanText } from '../delivery/plan-check.js';
30
31
  function stateDir(cwd) {
31
32
  return join(cwd, process.env.UAP_STATE_DIR || '.uap');
@@ -37,14 +38,44 @@ function windowSec() {
37
38
  const w = parseInt(process.env.UAP_PLAN_VALIDATE_WINDOW || '300', 10);
38
39
  return Number.isFinite(w) ? w : 300;
39
40
  }
41
+ /** Path key shared with the enforcer: repo-relative, forward slashes. */
42
+ export function planKey(cwd, file) {
43
+ return relative(resolve(cwd), resolve(cwd, file)).split(sep).join('/');
44
+ }
45
+ export function planHash(text) {
46
+ return createHash('sha256').update(text, 'utf-8').digest('hex');
47
+ }
40
48
  function readState(cwd) {
41
49
  try {
42
- return JSON.parse(readFileSync(statePath(cwd), 'utf-8'));
50
+ const parsed = JSON.parse(readFileSync(statePath(cwd), 'utf-8'));
51
+ return parsed && typeof parsed === 'object' ? parsed : {};
43
52
  }
44
53
  catch {
45
54
  return {};
46
55
  }
47
56
  }
57
+ /**
58
+ * Plans the enforcer recorded as touched but which have not been validated
59
+ * since, plus plans that were validated and have since DRIFTED on disk.
60
+ * Anything listed here is what the build gate is waiting on.
61
+ */
62
+ export function outstandingPlans(cwd) {
63
+ const st = readState(cwd);
64
+ const pending = Object.keys(st.pending ?? {}).sort();
65
+ const drifted = [];
66
+ for (const [key, hash] of Object.entries(st.validated ?? {})) {
67
+ let text;
68
+ try {
69
+ text = readFileSync(join(cwd, key), 'utf-8');
70
+ }
71
+ catch {
72
+ continue; // deleted or unreadable — nothing left to gate on
73
+ }
74
+ if (planHash(text) !== hash)
75
+ drifted.push(key);
76
+ }
77
+ return { pending, drifted: drifted.sort() };
78
+ }
48
79
  /** Mirror of the enforcer's plan-artifact test (validate_plan_on_change.py). */
49
80
  const PLAN_STEM_RE = /(^|[-_. ])plans?([-_. ]|$)/i;
50
81
  function isPlanFile(name) {
@@ -213,7 +244,31 @@ export async function planCommand(action, options = {}, cwd = process.cwd(), dep
213
244
  mkdirSync(dir, { recursive: true });
214
245
  const prev = readState(cwd);
215
246
  const now = Math.floor(Date.now() / 1000);
216
- writeFileSync(statePath(cwd), JSON.stringify({ ...prev, validated_at: now, review }, null, 2));
247
+ const pending = { ...(prev.pending ?? {}) };
248
+ const validated = { ...(prev.validated ?? {}) };
249
+ // Record the hash of what was ACTUALLY reviewed, and clear that plan from
250
+ // the pending set. Only the reviewed plan is cleared: validating one plan
251
+ // must not silently vouch for another the agent also touched.
252
+ // Which plan this validation vouches for. `review.file` is absent whenever
253
+ // the review did not resolve one — most importantly when the review was
254
+ // DISABLED (UAP_PLAN_REVIEW=0 / --no-review), which returns a bare
255
+ // {status:'skipped'}. Keying only off review.file meant `uap plan validate
256
+ // PLAN.md` reported success, recorded nothing, and left the build blocked
257
+ // on the very file the operator had just named. Fall back to the explicit
258
+ // argument, then to the artifact the review would have picked.
259
+ const stamped = review.file ?? explicitFile ?? findPlanArtifact(cwd) ?? undefined;
260
+ if (stamped) {
261
+ const key = planKey(cwd, stamped);
262
+ try {
263
+ validated[key] = planHash(readFileSync(resolve(cwd, stamped), 'utf-8'));
264
+ delete pending[key];
265
+ }
266
+ catch {
267
+ // Unreadable at stamp time — leave it pending rather than record a
268
+ // hash we cannot stand behind.
269
+ }
270
+ }
271
+ writeFileSync(statePath(cwd), JSON.stringify({ ...prev, validated_at: now, review, pending, validated }, null, 2));
217
272
  if (options.json) {
218
273
  console.log(JSON.stringify({ ok: true, validated_at: now, review }));
219
274
  return;
@@ -228,28 +283,47 @@ export async function planCommand(action, options = {}, cwd = process.cwd(), dep
228
283
  else {
229
284
  console.log(`✓ Plan validation recorded (review skipped: ${review.reason}).`);
230
285
  }
231
- console.log(` Plan-file writes are unblocked for ${windowSec()}s — re-run \`uap plan validate\` after that or after a substantive plan change.`);
286
+ const left = outstandingPlans(cwd);
287
+ const remaining = [...left.pending, ...left.drifted];
288
+ if (remaining.length) {
289
+ console.log(` Still awaiting validation before a build can run: ${remaining.join(', ')}`);
290
+ }
291
+ else {
292
+ console.log(' All touched plans are validated — builds are unblocked until a plan changes.');
293
+ }
232
294
  return;
233
295
  }
234
296
  // status (default)
235
297
  const st = readState(cwd);
236
298
  const validatedAt = Number(st.validated_at) || 0;
237
299
  const ageSec = validatedAt ? Math.floor(Date.now() / 1000) - validatedAt : null;
238
- const fresh = ageSec !== null && ageSec <= windowSec();
300
+ const { pending, drifted } = outstandingPlans(cwd);
301
+ // What the gate actually keys on now — not the age of the last stamp.
302
+ const blocked = pending.length > 0 || drifted.length > 0;
239
303
  if (options.json) {
240
- console.log(JSON.stringify({ validated_at: validatedAt || null, ageSec, fresh, window: windowSec(), review: st.review ?? null }));
304
+ console.log(JSON.stringify({
305
+ validated_at: validatedAt || null,
306
+ ageSec,
307
+ blocked,
308
+ pending,
309
+ drifted,
310
+ review: st.review ?? null,
311
+ }));
241
312
  return;
242
313
  }
243
- if (!validatedAt) {
244
- console.log('Plan validation: never recorded. A plan-file write will require `validate the plan` + `uap plan validate`.');
314
+ if (!blocked) {
315
+ console.log(validatedAt
316
+ ? `Plan validation: OK — no plan is awaiting validation (last validated ${ageSec}s ago).`
317
+ : 'Plan validation: nothing pending. Creating or editing a plan will require `validate the plan` before a build.');
318
+ if (st.review?.status) {
319
+ console.log(` Last review: ${st.review.status}${st.review.file ? ` (${st.review.file})` : ''}`);
320
+ }
245
321
  return;
246
322
  }
247
- console.log(`Plan validation: ${fresh ? 'FRESH' : 'STALE'} last validated ${ageSec}s ago (window ${windowSec()}s).`);
248
- if (st.review?.status) {
249
- console.log(` Last review: ${st.review.status}${st.review.file ? ` (${st.review.file})` : ''}`);
250
- }
251
- if (!fresh)
252
- console.log(' Re-run the `validate the plan` prompt, then `uap plan validate`, before editing a plan.');
323
+ console.log('Plan validation: BLOCKING a build cannot run until these are validated:');
324
+ pending.forEach((p) => console.log(` • ${p} (created/modified, never validated)`));
325
+ drifted.forEach((p) => console.log(` ${p} (changed since it was validated)`));
326
+ console.log(' Run the `validate the plan` prompt, then `uap plan validate <file>`.');
253
327
  }
254
328
  export { statePath as planStatePath, windowSec as planWindowSec };
255
329
  //# sourceMappingURL=plan.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"plan.js","sourceRoot":"","sources":["../../src/cli/plan.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,OAAO,EAAE,SAAS,EAAE,WAAW,EAAE,YAAY,EAAE,QAAQ,EAAE,aAAa,EAAE,MAAM,IAAI,CAAC;AACnF,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,GAAG,EAAE,MAAM,MAAM,CAAC;AAE1C,OAAO,EAAE,cAAc,EAA0B,MAAM,2BAA2B,CAAC;AAyBnF,SAAS,QAAQ,CAAC,GAAW;IAC3B,OAAO,IAAI,CAAC,GAAG,EAAE,OAAO,CAAC,GAAG,CAAC,aAAa,IAAI,MAAM,CAAC,CAAC;AACxD,CAAC;AACD,SAAS,SAAS,CAAC,GAAW;IAC5B,OAAO,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAE,iBAAiB,CAAC,CAAC;AAChD,CAAC;AACD,SAAS,SAAS;IAChB,MAAM,CAAC,GAAG,QAAQ,CAAC,OAAO,CAAC,GAAG,CAAC,wBAAwB,IAAI,KAAK,EAAE,EAAE,CAAC,CAAC;IACtE,OAAO,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC;AACtC,CAAC;AAED,SAAS,SAAS,CAAC,GAAW;IAC5B,IAAI,CAAC;QACH,OAAO,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,SAAS,CAAC,GAAG,CAAC,EAAE,OAAO,CAAC,CAAC,CAAC;IAC3D,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,EAAE,CAAC;IACZ,CAAC;AACH,CAAC;AAED,gFAAgF;AAChF,MAAM,YAAY,GAAG,6BAA6B,CAAC;AACnD,SAAS,UAAU,CAAC,IAAY;IAC9B,IAAI,CAAC,IAAI,CAAC,WAAW,EAAE,CAAC,QAAQ,CAAC,KAAK,CAAC;QAAE,OAAO,KAAK,CAAC;IACtD,OAAO,YAAY,CAAC,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC;AAC9C,CAAC;AAED,8EAA8E;AAC9E,MAAM,UAAU,gBAAgB,CAAC,GAAW;IAC1C,MAAM,UAAU,GAA2C,EAAE,CAAC;IAC9D,MAAM,IAAI,GAAG,CAAC,GAAW,EAAE,eAAwB,EAAQ,EAAE;QAC3D,IAAI,OAAO,GAAa,EAAE,CAAC;QAC3B,IAAI,CAAC;YACH,OAAO,GAAG,WAAW,CAAC,GAAG,CAAC,CAAC;QAC7B,CAAC;QAAC,MAAM,CAAC;YACP,OAAO;QACT,CAAC;QACD,KAAK,MAAM,IAAI,IAAI,OAAO,EAAE,CAAC;YAC3B,MAAM,CAAC,GAAG,IAAI,CAAC,GAAG,EAAE,IAAI,CAAC,CAAC;YAC1B,IAAI,CAAC;gBACH,MAAM,EAAE,GAAG,QAAQ,CAAC,CAAC,CAAC,CAAC;gBACvB,IAAI,CAAC,EAAE,CAAC,MAAM,EAAE;oBAAE,SAAS;gBAC3B,IAAI,eAAe,CAAC,CAAC,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,WAAW,EAAE,CAAC,QAAQ,CAAC,KAAK,CAAC,EAAE,CAAC;oBAC5E,UAAU,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,CAAC,EAAE,KAAK,EAAE,EAAE,CAAC,OAAO,EAAE,CAAC,CAAC;gBAClD,CAAC;YACH,CAAC;YAAC,MAAM,CAAC;gBACP,0BAA0B;YAC5B,CAAC;QACH,CAAC;IACH,CAAC,CAAC;IACF,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,OAAO,CAAC,EAAE,KAAK,CAAC,CAAC;IAChC,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,KAAK,CAAC,CAAC;IACxC,IAAI,CAAC,GAAG,EAAE,IAAI,CAAC,CAAC;IAChB,IAAI,UAAU,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,IAAI,CAAC;IACzC,UAAU,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,GAAG,CAAC,CAAC,KAAK,CAAC,CAAC;IAC7C,OAAO,UAAU,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;AAC5B,CAAC;AAED;0EAC0E;AAC1E,SAAS,YAAY,CAAC,IAAc;IAClC,MAAM,EAAE,GAAG,IAAI,CAAC,OAAO,CAAC,UAAU,CAAC,CAAC;IACpC,IAAI,EAAE,KAAK,CAAC,CAAC;QAAE,OAAO,SAAS,CAAC;IAChC,KAAK,MAAM,GAAG,IAAI,IAAI,CAAC,KAAK,CAAC,EAAE,GAAG,CAAC,CAAC,EAAE,CAAC;QACrC,IAAI,CAAC,GAAG,CAAC,UAAU,CAAC,GAAG,CAAC;YAAE,OAAO,GAAG,CAAC;IACvC,CAAC;IACD,OAAO,SAAS,CAAC;AACnB,CAAC;AAED;;;;GAIG;AACH,SAAS,mBAAmB,CAAC,IAAY,EAAE,GAAW;IACpD,IAAI,CAAC,IAAI,CAAC,WAAW,EAAE,CAAC,QAAQ,CAAC,KAAK,CAAC;QAAE,OAAO,2CAA2C,CAAC;IAC5F,MAAM,GAAG,GAAG,OAAO,CAAC,GAAG,EAAE,IAAI,CAAC,CAAC;IAC/B,IAAI,GAAG,KAAK,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,UAAU,CAAC,OAAO,CAAC,GAAG,CAAC,GAAG,GAAG,CAAC,EAAE,CAAC;QAChE,OAAO,0DAA0D,CAAC;IACpE,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC;AAED,0EAA0E;AAC1E,KAAK,UAAU,mBAAmB;IAChC,MAAM,EAAE,kBAAkB,EAAE,GAAG,MAAM,MAAM,CAAC,mCAAmC,CAAC,CAAC;IACjF,MAAM,EAAE,YAAY,EAAE,GAAG,MAAM,MAAM,CAAC,oBAAoB,CAAC,CAAC;IAC5D,MAAM,QAAQ,GACZ,OAAO,CAAC,GAAG,CAAC,qBAAqB,IAAI,OAAO,CAAC,GAAG,CAAC,iBAAiB,IAAI,YAAY,CAAC;IACrF,MAAM,KAAK,GAAG,YAAY,CAAC,QAAQ,CAAC,CAAC;IACrC,IAAI,CAAC,KAAK,EAAE,CAAC;QACX,MAAM,IAAI,KAAK,CAAC,yBAAyB,QAAQ,GAAG,CAAC,CAAC;IACxD,CAAC;IACD,wEAAwE;IACxE,kEAAkE;IAClE,MAAM,EAAE,GAAG,KAAK,CAAC,QAAQ,IAAI,OAAO,CAAC,GAAG,CAAC,sBAAsB,IAAI,EAAE,CAAC;IACtE,IAAI,EAAE,IAAI,CAAC,+FAA+F,CAAC,IAAI,CAAC,EAAE,CAAC,EAAE,CAAC;QACpH,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,qEAAqE,EAAE,MAAM,CAAC,CAAC;IACtG,CAAC;IACD,MAAM,MAAM,GAAG,IAAI,kBAAkB,EAAE,CAAC;IACxC,OAAO,KAAK,EAAE,MAAc,EAAE,EAAE;QAC9B,MAAM,CAAC,GAAG,MAAM,MAAM,CAAC,QAAQ,CAAC,KAAK,EAAE,MAAM,EAAE,EAAE,WAAW,EAAE,CAAC,EAAE,YAAY,EAAE,IAAI,EAAE,CAAC,CAAC;QACvF,OAAO,CAAC,CAAC,OAAO,CAAC;IACnB,CAAC,CAAC;AACJ,CAAC;AAED,KAAK,UAAU,aAAa,CAC1B,GAAW,EACX,YAAgC,EAChC,IAAc;IAEd,IAAI,YAAY,EAAE,CAAC;QACjB,MAAM,OAAO,GAAG,mBAAmB,CAAC,YAAY,EAAE,GAAG,CAAC,CAAC;QACvD,IAAI,OAAO;YAAE,OAAO,EAAE,MAAM,EAAE,SAAS,EAAE,IAAI,EAAE,YAAY,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,EAAE,OAAO,EAAE,CAAC;IAC/F,CAAC;IACD,MAAM,IAAI,GAAG,YAAY,IAAI,gBAAgB,CAAC,GAAG,CAAC,IAAI,SAAS,CAAC;IAChE,IAAI,CAAC,IAAI,EAAE,CAAC;QACV,OAAO,EAAE,MAAM,EAAE,SAAS,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,EAAE,wBAAwB,EAAE,CAAC;IAC/E,CAAC;IACD,IAAI,IAAI,GAAG,EAAE,CAAC;IACd,IAAI,CAAC;QACH,IAAI,GAAG,YAAY,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;IACrC,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,EAAE,MAAM,EAAE,SAAS,EAAE,IAAI,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,EAAE,kBAAkB,IAAI,EAAE,EAAE,CAAC;IACrF,CAAC;IACD,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,EAAE,CAAC;QACjB,OAAO,EAAE,MAAM,EAAE,SAAS,EAAE,IAAI,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,EAAE,wBAAwB,EAAE,CAAC;IACrF,CAAC;IACD,IAAI,QAAsB,CAAC;IAC3B,IAAI,CAAC;QACH,QAAQ,GAAG,IAAI,CAAC,cAAc,IAAI,CAAC,MAAM,mBAAmB,EAAE,CAAC,CAAC;IAClE,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,OAAO;YACL,MAAM,EAAE,SAAS;YACjB,IAAI;YACJ,QAAQ,EAAE,EAAE;YACZ,MAAM,EAAE,yBAAyB,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,GAAG;SACrF,CAAC;IACJ,CAAC;IACD,yEAAyE;IACzE,qEAAqE;IACrE,0EAA0E;IAC1E,8DAA8D;IAC9D,IAAI,WAAW,GAAkB,IAAI,CAAC;IACtC,MAAM,QAAQ,GAAiB,KAAK,EAAE,MAAM,EAAE,EAAE;QAC9C,IAAI,GAAW,CAAC;QAChB,IAAI,CAAC;YACH,GAAG,GAAG,MAAM,QAAQ,CAAC,MAAM,CAAC,CAAC;QAC/B,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,WAAW,GAAG,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;YAC/D,MAAM,GAAG,CAAC;QACZ,CAAC;QACD,IAAI,CAAC,GAAG,IAAI,CAAC,GAAG,CAAC,IAAI,EAAE;YAAE,WAAW,GAAG,uCAAuC,CAAC;QAC/E,OAAO,GAAG,CAAC;IACb,CAAC,CAAC;IACF,MAAM,OAAO,GAAsB,MAAM,cAAc,CAAC,IAAI,EAAE,QAAQ,CAAC,CAAC;IACxE,IAAI,WAAW,EAAE,CAAC;QAChB,OAAO,EAAE,MAAM,EAAE,SAAS,EAAE,IAAI,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,EAAE,yBAAyB,WAAW,GAAG,EAAE,CAAC;IACpG,CAAC;IACD,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,OAAO,CAAC,QAAQ,EAAE,CAAC;AACvE,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,WAAW,CAC/B,MAA0B,EAC1B,UAAuB,EAAE,EACzB,MAAc,OAAO,CAAC,GAAG,EAAE,EAC3B,OAAiB,EAAE;IAEnB,IAAI,MAAM,KAAK,UAAU,EAAE,CAAC;QAC1B,MAAM,YAAY,GAAG,CAAC,OAAO,CAAC,MAAM,IAAI,IAAI,CAAC,IAAI,OAAO,CAAC,GAAG,CAAC,eAAe,KAAK,GAAG,CAAC;QACrF,MAAM,KAAK,GAAG,OAAO,CAAC,KAAK,KAAK,IAAI,IAAI,OAAO,CAAC,GAAG,CAAC,qBAAqB,KAAK,GAAG,CAAC;QAClF,MAAM,YAAY,GAAG,OAAO,CAAC,IAAI,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,YAAY,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC;QACvF,MAAM,MAAM,GAAG,YAAY;YACzB,CAAC,CAAC,MAAM,aAAa,CAAC,GAAG,EAAE,YAAY,EAAE,IAAI,CAAC;YAC9C,CAAC,CAAE,EAAE,MAAM,EAAE,SAAS,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,EAAE,iBAAiB,EAAwB,CAAC;QAE1F,IAAI,MAAM,CAAC,MAAM,KAAK,MAAM,IAAI,CAAC,KAAK,EAAE,CAAC;YACvC,IAAI,OAAO,CAAC,IAAI,EAAE,CAAC;gBACjB,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,SAAS,CAAC,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,CAAC,CAAC,CAAC;YACrD,CAAC;iBAAM,CAAC;gBACN,OAAO,CAAC,GAAG,CAAC,mDAAmD,MAAM,CAAC,IAAI,IAAI,CAAC,CAAC;gBAChF,MAAM,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC,CAAC;gBACnE,OAAO,CAAC,GAAG,CAAC,gGAAgG,CAAC,CAAC;YAChH,CAAC;YACD,OAAO,CAAC,QAAQ,GAAG,CAAC,CAAC;YACrB,OAAO;QACT,CAAC;QAED,MAAM,GAAG,GAAG,QAAQ,CAAC,GAAG,CAAC,CAAC;QAC1B,SAAS,CAAC,GAAG,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;QACpC,MAAM,IAAI,GAAG,SAAS,CAAC,GAAG,CAAC,CAAC;QAC5B,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC,CAAC;QAC1C,aAAa,CAAC,SAAS,CAAC,GAAG,CAAC,EAAE,IAAI,CAAC,SAAS,CAAC,EAAE,GAAG,IAAI,EAAE,YAAY,EAAE,GAAG,EAAE,MAAM,EAAE,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC;QAC/F,IAAI,OAAO,CAAC,IAAI,EAAE,CAAC;YACjB,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,SAAS,CAAC,EAAE,EAAE,EAAE,IAAI,EAAE,YAAY,EAAE,GAAG,EAAE,MAAM,EAAE,CAAC,CAAC,CAAC;YACrE,OAAO;QACT,CAAC;QACD,IAAI,MAAM,CAAC,MAAM,KAAK,MAAM,EAAE,CAAC;YAC7B,OAAO,CAAC,GAAG,CAAC,yBAAyB,MAAM,CAAC,IAAI,0BAA0B,CAAC,CAAC;QAC9E,CAAC;aAAM,IAAI,MAAM,CAAC,MAAM,KAAK,MAAM,EAAE,CAAC;YACpC,OAAO,CAAC,GAAG,CAAC,iDAAiD,MAAM,CAAC,IAAI,IAAI,CAAC,CAAC;YAC9E,MAAM,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC,CAAC;QACrE,CAAC;aAAM,CAAC;YACN,OAAO,CAAC,GAAG,CAAC,+CAA+C,MAAM,CAAC,MAAM,IAAI,CAAC,CAAC;QAChF,CAAC;QACD,OAAO,CAAC,GAAG,CACT,wCAAwC,SAAS,EAAE,iFAAiF,CACrI,CAAC;QACF,OAAO;IACT,CAAC;IAED,mBAAmB;IACnB,MAAM,EAAE,GAAG,SAAS,CAAC,GAAG,CAAC,CAAC;IAC1B,MAAM,WAAW,GAAG,MAAM,CAAC,EAAE,CAAC,YAAY,CAAC,IAAI,CAAC,CAAC;IACjD,MAAM,MAAM,GAAG,WAAW,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC,GAAG,WAAW,CAAC,CAAC,CAAC,IAAI,CAAC;IAChF,MAAM,KAAK,GAAG,MAAM,KAAK,IAAI,IAAI,MAAM,IAAI,SAAS,EAAE,CAAC;IACvD,IAAI,OAAO,CAAC,IAAI,EAAE,CAAC;QACjB,OAAO,CAAC,GAAG,CACT,IAAI,CAAC,SAAS,CAAC,EAAE,YAAY,EAAE,WAAW,IAAI,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,SAAS,EAAE,EAAE,MAAM,EAAE,EAAE,CAAC,MAAM,IAAI,IAAI,EAAE,CAAC,CACrH,CAAC;QACF,OAAO;IACT,CAAC;IACD,IAAI,CAAC,WAAW,EAAE,CAAC;QACjB,OAAO,CAAC,GAAG,CAAC,4GAA4G,CAAC,CAAC;QAC1H,OAAO;IACT,CAAC;IACD,OAAO,CAAC,GAAG,CAAC,oBAAoB,KAAK,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,OAAO,qBAAqB,MAAM,iBAAiB,SAAS,EAAE,KAAK,CAAC,CAAC;IACvH,IAAI,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC;QACtB,OAAO,CAAC,GAAG,CAAC,kBAAkB,EAAE,CAAC,MAAM,CAAC,MAAM,GAAG,EAAE,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,KAAK,EAAE,CAAC,MAAM,CAAC,IAAI,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;IACnG,CAAC;IACD,IAAI,CAAC,KAAK;QAAE,OAAO,CAAC,GAAG,CAAC,2FAA2F,CAAC,CAAC;AACvH,CAAC;AAED,OAAO,EAAE,SAAS,IAAI,aAAa,EAAE,SAAS,IAAI,aAAa,EAAE,CAAC"}
1
+ {"version":3,"file":"plan.js","sourceRoot":"","sources":["../../src/cli/plan.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.175.11",
3
+ "version": "1.175.13",
4
4
  "description": "Autonomous AI agent memory system with CLAUDE.md protocol enforcement",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -21,7 +21,7 @@
21
21
  "start": "node dist/bin/cli.js",
22
22
  "test": "vitest",
23
23
  "test:ci": "vitest run",
24
- "test:enforcers": "python3 -m unittest tools.agents.tests.test_enforcement_self_protect tools.agents.tests.test_schema_diff_gate tools.agents.tests.test_stream_telemetry tools.agents.tests.test_project_telemetry_events tools.agents.tests.test_workdir_scope_enforcer tools.agents.tests.test_path_containment tools.agents.tests.test_path_normalizer_hardened tools.agents.tests.test_sandbox_tool_strip tools.agents.tests.test_proxy_env_loader tools.agents.tests.test_stream_required_tool tools.agents.tests.test_tool_call_wrapper_profiles tools.agents.tests.test_tool_convert_cache tools.agents.tests.test_doubling_break tools.agents.tests.test_error_loop_ignores_correctives tools.agents.tests.test_attractor_detection tools.agents.tests.test_client_disconnect tools.agents.tests.test_confidence_escalation tools.agents.tests.test_coordination_ban tools.agents.tests.test_coordination_early_ban tools.agents.tests.test_cycle_break_exploration tools.agents.tests.test_deferral_break tools.agents.tests.test_deliver_autoroute tools.agents.tests.test_delivery_enforcement_all_langs tools.agents.tests.test_delivery_enforcement_exemptions tools.agents.tests.test_delivery_enforcement_filepath tools.agents.tests.test_delivery_enforcement_web_and_bash tools.agents.tests.test_disconnect_watcher tools.agents.tests.test_empty_maxtokens_recovery tools.agents.tests.test_empty_tool_loop_break tools.agents.tests.test_enforcer_escape_hatches tools.agents.tests.test_error_loop_break tools.agents.tests.test_finalize_suppression tools.agents.tests.test_malformed_unclosed_think tools.agents.tests.test_mandate_beats_recon tools.agents.tests.test_mandate_deliver tools.agents.tests.test_overflow_truncate_count_tokens tools.agents.tests.test_passthrough_oauth tools.agents.tests.test_project_telemetry tools.agents.tests.test_proxy_auth_headers tools.agents.tests.test_prune_preserve_force_write tools.agents.tests.test_recon_deliver_gate tools.agents.tests.test_session_admission tools.agents.tests.test_stream_heartbeat tools.agents.tests.test_stuck_break_reattach tools.agents.tests.test_turn_count_breaker_periodic tools.agents.tests.test_upstream_chokepoint tools.agents.tests.test_vision_passthrough tools.agents.tests.test_worktree_required tools.agents.tests.test_enforcer_suite_coverage",
24
+ "test:enforcers": "python3 -m unittest tools.agents.tests.test_enforcement_self_protect tools.agents.tests.test_schema_diff_gate tools.agents.tests.test_stream_telemetry tools.agents.tests.test_project_telemetry_events tools.agents.tests.test_workdir_scope_enforcer tools.agents.tests.test_path_containment tools.agents.tests.test_path_normalizer_hardened tools.agents.tests.test_sandbox_tool_strip tools.agents.tests.test_proxy_env_loader tools.agents.tests.test_stream_required_tool tools.agents.tests.test_tool_call_wrapper_profiles tools.agents.tests.test_tool_convert_cache tools.agents.tests.test_doubling_break tools.agents.tests.test_error_loop_ignores_correctives tools.agents.tests.test_attractor_detection tools.agents.tests.test_client_disconnect tools.agents.tests.test_confidence_escalation tools.agents.tests.test_coordination_ban tools.agents.tests.test_coordination_early_ban tools.agents.tests.test_cycle_break_exploration tools.agents.tests.test_deferral_break tools.agents.tests.test_deliver_autoroute tools.agents.tests.test_delivery_enforcement_all_langs tools.agents.tests.test_delivery_enforcement_exemptions tools.agents.tests.test_delivery_enforcement_filepath tools.agents.tests.test_delivery_enforcement_web_and_bash tools.agents.tests.test_disconnect_watcher tools.agents.tests.test_empty_maxtokens_recovery tools.agents.tests.test_empty_tool_loop_break tools.agents.tests.test_enforcer_escape_hatches tools.agents.tests.test_error_loop_break tools.agents.tests.test_finalize_suppression tools.agents.tests.test_malformed_unclosed_think tools.agents.tests.test_mandate_beats_recon tools.agents.tests.test_mandate_deliver tools.agents.tests.test_overflow_truncate_count_tokens tools.agents.tests.test_passthrough_oauth tools.agents.tests.test_project_telemetry tools.agents.tests.test_proxy_auth_headers tools.agents.tests.test_prune_preserve_force_write tools.agents.tests.test_recon_deliver_gate tools.agents.tests.test_session_admission tools.agents.tests.test_stream_heartbeat tools.agents.tests.test_stuck_break_reattach tools.agents.tests.test_turn_count_breaker_periodic tools.agents.tests.test_upstream_chokepoint tools.agents.tests.test_vision_passthrough tools.agents.tests.test_worktree_required tools.agents.tests.test_enforcer_suite_coverage tools.agents.tests.test_validate_plan_gate",
25
25
  "test:coverage": "vitest --coverage",
26
26
  "bench": "vitest --config vitest.bench.config.ts",
27
27
  "lint": "eslint src --ext .ts",
@@ -1,41 +1,104 @@
1
1
  #!/usr/bin/env python3
2
- """validate-plan-on-change enforcer: ALWAYS validate a plan after creating or
3
- modifying it.
2
+ """validate-plan-on-change: the plan gate fires BEFORE a build, not on the write.
4
3
 
5
- A Write/Edit to a PLAN artifact (anything under a `plans/` directory, or a file
6
- whose name is plan-like: `PLAN.md`, `*-plan.md`, `plan-*.md`, `implementation-plan.md`,
7
- `*.plan.md`) is BLOCKED unless the agent has run the `validate the plan` prompt
8
- recently and recorded it with `uap plan validate`. The block message is the
9
- self-prompt: it tells the agent to validate the plan first, then retry.
4
+ The old rule blocked a Write/Edit to a plan artifact unless `uap plan validate`
5
+ had run in the last 300 seconds. That asked the agent to validate a plan that did
6
+ not exist yet: the review found no artifact (or an older one), recorded itself as
7
+ "skipped", stamped anyway, and for the next five minutes any plan content went in
8
+ unread while nothing gated the build at all. The plan that actually got
9
+ implemented was never reviewed.
10
10
 
11
- "Recently" = within UAP_PLAN_VALIDATE_WINDOW seconds (default 300) of the last
12
- `uap plan validate`, so a burst of edits in one planning session shares a single
13
- validation, but a plan touched after a gap re-validates.
11
+ So the gate moved to where it belongs:
14
12
 
15
- State: `.uap/plan_state.json` {"validated_at": <epoch>}. Escape hatch (justify in
16
- the plan/PR): UAP_PLAN_VALIDATE_OFF=1.
13
+ - creating or editing a plan is ALLOWED, and records the plan as pending
14
+ - a BUILD / EXECUTE / DEPLOY command is BLOCKED while any plan is pending, or
15
+ while a plan that WAS validated has since drifted on disk
16
+ - the refusal carries the `validate the plan` self-prompt
17
+
18
+ State lives in `.uap/plan_state.json` (honouring UAP_STATE_DIR) and is shared
19
+ with `uap plan validate` in src/cli/plan.ts:
20
+
21
+ pending { "<repo-relative path>": <epoch seen> }
22
+ validated { "<repo-relative path>": "<sha256 of the reviewed bytes>" }
23
+
24
+ Keying on CONTENT rather than a clock is the point: "these exact bytes were
25
+ reviewed" cannot be satisfied by validating an empty file and then writing the
26
+ real plan, which is precisely how the old window was defeated.
27
+
28
+ Escape hatch (justify in the plan/PR): UAP_PLAN_VALIDATE_OFF=1.
17
29
  """
18
30
  from __future__ import annotations
19
31
 
32
+ import hashlib
20
33
  import json
21
34
  import os
22
35
  import re
36
+ import sys
23
37
  import time
24
38
  from pathlib import Path
25
- import sys
26
39
 
27
40
  sys.path.insert(0, str(Path(__file__).parent))
28
- from _common import emit, parse_cli # noqa: E402
41
+ from _common import emit, parse_cli, scannable_command # noqa: E402
29
42
 
30
43
  EDIT_OPS = {"Write", "Edit", "MultiEdit", "write", "edit", "multiedit"}
31
- STATE = Path(os.environ.get("UAP_STATE_DIR", ".uap")) / "plan_state.json"
32
- DEFAULT_WINDOW = 300
44
+ BASH_OPS = {"Bash", "bash", "run_bash", "shell"}
33
45
 
34
46
  # A plan-like filename STEM (the part before `.md`): `plan`, `foo-plan`, `plan-v2`,
35
- # `implementation_plan`, `foo.plan`. Deliberately does NOT match `planning`, `plans`
36
- # (plural handled by the plans/ dir rule), or `explanation`.
47
+ # `implementation_plan`, `foo.plan`. Deliberately does NOT match `planning`,
48
+ # `plans` (plural handled by the plans/ dir rule), or `explanation`.
37
49
  PLAN_STEM_RE = re.compile(r"(^|[-_. ])plans?([-_. ]|$)", re.IGNORECASE)
38
50
 
51
+ # Commands that IMPLEMENT the plan. Matched anywhere in the command, not just at
52
+ # the start: `cd sub && npm run build` is a build, and a gate that only checks
53
+ # the prefix is avoidable by prepending anything at all.
54
+ #
55
+ # Word boundaries keep `make` from matching `cmake` or `makefile`. Everything
56
+ # NOT listed here is allowed — there is no second allowlist to keep in sync, and
57
+ # tests/linters/reads therefore stay free by construction. That matters: you
58
+ # cannot review a plan if you cannot inspect the tree or run its tests.
59
+ BUILD_RE = re.compile(
60
+ r"\b("
61
+ r"uap\s+deliver"
62
+ r"|npm\s+run\s+build|npm\s+start"
63
+ r"|(?:yarn|pnpm)\s+(?:run\s+)?build"
64
+ r"|make"
65
+ r"|cargo\s+(?:build|run)"
66
+ r"|go\s+(?:build|run)"
67
+ r"|mvn\s+(?:package|install)"
68
+ r"|gradle\s+build"
69
+ r"|docker\s+build"
70
+ r"|docker[\s-]compose\s+up"
71
+ r"|terraform\s+apply"
72
+ r"|kubectl\s+apply"
73
+ r"|helm\s+(?:install|upgrade)"
74
+ r")\b",
75
+ re.IGNORECASE,
76
+ )
77
+
78
+
79
+ def _state_path() -> Path:
80
+ """`.uap/plan_state.json`, honouring UAP_STATE_DIR (absolute or relative)."""
81
+ return Path(os.environ.get("UAP_STATE_DIR", ".uap")) / "plan_state.json"
82
+
83
+
84
+ def _load_state() -> dict:
85
+ try:
86
+ data = json.loads(_state_path().read_text())
87
+ return data if isinstance(data, dict) else {}
88
+ except Exception: # noqa: BLE001 - unreadable state must not break the tool call
89
+ return {}
90
+
91
+
92
+ def _save_state(state: dict) -> None:
93
+ """Best-effort persist. A gate that crashes on an unwritable dir is worse
94
+ than one that forgets — the write it was recording still happened."""
95
+ try:
96
+ path = _state_path()
97
+ path.parent.mkdir(parents=True, exist_ok=True)
98
+ path.write_text(json.dumps(state, indent=2))
99
+ except Exception: # noqa: BLE001
100
+ pass
101
+
39
102
 
40
103
  def _target(args: dict) -> str:
41
104
  for k in ("file_path", "path", "notebook_path", "filePath", "target"):
@@ -48,57 +111,89 @@ def _is_plan_file(target: str) -> bool:
48
111
  if not target:
49
112
  return False
50
113
  posix = target.replace(os.sep, "/")
51
- # Any file inside a plans/ directory.
52
- if re.search(r"(^|/)plans?/", posix, re.IGNORECASE):
114
+ if re.search(r"(^|/)plans?/", posix, re.IGNORECASE): # any file under plans/
53
115
  return True
54
116
  name = posix.rsplit("/", 1)[-1]
55
117
  if not name.lower().endswith(".md"):
56
118
  return False
57
- stem = name[:-3]
58
- return bool(PLAN_STEM_RE.search(stem))
119
+ return bool(PLAN_STEM_RE.search(name[:-3]))
59
120
 
60
121
 
61
- def _validated_at() -> float:
122
+ def _key(target: str) -> str:
123
+ """Repo-relative, forward-slashed — the shape `uap plan validate` records."""
124
+ posix = target.replace(os.sep, "/")
62
125
  try:
63
- return float(json.loads(STATE.read_text()).get("validated_at", 0))
126
+ rel = os.path.relpath(posix, os.getcwd())
127
+ if not rel.startswith(".."):
128
+ posix = rel.replace(os.sep, "/")
64
129
  except Exception: # noqa: BLE001
65
- return 0.0
130
+ pass
131
+ return posix.lstrip("./") if posix.startswith("./") else posix
66
132
 
67
133
 
68
- def main() -> None:
69
- op, args = parse_cli()
70
- if os.environ.get("UAP_PLAN_VALIDATE_OFF") == "1":
71
- emit(True, "UAP_PLAN_VALIDATE_OFF override set")
72
- if op not in EDIT_OPS:
73
- emit(True, "not a file-write operation")
74
-
75
- target = _target(args)
76
- if not _is_plan_file(target):
77
- emit(True, "not a plan artifact")
78
-
134
+ def _sha(path: str) -> str | None:
79
135
  try:
80
- window = int(os.environ.get("UAP_PLAN_VALIDATE_WINDOW", DEFAULT_WINDOW))
81
- except ValueError:
82
- window = DEFAULT_WINDOW
136
+ return hashlib.sha256(Path(path).read_bytes()).hexdigest()
137
+ except Exception: # noqa: BLE001
138
+ return None
83
139
 
84
- age = time.time() - _validated_at()
85
- if age <= window:
86
- emit(True, f"plan validated {int(age)}s ago (within {window}s)")
87
140
 
141
+ def _refuse(paths: list[str], why: str) -> None:
142
+ listed = ", ".join(sorted(paths))
88
143
  emit(
89
144
  False,
90
- "validate-plan-on-change: you are creating or modifying a plan "
91
- f"('{target}') but have NOT validated it. ALWAYS validate a plan after "
92
- "creating or modifying it.\n"
145
+ f"validate-plan-on-change: {why} ({listed}). A plan must be validated "
146
+ "before the work it describes is built.\n"
93
147
  " 1. Run the prompt `validate the plan` — review assumptions, gaps, "
94
148
  "risks, and whether it still matches the request.\n"
95
- " 2. Record it: `uap plan validate`.\n"
96
- " 3. Retry this write.\n"
97
- "(One validation covers a burst of edits for "
98
- f"{window}s; override with UAP_PLAN_VALIDATE_OFF=1.)",
149
+ f" 2. Record it: `uap plan validate {sorted(paths)[0]}`.\n"
150
+ " 3. Retry this command.\n"
151
+ "(Escape hatch, justify in the plan/PR: UAP_PLAN_VALIDATE_OFF=1.)",
99
152
  inject_prompt="validate the plan",
100
153
  )
101
154
 
102
155
 
156
+ def main() -> None:
157
+ op, args = parse_cli()
158
+ if os.environ.get("UAP_PLAN_VALIDATE_OFF") == "1":
159
+ emit(True, "UAP_PLAN_VALIDATE_OFF override set")
160
+
161
+ # A plan write is never blocked — it is RECORDED. Blocking here is what made
162
+ # the old gate ask for validation of a file that did not exist.
163
+ if op in EDIT_OPS:
164
+ target = _target(args)
165
+ if not _is_plan_file(target):
166
+ emit(True, "not a plan artifact")
167
+ state = _load_state()
168
+ pending = state.get("pending") or {}
169
+ pending[_key(target)] = int(time.time())
170
+ state["pending"] = pending
171
+ _save_state(state)
172
+ emit(True, f"plan write recorded — validation required before a build ({_key(target)})")
173
+
174
+ if op not in BASH_OPS:
175
+ emit(True, "not a gated operation")
176
+
177
+ command = str(args.get("command") or args.get("cmd") or "")
178
+ # scannable_command strips heredoc bodies and quoted payloads, so a build
179
+ # phrase inside DATA (`echo 'npm run build'`) is not a build.
180
+ if not BUILD_RE.search(scannable_command(command)):
181
+ emit(True, "not a build/execute/deploy command")
182
+
183
+ state = _load_state()
184
+ pending = list((state.get("pending") or {}).keys())
185
+ if pending:
186
+ _refuse(pending, "these plans were created or modified and never validated")
187
+
188
+ drifted = [
189
+ key for key, recorded in (state.get("validated") or {}).items()
190
+ if (_sha(key) is not None and _sha(key) != recorded)
191
+ ]
192
+ if drifted:
193
+ _refuse(drifted, "these plans changed after they were validated")
194
+
195
+ emit(True, "no plan is awaiting validation")
196
+
197
+
103
198
  if __name__ == "__main__":
104
199
  main()
@@ -7,34 +7,72 @@
7
7
 
8
8
  ## Rule
9
9
 
10
- A `Write` or `Edit` to a **plan artifact** is BLOCKED unless the plan has been
11
- validated recently. A plan artifact is any file under a `plans/` directory or a
12
- file with a plan-like name (`PLAN.md`, `*-plan.md`, `plan-*.md`,
13
- `implementation-plan.md`, `*.plan.md`). `planning.md`, `explanation.md`, and
14
- plural-but-unrelated names are NOT plan artifacts.
10
+ Creating or modifying a **plan artifact** is ALLOWED, and records that plan as
11
+ awaiting validation. A **build, execute or deploy** command is then BLOCKED
12
+ until the plan has been validated.
15
13
 
16
- On a blocked write the agent MUST:
14
+ A plan artifact is any file under a `plans/` directory, or a file with a
15
+ plan-like name (`PLAN.md`, `*-plan.md`, `plan-*.md`, `implementation-plan.md`,
16
+ `*.plan.md`). `planning.md`, `explanation.md`, and plural-but-unrelated names
17
+ are NOT plan artifacts.
18
+
19
+ On a blocked build the agent MUST:
17
20
 
18
21
  1. Run the prompt `validate the plan` — review assumptions, gaps, risks, and
19
22
  whether the plan still matches the request.
20
- 2. Record the validation: `uap plan validate`.
21
- 3. Retry the write.
23
+ 2. Record the validation: `uap plan validate <file>`.
24
+ 3. Retry the command.
22
25
 
23
- One validation covers a burst of edits for `UAP_PLAN_VALIDATE_WINDOW` seconds
24
- (default 300), so iterative edits in a single planning session don't re-prompt;
25
- a plan touched after that gap re-validates.
26
+ Validation is recorded against the **content hash** of the plan that was
27
+ reviewed. Editing that plan afterwards re-arms the gate. Reads, tests, linters
28
+ and type-checks are never gated — you cannot review a plan if you cannot inspect
29
+ the tree or run its tests.
26
30
 
27
31
  ## Why
28
32
 
29
- User directive: "force yourself to prompt `validate the plan` after any plan is
30
- created or modified — you should ALWAYS validate the plan." Plans drift from the
31
- request as they grow; an unvalidated plan ships last-mile gaps into code. Making
32
- validation a hard gate at the moment the plan changes catches those gaps while
33
- they are cheapest to fix. (Supersedes the older `validate-plan-before-build`,
34
- which never fired because nothing set its `plan_ready` marker.)
33
+ User directive: "once a plan is created the LLM is prompted to `validate the
34
+ plan` before execution/build."
35
+
36
+ The gate used to fire on the plan WRITE, keyed to a 300-second window. That
37
+ asked the agent to validate a plan before it existed: `uap plan validate` found
38
+ no artifact (or an older one), recorded the review as "skipped", stamped anyway,
39
+ and for the next five minutes any plan content could be written unread — while
40
+ nothing gated the build at all. The plan that actually got implemented was never
41
+ reviewed.
42
+
43
+ Moving the gate to the build fixes both halves. There is now something real to
44
+ review when the prompt fires, and the thing being gated is the work itself.
45
+ Keying on content rather than a clock closes the remaining hole: "these exact
46
+ bytes were reviewed" cannot be satisfied by validating an empty file first.
47
+
48
+ (Supersedes the older `validate-plan-before-build`, which never fired because
49
+ nothing set its `plan_ready` marker.)
35
50
 
36
51
  ## Enforcement
37
52
 
38
- Python enforcer `validate_plan_on_change.py`. State in `.uap/plan_state.json`
39
- (`{"validated_at": <epoch>}`, written by `uap plan validate`). Escape hatch,
40
- justify in the plan/PR: `UAP_PLAN_VALIDATE_OFF=1`.
53
+ Python enforcer `validate_plan_on_change.py`, exiting 2 on a refusal with
54
+ `inject_prompt: "validate the plan"`.
55
+
56
+ State in `.uap/plan_state.json` (honours `UAP_STATE_DIR`), shared with
57
+ `uap plan validate`:
58
+
59
+ ```json
60
+ {
61
+ "pending": { "<repo-relative path>": "<epoch seen>" },
62
+ "validated": { "<repo-relative path>": "<sha256 of the reviewed bytes>" }
63
+ }
64
+ ```
65
+
66
+ Gated commands: `uap deliver`, `npm run build`, `npm start`, `yarn`/`pnpm build`,
67
+ `make`, `cargo build|run`, `go build|run`, `mvn package|install`,
68
+ `gradle build`, `docker build`, `docker compose up`, `terraform apply`,
69
+ `kubectl apply`, `helm install|upgrade` — matched anywhere in the command, so
70
+ `cd sub && npm run build` is gated, and matched against the command with quoted
71
+ data and heredoc bodies stripped, so `echo 'npm run build'` is not.
72
+
73
+ Everything not listed is allowed; there is no second allowlist to keep in sync.
74
+
75
+ Escape hatch, justify in the plan/PR: `UAP_PLAN_VALIDATE_OFF=1`.
76
+
77
+ `uap plan status` reports exactly what the gate is waiting on (pending plans and
78
+ plans that have drifted since validation).