@sabaiway/agent-workflow-kit 5.11.1 → 6.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,96 @@
1
+ // flow-vocabulary.mjs — the CLOSED flow-record vocabulary (kinds, purposes, terminal lanes, the
2
+ // design seed assignment, the allowed-transition table) plus the five shared form bindings every
3
+ // validator states its refusals in: HEX64_RE, isPlainObject, isNonEmptyString, isHex64 and refuse.
4
+ // Split out of flow-record.mjs unchanged (baseline-practices tranche 3), which now re-exports the
5
+ // ten public names here; the five shared bindings stay OFF that surface (plan D3) — the leaves that
6
+ // need them import this module, so the record family's named grammars have exactly one home and no
7
+ // copy can drift.
8
+ //
9
+ // The LOWEST leaf of the family and pure form: no filesystem, no git, no CLI, no side effects on
10
+ // import, and orchestration-config.mjs (the schema version) is its only tools sibling. Imports run
11
+ // ONE way — the shape, identity, legality and manifest leaves compose this module; nothing here
12
+ // reaches back up to the facade.
13
+
14
+ import { FLOW_SCHEMA_VERSION } from './orchestration-config.mjs';
15
+
16
+ export { FLOW_SCHEMA_VERSION };
17
+
18
+ const deepFreeze = (value) => {
19
+ if (value !== null && typeof value === 'object') {
20
+ Object.values(value).forEach(deepFreeze);
21
+ Object.freeze(value);
22
+ }
23
+ return value;
24
+ };
25
+
26
+ // ── the closed vocabulary ─────────────────────────────────────────────────────────────────────────
27
+
28
+ export const CHAIN_KIND = 'chain';
29
+ export const CHAIN_PURPOSES = deepFreeze(['adoption', 'round', 'refresh', 're-baseline', 'freeze', 'unfreeze', 'park', 'resume', 'converged', 'complete']);
30
+ export const STEP_SCOPED_PURPOSES = deepFreeze(['round', 'refresh', 're-baseline', 'freeze', 'unfreeze', 'converged']);
31
+ export const PLAN_LANE_PURPOSES = deepFreeze(['adoption', 'park', 'resume', 'complete']);
32
+ export const GLOBAL_KINDS = deepFreeze(['internal-attestation', 'down-mark', 'down-mark-up', 'down-mark-clear', 'degrade-justification', 'rerun-cause', 'bookkeeping-delta', 'maintainer-override', 'consult-attestation', 'subset-attempt']);
33
+ export const FLOW_KINDS = deepFreeze([CHAIN_KIND, ...GLOBAL_KINDS]);
34
+
35
+ // Reserved lane-typed terminals (#16): converged terminates a CYCLE (its step's sequence), complete
36
+ // terminates the PLAN. Park is a resumable suspension, never a terminal (#59).
37
+ export const TERMINAL_LANES = deepFreeze({ converged: 'cycle', complete: 'plan' });
38
+
39
+ // Design §5 seed member → family assignment; the drift-guard test binds this map to the verbatim
40
+ // seed list on one side and to the shipped CHAIN_PURPOSES/GLOBAL_KINDS on the other.
41
+ export const DESIGN_SEED_ASSIGNMENT = deepFreeze({
42
+ adoption: { family: 'chain', purpose: 'adoption' },
43
+ 'round-chain': { family: 'chain', purpose: 'round' },
44
+ refresh: { family: 'chain', purpose: 'refresh' },
45
+ 're-baseline': { family: 'chain', purpose: 're-baseline' },
46
+ unfreeze: { family: 'chain', purpose: 'unfreeze' },
47
+ freeze: { family: 'chain', purpose: 'freeze' },
48
+ converged: { family: 'chain', purpose: 'converged' },
49
+ park: { family: 'chain', purpose: 'park' },
50
+ resume: { family: 'chain', purpose: 'resume' },
51
+ complete: { family: 'chain', purpose: 'complete' },
52
+ 'internal-attestation': { family: 'global', kind: 'internal-attestation' },
53
+ 'down-mark': { family: 'global', kind: 'down-mark' },
54
+ 'down-mark up': { family: 'global', kind: 'down-mark-up' },
55
+ 'down-mark clear': { family: 'global', kind: 'down-mark-clear' },
56
+ 'degrade-justification': { family: 'global', kind: 'degrade-justification' },
57
+ 'rerun-cause': { family: 'global', kind: 'rerun-cause' },
58
+ 'bookkeeping-delta': { family: 'global', kind: 'bookkeeping-delta' },
59
+ 'maintainer-override': { family: 'global', kind: 'maintainer-override' },
60
+ 'consult-attestation': { family: 'global', kind: 'consult-attestation' },
61
+ });
62
+
63
+ // The allowed-transition table — an exported frozen structure, never prose. Within a step:
64
+ // converged ends the sequence (only the unfreeze lane reopens it, and only in its own cycle);
65
+ // freeze admits only unfreeze/converged. Plan lane: park admits only resume (and both preserve the
66
+ // pre-park cycle/round); complete admits nothing; adoption is only ever the chain's first record.
67
+ // The boundary lane (between steps): the opener round, the unfreeze reopen, and a re-baseline for
68
+ // disjoint base motion that reopens nothing. The cross-step edge (the opener's prior-terminal
69
+ // reference) is enforced by validateChainSequence, distinct from the within-step successor rule.
70
+ export const ALLOWED_TRANSITIONS = deepFreeze({
71
+ stepOpening: 'round',
72
+ withinStep: {
73
+ round: ['round', 'refresh', 're-baseline', 'freeze', 'converged'],
74
+ refresh: ['round', 'refresh', 're-baseline', 'freeze', 'converged'],
75
+ 're-baseline': ['round', 'refresh', 're-baseline', 'freeze', 'converged'],
76
+ freeze: ['unfreeze', 'converged'],
77
+ unfreeze: ['round', 'refresh', 're-baseline', 'freeze', 'converged'],
78
+ converged: ['unfreeze'],
79
+ },
80
+ planLane: {
81
+ adoption: ['round', 'park', 'complete'],
82
+ park: ['resume'],
83
+ resume: ['round', 'park', 'complete'],
84
+ complete: [],
85
+ },
86
+ boundary: ['round', 'unfreeze', 're-baseline'],
87
+ });
88
+
89
+ // ── the shared form bindings (D3) — the record family's named grammars, off the public surface ────
90
+
91
+ export const HEX64_RE = /^[0-9a-f]{64}$/;
92
+ export const isPlainObject = (v) => v !== null && typeof v === 'object' && !Array.isArray(v);
93
+ export const isNonEmptyString = (v) => typeof v === 'string' && v.length > 0;
94
+ export const isHex64 = (v) => typeof v === 'string' && HEX64_RE.test(v);
95
+
96
+ export const refuse = (reason) => ({ ok: false, reason });
@@ -6,11 +6,9 @@
6
6
  //
7
7
  // --constraints slice the root AGENTS.md `## 🚫 Hard Constraints` section, verbatim
8
8
  // (exactly-one-match — 0 or >1 headings is a loud STOP, never a guess);
9
- // --plan <path> extract the plan's decision-bearing canonical sections, verbatim + whole:
10
- // `## Approach` (REQUIRED its "What we are NOT doing" text rides inside;
11
- // it is not a heading in canon) and `## Verification` (REQUIRED STOP if
12
- // missing), plus `## Decisions (locked)` (optional-if-absent, the engine §7
13
- // heading this release adds); a DUPLICATE heading is always a STOP.
9
+ // --plan <path> extract the plan's three canon sections, verbatim + whole — `## Goal and
10
+ // boundary`, `## Module ledger`, `## Verification` (the engine planning.md
11
+ // literal headings; each REQUIRED a missing or DUPLICATE heading is a STOP).
14
12
  // --extra <text|@file> append orchestrator-supplied facts verbatim AFTER the mechanical halves
15
13
  // (repeatable; @file reads are confined to the work tree + the system temp
16
14
  // surface — the merge happens INSIDE the tool, corpus #88/#95).
@@ -46,18 +44,14 @@ export const DEFAULT_MAX_PROMPT_BYTES = 120000;
46
44
  export const ARGV_HARD_MAX = 131000;
47
45
 
48
46
  export const CONSTRAINTS_HEADING = /^## .*Hard Constraints$/;
49
- export const PLAN_SECTIONS = [
50
- { heading: '## Approach', optional: false },
51
- { heading: '## Verification', optional: false },
52
- { heading: '## Decisions (locked)', optional: true },
53
- ];
47
+ export const PLAN_SECTIONS = ['## Goal and boundary', '## Module ledger', '## Verification'];
54
48
 
55
49
  // ── pure section slicing (exactly-one-match; the inject-methodology discipline) ────────
56
50
 
57
51
  // Slice ONE `## `-level section (its heading line through the line before the next `## ` heading),
58
52
  // verbatim. `heading` is a string (trimmed-line equality) or a RegExp over the trimmed line.
59
- // 0 matches → null when optional, else STOP; >1 matches → always STOP (never guess which).
60
- export const sliceSection = (text, heading, { optional = false, label = 'document' } = {}) => {
53
+ // 0 matches → STOP; >1 matches → STOP (never guess which).
54
+ export const sliceSection = (text, heading, { label = 'document' } = {}) => {
61
55
  const lines = text.split('\n');
62
56
  const matchesAt = [];
63
57
  for (let i = 0; i < lines.length; i += 1) {
@@ -66,7 +60,6 @@ export const sliceSection = (text, heading, { optional = false, label = 'documen
66
60
  }
67
61
  const shown = typeof heading === 'string' ? heading : String(heading);
68
62
  if (matchesAt.length === 0) {
69
- if (optional) return null;
70
63
  throw fail(1, `${label}: required section "${shown}" not found — STOP (nothing sliced)`);
71
64
  }
72
65
  if (matchesAt.length > 1) {
@@ -95,10 +88,7 @@ export const assembleGrounding = ({ constraintsText = null, autonomyText = null,
95
88
  // how autonomous this session is → what this plan decides).
96
89
  if (autonomyText != null) parts.push(autonomyText);
97
90
  if (planText != null) {
98
- for (const { heading, optional } of PLAN_SECTIONS) {
99
- const section = sliceSection(planText, heading, { optional, label: planLabel });
100
- if (section != null) parts.push(section);
101
- }
91
+ for (const heading of PLAN_SECTIONS) parts.push(sliceSection(planText, heading, { label: planLabel }));
102
92
  }
103
93
  // Orchestrator extras ride LAST, verbatim in argv order — live judgment facts read after the
104
94
  // mechanical slices, and the merge happens INSIDE the tool (corpus #88/#95: a shell append onto
@@ -268,9 +258,9 @@ Usage:
268
258
  stated source line; absent file → the computed defaults ARE the policy
269
259
  (exit 0); malformed/unreadable → fail-closed STOP (exit 1); informational —
270
260
  enforcement stays the sandbox + the orchestrator
271
- --plan <path> extract the plan's decision-bearing sections verbatim + whole:
272
- "## Approach" + "## Verification" (REQUIRED — STOP if missing),
273
- "## Decisions (locked)" when present; a duplicate heading is a STOP
261
+ --plan <path> extract the plan's three canon sections verbatim + whole: "## Goal and
262
+ boundary", "## Module ledger", "## Verification" (each REQUIRED — a
263
+ missing or duplicate heading is a STOP)
274
264
  --extra <text|@file> append orchestrator-supplied extra facts byte-verbatim AFTER the
275
265
  mechanical sections (repeatable, argv order; the agy-review --facts
276
266
  convention: literal text, or @path read whole through a race-free
@@ -84,6 +84,8 @@ export const AUTONOMY_EMPTY_SLOT = `${AUTONOMY_START_MARKER}\n${AUTONOMY_END_MAR
84
84
  export const KNOWN_PRIOR_METHODOLOGY_SLOT = [
85
85
  // v1.3.0 — pre-communication-contract methodology pointer (procedures route, no §1.9 clause).
86
86
  '> **Workflow methodology** — plan → execute → review. Plans are ephemeral `docs/plans/*.md` (gitignored, **never committed**); every Plan ends with a mandatory **Phase: Cleanup**; series order lives in `docs/plans/queue.md`. Full vocabulary, lifecycle, and the plan-then-execute split live in the project\'s **planning skill** (it overrides the generic `writing-plans`); summary in `docs/ai/agent_rules.md` §5. Named activities (plan-authoring, plan-execution) have procedures — see `/agent-workflow-kit procedures <activity>` for the steps + resolved recipe.',
87
+ // engine 2.1.0 — the pre-canon-rewrite pointer (vocabulary + plan-then-execute wording, with the communication contract).
88
+ '> **Workflow methodology** — plan → execute → review. Plans are ephemeral `docs/plans/*.md` (gitignored, **never committed**); every Plan ends with a mandatory **Phase: Cleanup**; series order lives in `docs/plans/queue.md`. Full vocabulary, lifecycle, and the plan-then-execute split live in the project\'s **planning skill** (it overrides the generic `writing-plans`); summary in `docs/ai/agent_rules.md` §5. Named activities (plan-authoring, plan-execution) have procedures — see `/agent-workflow-kit procedures <activity>` for the steps + resolved recipe. **Communication:** user-facing messages deliver the artifact inline (paste the prompt / diff / command — never "see §X" as a substitute), lead with the result, show exactly what was asked, and never read as mockery (a large artifact: a real summary inline + a link).',
87
89
  ];
88
90
  export const KNOWN_PRIOR_ORCH_SLOT = [
89
91
  // v1.3.0 — pre-read-at-start orchestration pointer (recipes vocabulary, no orchestration.json clause).
@@ -208,8 +208,8 @@ const backendSetLabel = (backends) =>
208
208
  : ` → ${backends[0]}`;
209
209
 
210
210
  // The review-loop economics block (M1 + M6's firing half) — printed when the activity engages a review
211
- // backend (a slot resolving reviewed | council) and OMITTED for solo. It paraphrases the §9 +
212
- // orchestration §4 canon (no rival rule): the ≤2-round architecture cap, the bar met by RAISING a
211
+ // backend (a slot resolving reviewed | council) and OMITTED for solo. It paraphrases the procedures.md
212
+ // Fold + loop step + orchestration §4 canon (no rival rule): the ≤2-round architecture cap, the bar met by RAISING a
213
213
  // surviving major to an acceptance invariant (not exhausting prose), backend divergence = the crossover
214
214
  // stop, the thin-plan/diff-review carve-out, a self-consistency read before every re-review, and the
215
215
  // REQUIRED per-round structured emission {round N · finding-origin tally · per-backend verdict}. Only a
@@ -221,7 +221,7 @@ const REVIEW_RECIPES = new Set(['reviewed', 'council']);
221
221
  const reviewLoopAdvice = (slots, activity) =>
222
222
  slots.some((s) => REVIEW_RECIPES.has(s.recipe))
223
223
  ? [
224
- 'Review-loop economics (planning.md §9 · orchestration.md §4) — the review this recipe runs:',
224
+ 'Review-loop economics (procedures.md Fold + loop · orchestration.md §4) — the review this recipe runs:',
225
225
  ' • Cap architecture plan-review at ≤2 rounds; the bar is met by RAISING a surviving major to an acceptance invariant (or handing it to Execute/diff-review), never by exhausting the strictest backend.',
226
226
  ' • Backend divergence (one backend grounded-ships while another keeps revising mechanics) IS the crossover stop.',
227
227
  ' • Route an all-mechanics/CI or prose-only artifact to a thin plan + diff-review; run a self-consistency read before every re-review.',
@@ -299,7 +299,7 @@ const autonomyAdvice = (activity, facts) => {
299
299
  };
300
300
 
301
301
  // The cost-lane advisory block (cost-tiered execution — orchestration.md §5 canon, paraphrased
302
- // at the point of use like reviewLoopAdvice paraphrases §9/§4). Rendered UNCONDITIONALLY for
302
+ // at the point of use like reviewLoopAdvice paraphrases procedures.md Fold + loop / orchestration §4). Rendered UNCONDITIONALLY for
303
303
  // every activity — the lanes route EVERY step, review-backed or not (unlike reviewLoopAdvice,
304
304
  // which fires only when a review backend engages). It may name the kit's own GENERIC L0
305
305
  // surfaces (the gate runner, the rotation checks, the cheap-agents vehicles) — point-of-use
@@ -368,9 +368,9 @@ const flowHalvesAdvice = (flow, probe) => {
368
368
  };
369
369
 
370
370
  // ── the declared source-size practice (D-17 U1) ────────────────────────────────────
371
- // A practice the agent meets only when a gate refuses is a practice learned too late: the caps, their
372
- // reason and the plan-time rung ride EVERY named-activity render, so the layout is cut to them while
373
- // the plan is being written. Composed from the project's live declaration, never from constants here.
371
+ // A practice the agent meets only when a gate refuses is a practice learned too late: the caps and
372
+ // their reason ride EVERY named-activity render, so the plan's Module ledger is cut to them while the
373
+ // plan is being written. Composed from the project's live declaration, never from constants here.
374
374
  // Each config state speaks as itself: ABSENT renders NOTHING (a project that declares no practice must
375
375
  // not be handed invented limits); AUTHORED and INCOMPLETE render the declared caps plus the honest
376
376
  // "nothing is recorded yet" line — both are pre-mint states, and treating INCOMPLETE as MINTED would
@@ -406,7 +406,6 @@ const declaredPracticeAdvice = (cwd, readFile, lstat) => {
406
406
  ? ` recorded: ${facts.recordedFiles} file(s) carry a recorded size (debt, not permission) · aggregate ${facts.aggregateLines} line(s), EXACT — growth takes a reasoned bump, never free headroom.`
407
407
  : unmintedRecord,
408
408
  ` why: ${SOURCE_SIZE_WHY}`,
409
- ' at plan time: every Step that CREATES a file names the file and its single responsibility, and the planned layout fits these caps — the gate is the backstop, never the teacher.',
410
409
  ];
411
410
  };
412
411