@gtrabanco/pi-agentic-workflow 4.10.0 → 5.0.0-canary.20261102.1

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 (37) hide show
  1. package/README.md +2 -2
  2. package/dist/config/defaults.js +0 -8
  3. package/dist/config/merge.js +0 -20
  4. package/dist/config/schema.js +1 -103
  5. package/dist/config/types.d.ts +0 -60
  6. package/dist/extension/factory.d.ts +3 -13
  7. package/dist/extension/factory.js +1 -5
  8. package/dist/extension/index.js +0 -24
  9. package/package.json +4 -4
  10. package/skills/execute-phase/SKILL.md +2 -2
  11. package/skills/execute-phase/references/PREFLIGHT.md +2 -2
  12. package/skills/init-workspace/SKILL.md +15 -8
  13. package/skills/init-workspace/references/BOOTSTRAP_WRITE.md +13 -7
  14. package/skills/init-workspace/references/UPGRADE.md +34 -14
  15. package/skills/phase-contract/SKILL.md +1 -1
  16. package/skills/pre-execution-review/references/LEDGERS.md +1 -1
  17. package/skills/pre-execution-review/references/POLICY.md +2 -2
  18. package/skills/product-audit/SKILL.md +6 -1
  19. package/skills/product-audit/references/AUDIT_DIMENSIONS.md +2 -0
  20. package/skills/product-audit/references/AUDIT_PROCESS.md +4 -1
  21. package/skills/review-code/SKILL.md +16 -1
  22. package/skills/review-implementation/references/CLASSIFY.md +1 -1
  23. package/skills/triage-issue/SKILL.md +3 -2
  24. package/skills/unit-lane/SKILL.md +3 -3
  25. package/skills/unit-lane/references/RELEASE.md +4 -4
  26. package/skills/workflow-status/SKILL.md +2 -2
  27. package/skills/workflow-status/references/SENSOR_CORE.md +4 -3
  28. package/dist/extension/ci-guard.d.ts +0 -29
  29. package/dist/extension/ci-guard.js +0 -39
  30. package/dist/extension/cost-guard.d.ts +0 -30
  31. package/dist/extension/cost-guard.js +0 -50
  32. package/dist/extension/guard-util.d.ts +0 -26
  33. package/dist/extension/guard-util.js +0 -35
  34. package/dist/extension/history-guard.d.ts +0 -59
  35. package/dist/extension/history-guard.js +0 -132
  36. package/dist/extension/merge-guard.d.ts +0 -36
  37. package/dist/extension/merge-guard.js +0 -79
package/README.md CHANGED
@@ -326,9 +326,9 @@ dialog.
326
326
 
327
327
  - Verified against the peer floor Pi 0.99.1 (2026-09-29) (`pi install`, package skills, friendly
328
328
  command registration, routed set/clear, settings console round-trip, `sendUserMessage`
329
- with prompt template expansion). Also verified against Pi 1.2.0 (2026-10-14) via a local
329
+ with prompt template expansion). Also verified against Pi 1.5.0 (2026-11-02) via a local
330
330
  two-leg `tsc --noEmit` run (legs `0.99.1` + `latest`, the `latest` leg resolving to pi
331
- 1.2.0; COMPAT-OK); continuously against the
331
+ 1.5.0; COMPAT-OK); continuously against the
332
332
  `latest` dist-tag by the `pi-compat-guard` PR gate (the leg is a moving tripwire,
333
333
  not a pinned version — a breaking release shows red here first).
334
334
  - The package declares Pi as a peer dependency; it bundles no copy of Pi.
@@ -20,14 +20,6 @@ export const DEFAULT_CONFIG = {
20
20
  // Feature 66 — the memory substrate is ask-first: nothing is provisioned
21
21
  // until the operator opts in.
22
22
  memory: { engram: false },
23
- // Tool-call guards ship all-off: they are this repository's working rules,
24
- // not a package opinion — a project opts in per guard in its config file.
25
- guards: {
26
- merge: false,
27
- history: false,
28
- ci: { create: false, run: false },
29
- expensiveTests: { maxTokens: 0, patterns: [] },
30
- },
31
23
  profiles: {},
32
24
  profileOrder: [],
33
25
  profileFallback: { ...DEFAULT_PROFILE_FALLBACK },
@@ -132,9 +132,6 @@ export function mergeConfigs(globalFile = {}, projectFile = {}) {
132
132
  memory: {
133
133
  engram: pick(false, projectFile.memory?.engram, globalFile.memory?.engram) === true,
134
134
  },
135
- // Tool-call guards resolve like every other knob: project wins, then
136
- // global, then the shipped OFF. A guard no scope mentions stays disabled.
137
- guards: mergeGuards(globalFile.guards, projectFile.guards),
138
135
  profiles: mergeProfiles(globalFile.profiles, projectFile.profiles),
139
136
  profileOrder: pick([], projectFile.profileOrder, globalFile.profileOrder),
140
137
  profileFallback: mergeProfileFallback(globalFile.profileFallback, projectFile.profileFallback),
@@ -147,23 +144,6 @@ export function mergeConfigs(globalFile = {}, projectFile = {}) {
147
144
  merged.advance = advance;
148
145
  return merged;
149
146
  }
150
- /** Guards merge whole-knob: a boolean takes project-then-global, and the
151
- * expensive-tests block resolves field-by-field (patterns are replaced, not
152
- * concatenated, so a project override is predictable — no hidden union). */
153
- function mergeGuards(globalGuards, projectGuards) {
154
- return {
155
- merge: pick(DEFAULT_CONFIG.guards.merge, projectGuards?.merge, globalGuards?.merge),
156
- history: pick(DEFAULT_CONFIG.guards.history, projectGuards?.history, globalGuards?.history),
157
- ci: {
158
- create: pick(DEFAULT_CONFIG.guards.ci.create, projectGuards?.ci?.create, globalGuards?.ci?.create),
159
- run: pick(DEFAULT_CONFIG.guards.ci.run, projectGuards?.ci?.run, globalGuards?.ci?.run),
160
- },
161
- expensiveTests: {
162
- maxTokens: pick(DEFAULT_CONFIG.guards.expensiveTests.maxTokens, projectGuards?.expensiveTests?.maxTokens, globalGuards?.expensiveTests?.maxTokens),
163
- patterns: pick(DEFAULT_CONFIG.guards.expensiveTests.patterns, projectGuards?.expensiveTests?.patterns, globalGuards?.expensiveTests?.patterns),
164
- },
165
- };
166
- }
167
147
  /** Project-over-global merge of the advance (conductor) knobs (feature 62).
168
148
  * Absent from the result when neither scope declares any — the resolved
169
149
  * shape of pre-62 configs is byte-stable. */
@@ -21,7 +21,6 @@ const ROOT_KEYS = new Set([
21
21
  "profileOrder",
22
22
  "profileFallback",
23
23
  "memory",
24
- "guards",
25
24
  ]);
26
25
  const ROUTE_KEYS = new Set(["model", "thinking"]);
27
26
  const COMMAND_NAME = /^[a-z0-9][a-z0-9._-]*$/u;
@@ -333,7 +332,7 @@ function validateConfig(value) {
333
332
  if (!ROOT_KEYS.has(key)) {
334
333
  issues.push({
335
334
  path: `$.${displayKey(key)}`,
336
- message: `unknown config key "${key}" (allowed: default, commands, onUnavailableRoute, onSettle, pathProtection, advance, recommendedModels, profiles, profileOrder, profileFallback, memory, guards)`,
335
+ message: `unknown config key "${key}" (allowed: default, commands, onUnavailableRoute, onSettle, pathProtection, advance, recommendedModels, profiles, profileOrder, profileFallback, memory)`,
337
336
  });
338
337
  }
339
338
  }
@@ -408,11 +407,6 @@ function validateConfig(value) {
408
407
  if (memory)
409
408
  config.memory = memory;
410
409
  }
411
- if (value.guards !== undefined) {
412
- const guards = checkGuards(value.guards, "$.guards", issues);
413
- if (guards)
414
- config.guards = guards;
415
- }
416
410
  if (value.profiles !== undefined) {
417
411
  if (!isRecord(value.profiles)) {
418
412
  issues.push({ path: "$.profiles", message: "must be an object mapping profile names to profile definitions" });
@@ -472,102 +466,6 @@ function checkMemory(value, path, issues) {
472
466
  }
473
467
  return unknown ? undefined : out;
474
468
  }
475
- /** Strict validator for the `guards` block. Everything absent stays off. */
476
- function checkGuards(value, path, issues) {
477
- if (!isRecord(value)) {
478
- issues.push({ path, message: `must be a JSON object, got ${describe(value)}` });
479
- return undefined;
480
- }
481
- const out = {};
482
- for (const key of ["merge", "history"]) {
483
- if (value[key] !== undefined) {
484
- if (typeof value[key] !== "boolean") {
485
- issues.push({ path: `${path}.${key}`, message: `must be a boolean, got ${describe(value[key])}` });
486
- }
487
- else {
488
- out[key] = value[key];
489
- }
490
- }
491
- }
492
- if (value.ci !== undefined) {
493
- const ciPath = `${path}.ci`;
494
- if (!isRecord(value.ci)) {
495
- issues.push({ path: ciPath, message: `must be a JSON object, got ${describe(value.ci)}` });
496
- }
497
- else {
498
- const ci = {};
499
- for (const key of ["create", "run"]) {
500
- if (value.ci[key] !== undefined) {
501
- if (typeof value.ci[key] !== "boolean") {
502
- issues.push({ path: `${ciPath}.${key}`, message: `must be a boolean, got ${describe(value.ci[key])}` });
503
- }
504
- else {
505
- ci[key] = value.ci[key];
506
- }
507
- }
508
- }
509
- for (const key of Object.keys(value.ci)) {
510
- if (key !== "create" && key !== "run") {
511
- issues.push({ path: `${ciPath}.${displayKey(key)}`, message: `unknown ci key "${key}" (allowed: create, run)` });
512
- }
513
- }
514
- out.ci = ci;
515
- }
516
- }
517
- if (value.expensiveTests !== undefined) {
518
- out.expensiveTests = checkExpensiveTests(value.expensiveTests, `${path}.expensiveTests`, issues);
519
- }
520
- for (const key of Object.keys(value)) {
521
- if (key !== "merge" && key !== "history" && key !== "ci" && key !== "expensiveTests") {
522
- issues.push({ path: `${path}.${displayKey(key)}`, message: `unknown guards key "${key}" (allowed: merge, history, ci, expensiveTests)` });
523
- }
524
- }
525
- return out;
526
- }
527
- function checkExpensiveTests(value, path, issues) {
528
- if (!isRecord(value)) {
529
- issues.push({ path, message: `must be a JSON object, got ${describe(value)}` });
530
- return undefined;
531
- }
532
- const out = {};
533
- if (value.maxTokens !== undefined) {
534
- if (typeof value.maxTokens !== "number" || !Number.isInteger(value.maxTokens) || value.maxTokens < 0) {
535
- issues.push({ path: `${path}.maxTokens`, message: `must be a non-negative integer, got ${describe(value.maxTokens)}` });
536
- }
537
- else {
538
- out.maxTokens = value.maxTokens;
539
- }
540
- }
541
- if (value.patterns !== undefined) {
542
- if (!Array.isArray(value.patterns)) {
543
- issues.push({ path: `${path}.patterns`, message: `must be an array, got ${describe(value.patterns)}` });
544
- }
545
- else {
546
- const patterns = [];
547
- value.patterns.forEach((entry, index) => {
548
- const entryPath = `${path}.patterns[${index}]`;
549
- if (!isRecord(entry) || typeof entry.match !== "string" || entry.match === "") {
550
- issues.push({ path: entryPath, message: `must be an object with a non-empty string "match", got ${describe(entry)}` });
551
- return;
552
- }
553
- if (typeof entry.tokens !== "number" || !Number.isInteger(entry.tokens) || entry.tokens < 0) {
554
- issues.push({ path: `${entryPath}.tokens`, message: `must be a non-negative integer, got ${describe(entry.tokens)}` });
555
- return;
556
- }
557
- try {
558
- new RegExp(entry.match, "u");
559
- }
560
- catch {
561
- issues.push({ path: `${entryPath}.match`, message: `not a valid regular expression: ${entry.match}` });
562
- return;
563
- }
564
- patterns.push({ match: entry.match, tokens: entry.tokens });
565
- });
566
- out.patterns = patterns;
567
- }
568
- }
569
- return out;
570
- }
571
469
  export function parseConfigFile(text) {
572
470
  if (text.trim() === "")
573
471
  return { ok: true, config: {} };
@@ -78,9 +78,6 @@ export interface ConfigFile {
78
78
  /** Feature 66 (AC3/D3): the memory-substrate consent surface — Engram's MCP
79
79
  * registration is ask-first, so nothing is provisioned unless this says so. */
80
80
  memory?: MemoryConfigFile;
81
- /** Tool-call guards (owner-gated merges, history, CI, expensive tests).
82
- * Everything absent stays off. */
83
- guards?: GuardsConfigFile;
84
81
  /** Named user profiles keyed by profile name (feature 63, AC5). */
85
82
  profiles?: Record<string, ProfileFile>;
86
83
  /** Ordered list of profile names to probe; empty means ["default"] only
@@ -108,61 +105,6 @@ export interface MemoryConfigFile {
108
105
  export interface MemoryConfig {
109
106
  engram: boolean;
110
107
  }
111
- /**
112
- * One tool-call guard, as an operator WRITES it (this repository's working
113
- * rules, made mechanical; every other project gets none of it unless it
114
- * configures it). Each guard ships disabled — an absent key is OFF.
115
- *
116
- * merge every direct merge route is owner-only: `git merge`,
117
- * `gh pr merge`, and `git pull`'s merge form.
118
- * history pushes are decided by asking git (`git push --dry-run`):
119
- * a forced update, a ref delete or a push to `main` is
120
- * declined — one mechanism covering every flag spelling and
121
- * git alias.
122
- * ci CI is owner-gated: creating workflow files and triggering
123
- * workflow runs are blocked.
124
- * expensiveTests commands matching a configured pattern are estimated in
125
- * tokens; above `maxTokens` they are blocked. `tokens: 0`
126
- * blocks every match regardless of estimate.
127
- *
128
- * The only way through a guard is the explicit per-command token — the
129
- * command's first word — after an owner authorization in the same turn:
130
- * `AW_ALLOW=merge`, `AW_ALLOW=force`, `AW_ALLOW=ci`, `AW_ALLOW=cost`.
131
- */
132
- export interface GuardsConfigFile {
133
- merge?: boolean;
134
- history?: boolean;
135
- ci?: GuardsCiFile;
136
- expensiveTests?: GuardsExpensiveTestsFile;
137
- }
138
- export interface GuardsCiFile {
139
- create?: boolean;
140
- run?: boolean;
141
- }
142
- export interface GuardsExpensiveTestsFile {
143
- /** Estimated-token ceiling; a match above it is blocked. `0` (default)
144
- * blocks every match regardless of estimate. */
145
- maxTokens?: number;
146
- /** Command patterns (regex source, `u` flag) and their estimated cost. */
147
- patterns?: GuardsCostPattern[];
148
- }
149
- export interface GuardsCostPattern {
150
- match: string;
151
- tokens: number;
152
- }
153
- /** What the extension READS: every guard knob resolved. */
154
- export interface GuardsConfig {
155
- merge: boolean;
156
- history: boolean;
157
- ci: {
158
- create: boolean;
159
- run: boolean;
160
- };
161
- expensiveTests: {
162
- maxTokens: number;
163
- patterns: GuardsCostPattern[];
164
- };
165
- }
166
108
  export interface ConfigIssue {
167
109
  path: string;
168
110
  message: string;
@@ -191,8 +133,6 @@ export interface EffectiveConfig {
191
133
  profileFallback: ProfileFallbackConfig;
192
134
  /** Feature 66: the memory-substrate consent surface. */
193
135
  memory: MemoryConfig;
194
- /** The effective tool-call guards (all off unless a config file enables them). */
195
- guards: GuardsConfig;
196
136
  /** Feature 63: declaredness of the implicit `default` profile. */
197
137
  declared: {
198
138
  default: RouteDeclaration;
@@ -2,10 +2,6 @@ import type { LoadedConfig } from "../config/load.js";
2
2
  import type { Catalogue } from "../routing/catalogue.js";
3
3
  import type { Router } from "../routing/dispatch.js";
4
4
  import { dirtyWorktreeWarning, receiptGuard } from "./receipt-guard.js";
5
- import { mergeGuard } from "./merge-guard.js";
6
- import { historyGuard } from "./history-guard.js";
7
- import { ciGuard } from "./ci-guard.js";
8
- import { costGuard } from "./cost-guard.js";
9
5
  import type { ExtensionSurface, InvocationContext, ModelRef, RoutingControls } from "../routing/types.js";
10
6
  import type { HintStore } from "../routing/state.js";
11
7
  /**
@@ -58,18 +54,12 @@ export interface ExtensionHandle<M extends ModelRef = ModelRef> {
58
54
  catalogue: Catalogue;
59
55
  /**
60
56
  * The Pi-free predicates the adapter (`index.ts`) binds to Pi's lifecycle
61
- * events: the inline-receipt block guard, the owner-gated tool guards (merge,
62
- * history, CI, cost — all config-gated, all off unless a project enables
63
- * them), and the dirty-worktree notice. They travel with the handle so the
64
- * entry stays a thin translation layer and the decisions are unit-tested with
65
- * no session (issues #182, #278).
57
+ * events: the inline-receipt block guard and the dirty-worktree notice. They
58
+ * travel with the handle so the entry stays a thin translation layer and the
59
+ * decisions are unit-tested with no session (issue #182).
66
60
  */
67
61
  guards: {
68
62
  receiptGuard: typeof receiptGuard;
69
- mergeGuard: typeof mergeGuard;
70
- historyGuard: typeof historyGuard;
71
- ciGuard: typeof ciGuard;
72
- costGuard: typeof costGuard;
73
63
  dirtyWorktreeWarning: typeof dirtyWorktreeWarning;
74
64
  };
75
65
  }
@@ -2,10 +2,6 @@ import { loadConfig } from "../config/load.js";
2
2
  import { readCatalogue } from "../routing/catalogue.js";
3
3
  import { createRouter } from "../routing/dispatch.js";
4
4
  import { dirtyWorktreeWarning, receiptGuard } from "./receipt-guard.js";
5
- import { mergeGuard } from "./merge-guard.js";
6
- import { historyGuard } from "./history-guard.js";
7
- import { ciGuard } from "./ci-guard.js";
8
- import { costGuard } from "./cost-guard.js";
9
5
  import { ADVANCE_COMMAND, SETTINGS_COMMAND, SETTINGS_COMMAND_ALIAS } from "../routing/types.js";
10
6
  import { registerAdvanceCommand } from "./conductor-command.js";
11
7
  import { createProfileStateStore, stateFilePath } from "../routing/state.js";
@@ -82,5 +78,5 @@ export function createExtension(deps) {
82
78
  description: "Show and configure per-command model routing",
83
79
  handler: settingsHandler,
84
80
  });
85
- return { router, catalogue, guards: { receiptGuard, mergeGuard, historyGuard, ciGuard, costGuard, dirtyWorktreeWarning } };
81
+ return { router, catalogue, guards: { receiptGuard, dirtyWorktreeWarning } };
86
82
  }
@@ -296,32 +296,8 @@ export default function extension(pi) {
296
296
  // names the script that proves the receipt landed (issue #182). The guard
297
297
  // itself filters to `bash`, so every tool call is checked — nesting it under
298
298
  // one tool name would make the block unreachable for the calls it exists for.
299
- //
300
- // Owner-gated tool guards, all config-driven (`guards` in the config files;
301
- // every guard ships OFF — a project opts in per guard):
302
- // merge `git merge`, `gh pr merge`, `git pull`'s merge form (issue #278).
303
- // history pushes decided by git itself (`git push --dry-run`): a forced
304
- // update, a ref delete or a push to `main` is declined.
305
- // ci writing workflow definitions and triggering workflow runs.
306
- // cost commands estimated above the configured token budget.
307
- // The only way through any of them is the explicit per-command token
308
- // (`AW_ALLOW=merge|force|ci|cost`) after an owner instruction in the turn.
309
299
  pi.on("tool_call", (event, ctx) => {
310
300
  const command = "command" in event.input && typeof event.input.command === "string" ? event.input.command : undefined;
311
- const inputPath = "path" in event.input && typeof event.input.path === "string" ? event.input.path : undefined;
312
- const { guards: policy } = loadConfig({ agentDir, cwd: ctx.cwd, projectTrusted: ctx.isProjectTrusted() }).config;
313
- const merge = guards.mergeGuard({ toolName: event.toolName, command }, policy.merge);
314
- if (merge.block)
315
- return merge;
316
- const history = guards.historyGuard({ toolName: event.toolName, command }, policy.history, { cwd: ctx.cwd });
317
- if (history.block)
318
- return history;
319
- const ci = guards.ciGuard({ toolName: event.toolName, command, path: inputPath }, policy.ci);
320
- if (ci.block)
321
- return ci;
322
- const cost = guards.costGuard({ toolName: event.toolName, command }, policy.expensiveTests);
323
- if (cost.block)
324
- return cost;
325
301
  const receipt = guards.receiptGuard({ toolName: event.toolName, command });
326
302
  if (receipt.block)
327
303
  return receipt;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gtrabanco/pi-agentic-workflow",
3
- "version": "4.10.0",
3
+ "version": "5.0.0-canary.20261102.1",
4
4
  "description": "Pi package: canonical agentic-workflow skills, friendly slash commands, and per-command model routing.",
5
5
  "keywords": [
6
6
  "pi-package",
@@ -58,11 +58,11 @@
58
58
  "@earendil-works/pi-tui": "*"
59
59
  },
60
60
  "dependencies": {
61
- "@gtrabanco/agentic-workflow": "4.10.0",
62
- "@gtrabanco/agentic-workflow-schema": "4.10.0"
61
+ "@gtrabanco/agentic-workflow": "5.0.0-canary.20261102.1",
62
+ "@gtrabanco/agentic-workflow-schema": "5.0.0-canary.20261102.1"
63
63
  },
64
64
  "devDependencies": {
65
- "@types/node": "24.13.3",
65
+ "@types/node": "26.6.6",
66
66
  "typescript": "7"
67
67
  }
68
68
  }
@@ -227,8 +227,8 @@ the full accumulated diff. On gate red at any step: repair that step (up to
227
227
  `--max-attempts`), then continue the loop. The close-out is part of the last
228
228
  step's completion: open the PR on `main` with `Closes #N`, write `- PR: <url>`
229
229
  into References, and settle the index row with `agentic-workflow roadmap
230
- <index> rowUpsert '<json>'` (`docs/features/ROADMAP.md`; a fix unit edits its
231
- `docs/fix/README.md` status cell — no `NN` column, no `rowUpsert`).
230
+ <index> rowUpsert '<json>'` (`docs/ROADMAP.md`; a fix unit edits its row in that
231
+ document's `## Fixes` table — no `NN` column, no `rowUpsert`).
232
232
  `bun scripts/roadmap-settle.mjs` exits 0 (#316).
233
233
  Terminal:
234
234
 
@@ -69,8 +69,8 @@ appending to the unit doc's Evidence section.
69
69
 
70
70
  ### Own-status precondition (runs after the dependency closure is met, still before any edit)
71
71
 
72
- Read the unit doc's status from the roadmap (the five-state machine —
73
- `docs/features/ROADMAP.md` → Status legend):
72
+ Read the unit doc's status from the location index (the five-state machine —
73
+ `docs/ROADMAP.md` → Status legend):
74
74
 
75
75
  1. **`idea`** (or no triage block in the unit doc) → STOP, before any edit:
76
76
 
@@ -65,7 +65,8 @@ Inspect the target dir (`[target-dir]`, default cwd) before touching anything:
65
65
  - An existing **agent guide** (`AGENTS.md`, or a legacy `CLAUDE.md`), `docs/`,
66
66
  `.github/`? If so, **do not clobber** — check first whether it's an
67
67
  **agentic-workflow scaffold** (marker: an agent guide present *and* either
68
- `docs/features/ROADMAP.md` or a `docs/workflow/` dir). If both markers are
68
+ `docs/ROADMAP.md` — or the pre-319 `docs/features/ROADMAP.md` — or a
69
+ `docs/workflow/` dir). If both markers are
69
70
  present, offer **upgrade** as the default action, alongside merge /
70
71
  adapt-in-place / abort — see **Upgrade mode** below. If an agent guide or
71
72
  `docs/` exists but the markers are absent (a foreign scaffold), stay in
@@ -166,11 +167,17 @@ loads exactly upgrade and skips bootstrap plus portability.
166
167
 
167
168
  ## Unit document conventions (the adaptive lane)
168
169
 
169
- The adaptive lane (feature 61) replaces the fixed pipeline with a single-unit-
170
- document model. Every NEW unit produces one `SPEC.md` under
171
- `docs/features/<NN>-<slug>/` with 14 sections: Objective, Why, User outcome, Acceptance criteria, Impact,
172
- Non-goals, Future cost, Applicable tests, Known pre-existing issues, Tasks,
173
- Evidence, Progress log, Next, References.
170
+ The adaptive lane replaces the fixed pipeline with a single-unit-document model.
171
+ Every NEW unit produces one `SPEC.md` under `docs/features/<NN>-<slug>/`, cut in
172
+ the **`spec-by-reference`** shape the template ships: `Specs` (numbered `S#`, each
173
+ quoting the requester's words verbatim), Impact, Non-goals, Future cost,
174
+ Applicable tests, Known pre-existing issues, `Tasks` (each bullet linked to the
175
+ `S#` it delivers), `Findings` (OPEN rows only), `Log` (append-only; `L1` is the
176
+ request verbatim), Next, References. A unit opened before this shape keeps the
177
+ legacy 14-section shape — Objective, Why, User outcome, Acceptance criteria,
178
+ Impact, Non-goals, Future cost, Applicable tests, Known pre-existing issues,
179
+ Tasks, Evidence, Progress log, Next, References — and is never rewritten to
180
+ migrate.
174
181
 
175
182
  Conventions written into the project's AGENTS.md:
176
183
 
@@ -188,8 +195,8 @@ Conventions written into the project's AGENTS.md:
188
195
 
189
196
  These are written by bootstrap (fresh) and upgrade mode (additive-only for pre-
190
197
  lane installs). A pre-lane project gets only: the lane conventions section in
191
- AGENTS.md, the unit doc template under `docs/features/_TEMPLATE/`, and the
192
- docs-map row for the unit doc.
198
+ AGENTS.md, the unit doc templates under `docs/features/_TEMPLATE/` and
199
+ `docs/fix/_TEMPLATE/`, and the docs-map rows for them.
193
200
 
194
201
  ## Normalized Repository State
195
202
 
@@ -4,10 +4,11 @@
4
4
  the documentation map rows, architecture); write the **adaptive lane
5
5
  conventions** (unit document, triage, catalog steps, guards, evidence rows,
6
6
  dated progress log, closed commit format `type(scope): description`); keep
7
- the `features/_TEMPLATE` + `ROADMAP`, the `fix/_TEMPLATE` + `README`, and
8
- the `.github/` templates; keep `.agentic-workflow/hooks/` and activate only
9
- the adapters explicitly accepted in the interview; prune unused doc folders
10
- and map rows. Leave honest placeholders where the user hasn't decided — never
7
+ the documentation templates it ships — `features/_TEMPLATE` beside
8
+ `features/ROADMAP.md`, and `fix/_TEMPLATE` beside `fix/README.md` — and the
9
+ `.github/` templates; keep `.agentic-workflow/hooks/` and activate only the
10
+ adapters explicitly accepted in the interview; prune unused doc folders and
11
+ map rows. Leave honest placeholders where the user hasn't decided — never
11
12
  invent values.
12
13
  5. **Seed Normalized Repository State.** Copy
13
14
  `template/docs/workflow/REPOSITORY_STATE.md` to
@@ -21,9 +22,14 @@
21
22
  optional: an absent document means no project invariants are declared, not
22
23
  that the scaffold failed. If retained, add its documentation-map row and
23
24
  name the project's explicit architectural-decision authority.
24
- 7. **Propagate the unit doc template.** Ensure
25
- `docs/features/_TEMPLATE/SPEC.md` exists with all 14 sections sections.
26
- This is the template used when creating new unit documents.
25
+ 7. **Propagate the unit doc templates.** Ensure both template folders exist and
26
+ are cut in the `spec-by-reference` shape the scaffold ships —
27
+ `docs/features/_TEMPLATE/SPEC.md` and `docs/fix/_TEMPLATE/SPEC.md` (`Specs`
28
+ with `S#` requirements, Impact, Non-goals, Future cost, Applicable tests,
29
+ Known pre-existing issues, `Tasks`, `Findings`, `Log`, Next, References).
30
+ These are the templates used when creating new unit documents; a unit opened
31
+ before this shape keeps the legacy 14-section shape and is never rewritten to
32
+ migrate.
27
33
  8. **Provision Serena — one consented question, then write.** Load
28
34
  [SERENA](SERENA.md) and ask its single consent question; one yes covers the
29
35
  tool install, the detected platform's MCP registration and the global trust
@@ -4,7 +4,7 @@ Entered when Step 0 finds an existing **agentic-workflow scaffold** (not a
4
4
  bare or foreign repo). Bootstrap mode (the Process section above) never
5
5
  engages here — upgrade mode reuses the same discovery + interview machinery,
6
6
  scoped to **only the blocks the current template has that this project
7
- lacks**. Nine ordered steps:
7
+ lacks**. Ten ordered steps:
8
8
 
9
9
  1. **Locate the current template.** Fetch the current `template/` the same
10
10
  way bootstrap does — `npx degit gtrabanco/agentic-workflow/template <temp-dir>`
@@ -33,14 +33,34 @@ lacks**. Nine ordered steps:
33
33
  dated migration note so the proposal explains *why* the block exists and
34
34
  *what* it migrates. If `MIGRATION.md` is absent, proceed on the template
35
35
  diff alone and say so in the report — never block on a missing note.
36
- 4. **Propose the adaptive lane conventions (feature 61 P11).** For pre-lane
36
+ 4. **Propose the consolidated location index (issue #319).** A project whose
37
+ roadmap still lives at `docs/features/ROADMAP.md`, or whose fix index is still
38
+ a standalone `docs/fix/README.md`, is offered the move **additively**:
39
+ `docs/ROADMAP.md` gained as a new file (the project's feature table plus a
40
+ `## Fixes` section built from its own fix index), the old path removed **only
41
+ after the new file is in place**, and the fix README reduced to the one-line
42
+ pointer stub the template ships. Never rewrite an edited roadmap: if the
43
+ project's table differs from the template's, carry the project's rows
44
+ verbatim and list the differences as residuals. **The read window is closed**
45
+ (`docs/workflow/MIGRATION.md`, 2026-10-30, decision D4): the old path is only
46
+ *detected* so this step can offer the move — a repository that still runs the
47
+ lane against it is refused with a `roadmap-moved` pointer, so propose the move
48
+ as the first thing to fix, never as an optional cleanup. A half-migrated tree
49
+ (canonical roadmap without a `## Fixes` table) still reads its standalone fix
50
+ index, so the fix table can be folded in a second pass.
51
+
52
+ 5. **Propose the adaptive lane conventions (feature 61 P11).** For pre-lane
37
53
  installs (before the fixed pipeline was retired), propose these missing
38
54
  blocks additively — **never clobber existing content**:
39
- - The **unit document conventions** section in AGENTS.md (the 14-section
40
- SPEC.md, triage-driven step selection, diff-size guard, evidence rows,
41
- dated progress log, closed commit format `type(scope): description`).
42
- - The **unit doc template** under `docs/features/_TEMPLATE/SPEC.md` if
43
- absent — the same 14-section template used by new units.
55
+ - The **unit document conventions** section in AGENTS.md (the
56
+ `spec-by-reference` unit doc the template ships — `Specs` with `S#`
57
+ requirements, `Tasks`, `Findings`, `Log` — triage-driven step selection,
58
+ diff-size guard, evidence rows, dated progress log, closed commit format
59
+ `type(scope): description`).
60
+ - The **unit doc templates** under `docs/features/_TEMPLATE/SPEC.md` and
61
+ `docs/fix/_TEMPLATE/SPEC.md` if absent — the re-cut `spec-by-reference`
62
+ templates the scaffold ships. A pre-existing unit keeps the legacy
63
+ 14-section shape and is never rewritten to migrate.
44
64
  - The **lane workflow doc** under `docs/workflow/` if absent: `FEATURE_WORKFLOW.md`
45
65
  (unit doc → triage → catalog steps → evidence → review → release),
46
66
  `ISSUE_WORKFLOW.md` (issues route through triage to the lane).
@@ -50,7 +70,7 @@ lacks**. Nine ordered steps:
50
70
  the project already answered** — a block that's already filled is skipped,
51
71
  not re-interviewed. The lane conventions are additive only: existing content
52
72
  is never rewritten, only new blocks are inserted where missing.
53
- 5. **Write additively.** Insert accepted blocks, including a missing
73
+ 6. **Write additively.** Insert accepted blocks, including a missing
54
74
  `docs/workflow/REPOSITORY_STATE.md`; fill raw, still-placeholder
55
75
  blocks with the confirmed values. **Never rewrite a block the project has
56
76
  already tailored, and never delete anything** — a tailored block the
@@ -61,7 +81,7 @@ lacks**. Nine ordered steps:
61
81
  `path-protection.md` page are proposed from the template; an existing
62
82
  owner-written `path-policy.json` is never clobbered and is listed as a
63
83
  residual instead. Leave honest placeholders where the user skipped.
64
- 6. **Retire the legacy `CLAUDE.md` — only with an explicit yes, never as a side
84
+ 7. **Retire the legacy `CLAUDE.md` — only with an explicit yes, never as a side
65
85
  effect.** Older releases of this workflow wrote the guide as `CLAUDE.md`;
66
86
  current ones write `AGENTS.md`, and current Claude Code reads `AGENTS.md`.
67
87
  When the project has a `CLAUDE.md`, three ordered moves — never skip (a):
@@ -83,7 +103,7 @@ lacks**. Nine ordered steps:
83
103
 
84
104
  This step is the only writer of `CLAUDE.md` in upgrade mode, and it is never
85
105
  reached without the answer from (b).
86
- 7. **Seed missing urgency labels, additively (feature 15).** Independent of
106
+ 8. **Seed missing urgency labels, additively (feature 15).** Independent of
87
107
  the agent-guide/`docs/` block diff above (this is forge-repo state, not a
88
108
  doc block): check whether the target repo already has the `urgent` and
89
109
  `fix-next` labels (`gh label list`); create whichever is missing with the
@@ -92,7 +112,7 @@ lacks**. Nine ordered steps:
92
112
  rule as the doc blocks; a pre-existing `urgent`/`fix-next` label the
93
113
  project recolored or redescribed is left exactly as-is).
94
114
 
95
- 8. **Provision Serena, additively only (Refs #219).** Load
115
+ 9. **Provision Serena, additively only (Refs #219).** Load
96
116
  [SERENA](SERENA.md) and ask its single consent question — the tool install,
97
117
  the detected platform's MCP registration and the global trust append all ride
98
118
  on that one yes, and a refusal writes nothing. Then append the `.gitignore`
@@ -101,7 +121,7 @@ lacks**. Nine ordered steps:
101
121
  the health check, and render the fixed provisioning report. This step is
102
122
  additive-only; it never rewrites or deletes, consistent with upgrade mode's
103
123
  rule.
104
- 9. **Report + hand off.** Summarize blocks added, filled, and skipped
124
+ 10. **Report + hand off.** Summarize blocks added, filled, and skipped
105
125
  (residuals), the legacy `CLAUDE.md` outcome (folded items and removed, or kept
106
126
  with the reason), the urgency labels seeded (or already present), then print the
107
127
  recommendation to run `product-audit` next to see which newly-available
@@ -121,12 +141,12 @@ lacks**. Nine ordered steps:
121
141
  - **A block the project already tailored.** If the current template also
122
142
  changed a block the project customized, **do not merge, diff-patch, or
123
143
  overwrite it** — leave it exactly as the project has it and list it as a
124
- residual in the report (step 5's never-clobber invariant). The user decides
144
+ residual in the report (step 6's never-clobber invariant). The user decides
125
145
  separately, via an explicit bootstrap adapt-in-place run, whether to
126
146
  re-tailor it.
127
147
  - **Bootstrap stays unchanged on a bare or foreign repo.** If Step 0's
128
148
  scaffold markers (an agent guide — `AGENTS.md` or a legacy `CLAUDE.md` — plus
129
- `docs/features/ROADMAP.md` or
149
+ `docs/ROADMAP.md`, a pre-319 `docs/features/ROADMAP.md`, or
130
150
  `docs/workflow/`) are absent — no repo, an empty repo, or a guide
131
151
  that isn't this workflow's — upgrade mode never engages; the existing
132
152
  bootstrap Process (merge/adapt/abort) runs exactly as before this mode was
@@ -72,7 +72,7 @@ split and never carries a second copy of the semantics:
72
72
  | grammar | where | shape |
73
73
  |---|---|---|
74
74
  | **plan** | a plan file, or any `##/###/#### P<n> — <Title>` heading | heading + `Layer: <enum>` + `- [ ]` tasks + `Done-when:` |
75
- | **unit doc** | the unit doc's `## Tasks` section | one `P<n> — <task> (validator: <command> <outcome>)` bullet per phase, wrapped lines joining the bullet, plus an optional nested `- Relevant files: <path, path…>` line |
75
+ | **unit doc** | the unit doc's `## Tasks` section | one `P<n> — <task> (validator: <command> <outcome>)` bullet per phase, optionally carrying the executed-phase tick mark (`- [x] P<n> — …`; the tick is state, never a second phase), wrapped lines joining the bullet, plus an optional nested `- Relevant files: <path, path…>` line |
76
76
 
77
77
  Precedence is fail-closed and one-way: **if a document carries any plan-style
78
78
  phase heading, the plan grammar governs it**; only when it carries none does
@@ -170,7 +170,7 @@ planning-findings | docs/features/<NN>-<slug>/planning-findings.md · docs/fix/<
170
170
  progress | docs/features/<NN>-<slug>/SPEC.md · docs/fix/<issue>-<topic>/SPEC.md | unit-lane:progress-entries + execute-phase:phase-entries + execute-phase:gate-rejection-traces | none | none | bun test scripts/pre-execution-sensor.test.mjs
171
171
  known-issues | docs/features/<NN>-<slug>/known-issues.md · docs/fix/<issue>-<topic>/known-issues.md | unit-lane:known-entries + execute-phase:blocker-entries-and-status | none | none | bun test scripts/ledger-ownership.test.mjs
172
172
  decisions | docs/features/<NN>-<slug>/decisions.md · docs/fix/<issue>-<topic>/decisions.md | unit-lane:decisions + execute-phase:phase-decisions + human-owner:ratified-verdicts | none | none | bun test scripts/ledger-ownership.test.mjs
173
- roadmap | docs/features/ROADMAP.md · docs/fix/README.md | unit-lane:row-registration + execute-phase:status-and-pr-link + audit-docs:low-risk-row-repair | none | none | bun test scripts/bounded-delivery-loops.test.mjs
173
+ roadmap | docs/ROADMAP.md | unit-lane:row-registration + execute-phase:status-and-pr-link + audit-docs:low-risk-row-repair | none | none | bun test scripts/bounded-delivery-loops.test.mjs
174
174
  acceptance-manifest | docs/features/<NN>-<slug>/ACCEPTANCE.md · docs/fix/<issue>-<topic>/ACCEPTANCE.md | unit-lane:acceptance-freeze + human-owner:approved-amendment | none | none | git hash-object docs/features/<NN>-<slug>/ACCEPTANCE.md (legacy pre-feature 61) · sed -n '/^## Acceptance criteria/,/^## /p' <unit>/SPEC.md then git hash-object --stdin (unit doc era, post-feature 61)
175
175
  # no-script-writer: SPEC.md · PLAN.md · TASKS.md · CHECKLIST.md · testing.md · architecture-notes.md · planning-evidence.md · planning-obligations.md · delegated-evidence.md
176
176
  ```
@@ -136,8 +136,8 @@ a count and never waives a finding.
136
136
  `execute-phase`, and a `PLAN-REVIEW-PASS` never certifies the Product half.
137
137
  - A gate answer from a roadmap row's status. Gates read receipts only;
138
138
  roadmap rows are labels and never answer a gate. `done` means built + PR open
139
- with the human merge pending (`docs/features/ROADMAP.md` § Status legend;
140
- `docs/fix/README.md` § Status legend), so it never satisfies or suppresses a
139
+ with the human merge pending (`docs/ROADMAP.md` § Status legend and § Fix
140
+ status legend), so it never satisfies or suppresses a
141
141
  pre-execution/verification gate, never retires the obligation to re-derive a
142
142
  receipt, and never lets a reviewer or consumer report a unit closed while its
143
143
  PR is unmerged. The complement of §5's "no verdict *from* a status" — and the