@sabaiway/agent-workflow-kit 5.5.0 → 5.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (55) hide show
  1. package/CHANGELOG.md +122 -0
  2. package/README.md +1 -1
  3. package/SKILL.md +1 -1
  4. package/capability.json +1 -1
  5. package/package.json +1 -1
  6. package/references/hooks/gate-approve.mjs +7 -1
  7. package/references/modes/doc-parity.md +1 -1
  8. package/references/modes/gates.md +20 -4
  9. package/references/modes/procedures.md +2 -0
  10. package/references/modes/recommendations.md +4 -1
  11. package/references/modes/review-state.md +1 -1
  12. package/references/modes/setup.md +18 -2
  13. package/references/modes/upgrade.md +38 -18
  14. package/references/modes/velocity.md +1 -0
  15. package/references/scripts/migrate-gates-branches.test.mjs +146 -1
  16. package/references/scripts/migrate-gates.mjs +295 -60
  17. package/references/scripts/migrate-gates.test.mjs +206 -14
  18. package/references/shared/deploy-tail.md +1 -1
  19. package/references/templates/gates.json +1 -1
  20. package/tools/ack-write.mjs +20 -11
  21. package/tools/atomic-write.mjs +71 -18
  22. package/tools/checker-claim.mjs +100 -0
  23. package/tools/coverage-producer.mjs +43 -6
  24. package/tools/direct-run.mjs +76 -0
  25. package/tools/doc-parity.mjs +34 -3
  26. package/tools/engine-source.mjs +12 -8
  27. package/tools/ensure-configs.mjs +141 -0
  28. package/tools/ensure-ops.mjs +284 -0
  29. package/tools/ensure-vocabulary.mjs +71 -0
  30. package/tools/flow-check-cores.mjs +253 -0
  31. package/tools/flow-check-git-lane.mjs +56 -0
  32. package/tools/flow-check-rungs.mjs +330 -0
  33. package/tools/flow-check.mjs +23 -611
  34. package/tools/gates-declaration.mjs +36 -11
  35. package/tools/gates-init.mjs +140 -25
  36. package/tools/hide-footprint.mjs +21 -3
  37. package/tools/lens-region.mjs +74 -23
  38. package/tools/orchestration-config.mjs +5 -3
  39. package/tools/orchestration-write.mjs +7 -0
  40. package/tools/procedures.mjs +64 -5
  41. package/tools/recommendations.mjs +384 -34
  42. package/tools/refresh-parity.mjs +263 -0
  43. package/tools/run-gates.mjs +8 -5
  44. package/tools/setup-backends.mjs +88 -77
  45. package/tools/source-size-check.mjs +310 -0
  46. package/tools/source-size-config.mjs +244 -0
  47. package/tools/source-size-core.mjs +59 -0
  48. package/tools/source-size-gate-cmd.mjs +27 -0
  49. package/tools/source-size-judge.mjs +114 -0
  50. package/tools/source-size-refusal.mjs +70 -0
  51. package/tools/source-size-report.mjs +254 -0
  52. package/tools/source-size-scope.mjs +145 -0
  53. package/tools/tracked-tree-census.mjs +102 -0
  54. package/tools/upgrade-runlist.mjs +92 -0
  55. package/tools/velocity-profile.mjs +24 -3
@@ -1,4 +1,3 @@
1
- #!/usr/bin/env node
2
1
  // orchestration-config.mjs — the schema / read / pure-transform core for the per-project
3
2
  // orchestration config (docs/ai/orchestration.json). It is the SINGLE source of the config contract:
4
3
  //
@@ -16,12 +15,13 @@
16
15
  //
17
16
  // This module performs NO filesystem WRITES — only reads (loadConfig). The single fs-writer lives in
18
17
  // orchestration-write.mjs, which procedures.mjs never imports DIRECTLY (the pinned import-split
19
- // rule). Pure-where-possible (fs injectable), dependency-free, Node >= 22. No side
20
- // effects on import.
18
+ // rule). It has NO CLI while upgrade.md names it — hence the registered refusal at the foot of the
19
+ // file (direct-run.mjs), and no shebang. Fs-injectable, dependency-free, Node >= 22; nothing on import.
21
20
 
22
21
  import { readFileSync, lstatSync } from 'node:fs';
23
22
  import { join } from 'node:path';
24
23
  import { ACTIVITIES, SLOT_RECIPES } from './recipes.mjs';
24
+ import { refuseDirectRun } from './direct-run.mjs';
25
25
 
26
26
  // The hand-editable / agent-writable, per-project config (strict JSON). cwd-relative — the error prefix
27
27
  // uses this rel path so a user sees a path they can open, never an absolute temp/host path.
@@ -396,3 +396,5 @@ export const refreshReadme = (config) => {
396
396
 
397
397
  // The canonical seed file body (what `init` deploys + what serializeConfig round-trips byte-identically).
398
398
  export const SEED_CONFIG = { _README: CANON_README, 'plan-authoring': { review: 'solo' }, 'plan-execution': { execute: 'solo', review: 'solo' } };
399
+
400
+ refuseDirectRun(import.meta.url);
@@ -25,3 +25,10 @@ const stop = (message) => Object.assign(new Error(`[agent-workflow-kit] ${messag
25
25
  // creation. config is serialized canonically (serializeConfig: 2-space, _README-first, trailing NL).
26
26
  export const writeConfig = (cwd, config, deps = {}) =>
27
27
  writeDocsAiFileAtomic(cwd, CONFIG_REL, serializeConfig(config), deps, { stop, noun: 'a config' });
28
+
29
+ // seedConfig(cwd, config, deps) → { writtenPath, created }. Same writer, CREATE-ONLY: it is the
30
+ // seed-if-missing arm the ensure CLI runs, where a config that appeared between the probe and the
31
+ // write must survive untouched (`created: false` says it did). writeConfig stays the arm for a
32
+ // content update of a file the caller has just read.
33
+ export const seedConfig = (cwd, config, deps = {}) =>
34
+ writeDocsAiFileAtomic(cwd, CONFIG_REL, serializeConfig(config), deps, { stop, noun: 'a config', createOnly: true });
@@ -5,7 +5,8 @@
5
5
  // steps LIVE from the installed agent-workflow-engine (references/procedures.md — AD-016 live read, no
6
6
  // bundled mirror), reads the per-project, hand-edited config (docs/ai/orchestration.json), runs the
7
7
  // read-only backend detector, and prints the activity's steps VERBATIM + the resolved effective recipe
8
- // per slot (default = Reviewed-when-a-backend-is-ready, Council on request, slot-aware incl. Delegated).
8
+ // per slot (default = Reviewed-when-a-backend-is-ready, Council on request, slot-aware incl. Delegated),
9
+ // plus the project's DECLARED source-size practice when it declares one (D-17 U1).
9
10
  //
10
11
  // Invariants (mirror recipes.mjs): pure-where-possible, READ-ONLY (never writes, never commits, never
11
12
  // runs a subscription CLI). The deterministic resolution lives in the kit (resolveActivityRecipe), not
@@ -44,6 +45,10 @@ import { AUTONOMY_REL, loadAutonomy, resolveAutonomy, isSparseSeedConfig } from
44
45
  // acyclic — pinned by test/read-graph-purity.test.mjs (FLOW-READ-GRAPH-PURITY).
45
46
  import { resolveFlowStorePath, readFlowStore } from './flow-store-read.mjs';
46
47
  import { CHAIN_KIND } from './flow-record.mjs';
48
+ // The declared source-size practice (D-17 U1), read through the practice's PURE READ core — never
49
+ // source-size-check.mjs, which owns the writer half: this advisor is a read root of
50
+ // test/read-graph-purity.test.mjs, and the core exists so a surface can ask without reaching a writer.
51
+ import { SOURCE_SIZE_CONFIG_REL, SOURCE_SIZE_WHY, loadSourceSizeConfig, practiceFacts } from './source-size-core.mjs';
47
52
  export { CONFIG_REL };
48
53
 
49
54
  // ── argument + override parsing (usage errors → exit 2) ─────────────────────────────
@@ -361,6 +366,49 @@ const flowHalvesAdvice = (flow, probe) => {
361
366
  ];
362
367
  };
363
368
 
369
+ // ── the declared source-size practice (D-17 U1) ────────────────────────────────────
370
+ // A practice the agent meets only when a gate refuses is a practice learned too late: the caps, their
371
+ // reason and the plan-time rung ride EVERY named-activity render, so the layout is cut to them while
372
+ // the plan is being written. Composed from the project's live declaration, never from constants here.
373
+ // Each config state speaks as itself: ABSENT renders NOTHING (a project that declares no practice must
374
+ // not be handed invented limits); AUTHORED and INCOMPLETE render the declared caps plus the honest
375
+ // "nothing is recorded yet" line — both are pre-mint states, and treating INCOMPLETE as MINTED would
376
+ // report a half record as the whole tree's debt; MINTED renders the recorded counts too.
377
+ // A config that cannot be read renders ONE loud line carrying the reader's own message and the render
378
+ // still completes: the exit code for a broken source-size config belongs to the practice's own
379
+ // checker (exit 2 there, and its declared gate reds the matrix on it), while THIS tool's exit
380
+ // contract is about its own config and the engine.
381
+
382
+ export const DECLARED_PRACTICE_HEADER = `Declared source-size practice (${SOURCE_SIZE_CONFIG_REL}) — known BEFORE the code is written:`;
383
+
384
+ const declaredPracticeAdvice = (cwd, readFile, lstat) => {
385
+ let declaration;
386
+ try {
387
+ declaration = loadSourceSizeConfig(cwd, { readFile, lstat });
388
+ } catch (err) {
389
+ return [`Declared source-size practice: UNREADABLE — ${(err && err.message) || err} — fix ${SOURCE_SIZE_CONFIG_REL} by hand; a declared practice is never guessed around.`];
390
+ }
391
+ if (declaration.state === 'absent') return [];
392
+ const facts = practiceFacts(declaration.config);
393
+ // The two pre-mint states share a LANE (the ratchet holds nothing, the mint step is next) but not a
394
+ // FACT: an incomplete file carries half the machine record, so "no size is recorded" would be a
395
+ // plain untruth about it — it names the missing half instead. Neither ever prints minted counts.
396
+ // The mint step is named, not rendered: on a project path that does not survive double-quoting the
397
+ // checker deliberately withholds a paste-ready command, and this advisor never re-decides that.
398
+ const unmintedRecord = declaration.state === 'incomplete'
399
+ ? ` recorded: PARTIAL — the machine record is half-written (missing ${declaration.missingMachineKeys.map((key) => `"${key}"`).join(', ')}), so the ratchet holds nothing yet; run \`source-size-check.mjs --check\` for the mint step.`
400
+ : ' recorded: NOTHING YET — the caps are declared but no size is recorded, so the ratchet holds nothing; run `source-size-check.mjs --check` for the mint step.';
401
+ return [
402
+ DECLARED_PRACTICE_HEADER,
403
+ ` caps: ${facts.maxLines} lines · ${facts.maxLineBytes} bytes per line, over ${facts.roots} declared root(s).`,
404
+ declaration.state === 'minted'
405
+ ? ` 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.`
406
+ : unmintedRecord,
407
+ ` why: ${SOURCE_SIZE_WHY}`,
408
+ ' 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.',
409
+ ];
410
+ };
411
+
364
412
  // The verbatim per-backend DRIVING CONTRACT block (M-contract): the exact invocation descriptor(s),
365
413
  // the closed flag set, the grounding note, the round-2/continue delta, and the guarded passthrough
366
414
  // tiers — every descriptor printed VERBATIM from the registry mirror of the bridge manifest
@@ -397,7 +445,7 @@ const contractLines = ({ cmd, contract, settings }) => {
397
445
  return lines;
398
446
  };
399
447
 
400
- const formatHuman = ({ activity, section, slots, warnings, plans, autonomy, flowHalves }) => {
448
+ const formatHuman = ({ activity, section, slots, warnings, plans, autonomy, flowHalves, declaredPractice }) => {
401
449
  const lines = [
402
450
  section,
403
451
  '',
@@ -417,6 +465,7 @@ const formatHuman = ({ activity, section, slots, warnings, plans, autonomy, flow
417
465
  const advice = reviewLoopAdvice(slots, activity);
418
466
  if (advice.length) lines.push('', ...advice);
419
467
  lines.push('', ...costLanesAdvice());
468
+ if (declaredPractice.length) lines.push('', ...declaredPractice);
420
469
  if (warnings.length) {
421
470
  lines.push('', 'warnings:');
422
471
  for (const w of warnings) lines.push(` ⚠ ${w}`);
@@ -424,7 +473,7 @@ const formatHuman = ({ activity, section, slots, warnings, plans, autonomy, flow
424
473
  return lines.join('\n');
425
474
  };
426
475
 
427
- const buildJson = ({ activity, section, slots, configSource, warnings, plans, autonomy, flowHalves }) => ({
476
+ const buildJson = ({ activity, section, slots, configSource, warnings, plans, autonomy, flowHalves, declaredPractice }) => ({
428
477
  activity,
429
478
  section,
430
479
  slots: Object.fromEntries(
@@ -439,6 +488,9 @@ const buildJson = ({ activity, section, slots, configSource, warnings, plans, au
439
488
  costLanes: costLanesAdvice(),
440
489
  // ADDITIVE (AD-044 Plan 4): the per-activity autonomy block, structured (empty when unresolvable).
441
490
  autonomy: autonomyAdvice(activity, autonomy),
491
+ // ADDITIVE (D-17 U1): the SAME composed lines the human render prints — one array, two renders, so
492
+ // a scripted reader and a human can never be told different things about the declared practice.
493
+ declaredPractice,
442
494
  // CONDITIONAL (flow P8): the armed-halves block rides ONLY a flow-carrying config — the unarmed
443
495
  // JSON key set stays byte-exact (unarmed neutrality outranks the additive-key precedent).
444
496
  ...(flowHalves == null ? {} : { flowHalves }),
@@ -461,6 +513,12 @@ ${CONFIG_REL} + the read-only backend detector, and prints both. A per-run
461
513
  --override <slot>=<recipe> (repeatable) overrides the configured/default recipe for that slot.
462
514
  Read-only: never writes, never commits, never runs a subscription CLI.
463
515
 
516
+ Also prints the project's DECLARED source-size practice (${SOURCE_SIZE_CONFIG_REL}) when it declares
517
+ one — the caps, what is recorded, why the practice exists, and the plan-time rung — as the
518
+ declaredPractice block (--json: the same lines under "declaredPractice"). A project with no such
519
+ file renders nothing; a file that cannot be read renders ONE loud UNREADABLE line and still exits 0
520
+ (the practice's own checker owns the exit code for its config).
521
+
464
522
  Exit codes: 0 success (an unsatisfiable override degrades loudly, still 0);
465
523
  2 usage (unknown activity / bad --override); 1 config or engine error
466
524
  (incl. a malformed ${AUTONOMY_REL} — the advisory still renders, the exit flips).`;
@@ -515,9 +573,10 @@ export const main = (argv, ctx = {}) => {
515
573
  // unarmed project keeps byte-identical output (human AND JSON) and never pays the store probe.
516
574
  const flowProbe = ctx.flowProbe ?? defaultFlowProbe;
517
575
  const flowHalves = config?.flow == null ? null : flowHalvesAdvice(config.flow, flowProbe(cwd));
576
+ const declaredPractice = declaredPracticeAdvice(cwd, readFile, lstat);
518
577
  const stdout = json
519
- ? JSON.stringify(buildJson({ activity, section, slots, configSource, warnings, plans, autonomy, flowHalves }), null, 2)
520
- : formatHuman({ activity, section, slots, warnings, plans, autonomy, flowHalves });
578
+ ? JSON.stringify(buildJson({ activity, section, slots, configSource, warnings, plans, autonomy, flowHalves, declaredPractice }), null, 2)
579
+ : formatHuman({ activity, section, slots, warnings, plans, autonomy, flowHalves, declaredPractice });
521
580
  if (autonomy?.error) {
522
581
  return { code: 1, stdout, stderr: `procedures: malformed ${AUTONOMY_REL} — ${autonomy.error}` };
523
582
  }