@sabaiway/agent-workflow-kit 5.11.0 → 5.11.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.
- package/CHANGELOG.md +61 -0
- package/SKILL.md +1 -1
- package/capability.json +1 -1
- package/package.json +1 -1
- package/references/hooks/gate-approve.mjs +13 -2
- package/references/hooks/state-block-guard.mjs +14 -2
- package/references/scripts/archive-changelog.mjs +14 -3
- package/references/scripts/archive-decisions.mjs +14 -3
- package/references/scripts/archive-issues.mjs +14 -3
- package/references/scripts/check-docs-size.mjs +14 -3
- package/references/scripts/migrate-gates.mjs +13 -2
- package/tools/ack-write.mjs +3 -3
- package/tools/autonomy-doctor.mjs +2 -3
- package/tools/bridge-settings.mjs +2 -3
- package/tools/cheap-agents.mjs +3 -3
- package/tools/commands.mjs +2 -3
- package/tools/commit-guard.mjs +3 -3
- package/tools/core-evidence.mjs +2 -3
- package/tools/coverage-check.mjs +2 -3
- package/tools/delegation.mjs +2 -3
- package/tools/detect-backends.mjs +2 -3
- package/tools/doc-parity.mjs +2 -3
- package/tools/family-registry.mjs +3 -3
- package/tools/flow-adoption-mint.mjs +70 -0
- package/tools/flow-append.mjs +309 -0
- package/tools/flow-chain-state.mjs +91 -0
- package/tools/flow-check.mjs +2 -3
- package/tools/flow-delta-proof.mjs +307 -0
- package/tools/flow-record.mjs +1 -1
- package/tools/flow-store-read.mjs +3 -3
- package/tools/flow-store.mjs +35 -812
- package/tools/flow-subset-budget.mjs +81 -0
- package/tools/flow-writer.mjs +3 -3
- package/tools/gate-hook.mjs +3 -3
- package/tools/gates-init.mjs +3 -3
- package/tools/grounding.mjs +2 -3
- package/tools/hide-footprint.mjs +2 -3
- package/tools/inject-methodology.mjs +2 -3
- package/tools/lens-region.mjs +2 -3
- package/tools/manifest/validate.mjs +2 -3
- package/tools/migrate-adr-store.mjs +3 -3
- package/tools/path-inventory.mjs +2 -3
- package/tools/procedures.mjs +3 -3
- package/tools/receipt-deadline.mjs +2 -3
- package/tools/recipes.mjs +2 -3
- package/tools/recommendations.mjs +3 -3
- package/tools/release-scan.mjs +2 -3
- package/tools/repo-search.mjs +2 -3
- package/tools/review-state.mjs +3 -3
- package/tools/run-gates.mjs +2 -3
- package/tools/sandbox-masks.mjs +3 -3
- package/tools/set-autonomy.mjs +2 -3
- package/tools/set-flow.mjs +3 -3
- package/tools/set-recipe.mjs +2 -3
- package/tools/setup-backends.mjs +3 -3
- package/tools/store-append.mjs +2 -2
- package/tools/uninstall.mjs +2 -3
- package/tools/velocity-profile.mjs +3 -3
- package/tools/worktrees.mjs +3 -3
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,67 @@ Semantically versioned ([semver](https://semver.org)), newest first. The `versio
|
|
|
4
4
|
is the current release. `upgrade` mode reads a project's `docs/ai/.workflow-version` and applies
|
|
5
5
|
every `migrations/<version>-<slug>.md` newer than it, in semver order.
|
|
6
6
|
|
|
7
|
+
## 5.11.1 — the flow store becomes a facade over five leaves, and the direct-run guard fix finally reaches you (AD-102)
|
|
8
|
+
|
|
9
|
+
**A tool invoked through a symlink used to run nothing and exit 0 — and five of the affected tools are
|
|
10
|
+
declared gates.** That fix has been sitting in-repo, unpublished, since the delegation series' own
|
|
11
|
+
measurement came back FAIL and the fix was correctly held back from a release it should not have
|
|
12
|
+
justified (AD-101). It ships here, on a regular train, because it is a bug fix and every deployed
|
|
13
|
+
host still carries the broken form. Beside it, the flow store stops being one 827-line module.
|
|
14
|
+
|
|
15
|
+
- **The direct-run entry guard now decides by REAL PATH, in all 66 frozen sites.** The lexical form
|
|
16
|
+
compared `import.meta.url` against `process.argv[1]`, which is false when the entry point is a
|
|
17
|
+
symlink — so the tool did nothing, said nothing, and exited 0. Every one of the 39 kit tools now
|
|
18
|
+
decides through the shipped `isDirectRun(import.meta.url)` leaf; the seven mirrored
|
|
19
|
+
`references/scripts` and `references/hooks` payload scripts, which cannot reach that leaf from a
|
|
20
|
+
deployed project, inline the same fail-closed realpath guard. `velocity-profile.mjs` also drops a
|
|
21
|
+
vestigial exported predicate with no importer. The new `test/direct-run-sweep.test.mjs` pins list
|
|
22
|
+
completeness, in-process import of all 39, the absence of the lexical byte pattern, and a SPAWNED
|
|
23
|
+
symlink smoke — observed red 3/3 before the fix, so the silent-pass class is proven dead rather than
|
|
24
|
+
believed dead.
|
|
25
|
+
- **`tools/flow-store.mjs` keeps its path, its 29 export names and every one of its 22 import sites,
|
|
26
|
+
and becomes a 50-line re-export facade.** The flow-specific write side moved verbatim into five
|
|
27
|
+
leaves with one responsibility each and one-way edges: `flow-chain-state.mjs` (91, the pure chain
|
|
28
|
+
walk and the prior-terminal reference validator) · `flow-subset-budget.mjs` (81, the pure
|
|
29
|
+
Decision-7/8 counting-context budget) · `flow-append.mjs` (309, the store's ONE write door, keeping
|
|
30
|
+
the locked subset-attempt factory beside the lane it is the only sanctioned entrance to) ·
|
|
31
|
+
`flow-adoption-mint.mjs` (70) · `flow-delta-proof.mjs` (307, the bookkeeping-delta custody proof).
|
|
32
|
+
Nothing about the public surface moved, so nothing you import changes.
|
|
33
|
+
- **The split is proven, not asserted.** A line-multiset conservation check compares the pre-split
|
|
34
|
+
module against the union of the five leaves and expects exactly two lines to have gained a copy —
|
|
35
|
+
and it was run twice more, against the unsplit module alone and with one leaf omitted, so that a
|
|
36
|
+
comparator which cannot report loss could not pass for one that does. The three owning suites are
|
|
37
|
+
BYTE-IDENTICAL and reproduce 169/169; the ten named suites show an empty `git diff --name-only`.
|
|
38
|
+
`test/flow-store-layout.test.mjs` (not shipped) now pins the frozen surface, that every facade name
|
|
39
|
+
is the SAME binding as its leaf's export, that the facade carries no logic at all, the size caps,
|
|
40
|
+
and the one-way import direction including its negative edges.
|
|
41
|
+
- **`test/package-content.test.mjs` moves its payload pin from 222 to 227 files** and names the five
|
|
42
|
+
leaves: the facade imports all five, so a leaf falling out of the published tarball would break
|
|
43
|
+
every append at load, and the pin is what says so.
|
|
44
|
+
|
|
45
|
+
Recorded size effect, reason `tranche 2: flow-store split`:
|
|
46
|
+
|
|
47
|
+
```text
|
|
48
|
+
agent-workflow-kit/test/package-content.test.mjs: lines 583 -> 600 (raise)
|
|
49
|
+
agent-workflow-kit/tools/flow-store.mjs: lines 827 -> none
|
|
50
|
+
agent-workflow-kit: aggregate lines 124960 -> 125315 (raise)
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
Recorded size effect of the direct-run sweep that ships with it (reason: a standalone deployed script
|
|
54
|
+
cannot reach the kit's shared direct-run leaf, so it inlines the realpath guard — one lexical line
|
|
55
|
+
becomes an 11-line fail-closed IIFE; the 39 converted tool guards themselves only shrink):
|
|
56
|
+
|
|
57
|
+
```text
|
|
58
|
+
agent-workflow-kit/references/hooks/gate-approve.mjs: lines 559 -> 570 (raise)
|
|
59
|
+
agent-workflow-kit/references/hooks/state-block-guard.mjs: lines 425 -> 437 (raise)
|
|
60
|
+
agent-workflow-kit/references/scripts/archive-changelog.mjs: lines 546 -> 557 (raise)
|
|
61
|
+
agent-workflow-kit/references/scripts/archive-decisions.mjs: lines 1199 -> 1210 (raise)
|
|
62
|
+
agent-workflow-kit/references/scripts/archive-issues.mjs: lines 415 -> 426 (raise)
|
|
63
|
+
agent-workflow-kit/references/scripts/check-docs-size.mjs: lines 580 -> 591 (raise)
|
|
64
|
+
agent-workflow-kit/references/scripts/migrate-gates.mjs: lines 722 -> 733 (raise)
|
|
65
|
+
agent-workflow-kit: aggregate lines 124726 -> 124805 -> 124925 -> 124960 (raise)
|
|
66
|
+
```
|
|
67
|
+
|
|
7
68
|
## 5.11.0 — the fan-out half: which vehicle carries a sub-task, what a satellite is told, and what its handoff brings back (AD-100)
|
|
8
69
|
|
|
9
70
|
**A delegated thread could be recorded end to end, and the two questions around it still had no
|
package/SKILL.md
CHANGED
|
@@ -3,7 +3,7 @@ name: agent-workflow-kit
|
|
|
3
3
|
description: Deploy or upgrade a portable AI-agent memory-and-workflow system in any project. Use when the user wants to bootstrap `docs/ai/` + an entry-point `AGENTS.md` (+ `CLAUDE.md` alias) + cap/archive/index enforcement in a new or existing repo, set up the Memory Map and session protocols, install the docs-rotation pre-commit hook, or run `/agent-workflow-kit` / `/agent-workflow-kit upgrade`. Triggers on phrases like "set up the memory system", "deploy the AI workflow here", "bootstrap docs/ai", "upgrade the workflow".
|
|
4
4
|
disable-model-invocation: true
|
|
5
5
|
metadata:
|
|
6
|
-
version: '5.11.
|
|
6
|
+
version: '5.11.1'
|
|
7
7
|
---
|
|
8
8
|
|
|
9
9
|
# agent-workflow-kit
|
package/capability.json
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sabaiway/agent-workflow-kit",
|
|
3
|
-
"version": "5.11.
|
|
3
|
+
"version": "5.11.1",
|
|
4
4
|
"description": "Portable, cross-agent memory & workflow for AI coding agents — Claude Code, Codex, Cursor, Devin Desktop. One command deploys an AGENTS.md entry point + docs/ai context with cap/archive/index enforcement into any repo.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"ai-agents",
|
|
@@ -85,7 +85,7 @@
|
|
|
85
85
|
|
|
86
86
|
import { readFileSync, realpathSync } from 'node:fs';
|
|
87
87
|
import { join } from 'node:path';
|
|
88
|
-
import {
|
|
88
|
+
import { fileURLToPath } from 'node:url';
|
|
89
89
|
|
|
90
90
|
export const HOOK_EVENT_NAME = 'PreToolUse';
|
|
91
91
|
export const BASH_TOOL_NAME = 'Bash';
|
|
@@ -555,5 +555,16 @@ export const main = async () => {
|
|
|
555
555
|
return EXIT_OK;
|
|
556
556
|
};
|
|
557
557
|
|
|
558
|
-
|
|
558
|
+
// Run main() only when executed directly, never on import. Compare by REAL path: an entry point
|
|
559
|
+
// reached through a symlink resolves to its target, so a raw string compare reads the two as
|
|
560
|
+
// different and the CLI never runs. realpathSync collapses the link so both sides match.
|
|
561
|
+
const isDirectRun = (() => {
|
|
562
|
+
const invoked = process.argv[1];
|
|
563
|
+
if (!invoked) return false;
|
|
564
|
+
try {
|
|
565
|
+
return realpathSync(invoked) === realpathSync(fileURLToPath(import.meta.url));
|
|
566
|
+
} catch {
|
|
567
|
+
return false;
|
|
568
|
+
}
|
|
569
|
+
})();
|
|
559
570
|
if (isDirectRun) main().then((code) => process.exit(code));
|
|
@@ -37,7 +37,8 @@
|
|
|
37
37
|
// fields (`decision`, `continue`, `stopReason`, `hookSpecificOutput`) are deliberately never emitted:
|
|
38
38
|
// blocking the stop would re-enter the model on a message already sent to the reader.
|
|
39
39
|
|
|
40
|
-
import {
|
|
40
|
+
import { realpathSync } from 'node:fs';
|
|
41
|
+
import { fileURLToPath } from 'node:url';
|
|
41
42
|
|
|
42
43
|
export const HOOK_EVENT_NAME = 'Stop';
|
|
43
44
|
const EXIT_OK = 0;
|
|
@@ -419,7 +420,18 @@ export const main = async ({
|
|
|
419
420
|
return EXIT_OK;
|
|
420
421
|
};
|
|
421
422
|
|
|
423
|
+
// Run main() only when executed directly, never on import. Compare by REAL path: an entry point
|
|
424
|
+
// reached through a symlink resolves to its target, so a raw string compare reads the two as
|
|
425
|
+
// different and the CLI never runs. realpathSync collapses the link so both sides match.
|
|
426
|
+
const isDirectRun = (() => {
|
|
427
|
+
const invoked = process.argv[1];
|
|
428
|
+
if (!invoked) return false;
|
|
429
|
+
try {
|
|
430
|
+
return realpathSync(invoked) === realpathSync(fileURLToPath(import.meta.url));
|
|
431
|
+
} catch {
|
|
432
|
+
return false;
|
|
433
|
+
}
|
|
434
|
+
})();
|
|
422
435
|
// `process.exitCode`, never `process.exit()`: an immediate exit can truncate a pending stdout write
|
|
423
436
|
// to a pipe, and that single warning is the entire product of this hook.
|
|
424
|
-
const isDirectRun = process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href;
|
|
425
437
|
if (isDirectRun) main().then((code) => { process.exitCode = code; });
|
|
@@ -32,9 +32,9 @@
|
|
|
32
32
|
// --warm-days=N (default 30)
|
|
33
33
|
// --today=YYYY-MM-DD (default today UTC) — useful for tests / reproducible runs
|
|
34
34
|
|
|
35
|
-
import { readFileSync, writeFileSync, mkdirSync, readdirSync, existsSync } from 'node:fs';
|
|
35
|
+
import { readFileSync, writeFileSync, mkdirSync, readdirSync, existsSync, realpathSync } from 'node:fs';
|
|
36
36
|
import { dirname, resolve, basename } from 'node:path';
|
|
37
|
-
import { fileURLToPath
|
|
37
|
+
import { fileURLToPath } from 'node:url';
|
|
38
38
|
import { tokenizeMarkdown, findParagraphBreak, fail } from './markdown-blocks.mjs';
|
|
39
39
|
|
|
40
40
|
const __filename = fileURLToPath(import.meta.url);
|
|
@@ -542,5 +542,16 @@ export const runCli = (argv, deps = {}) => {
|
|
|
542
542
|
}
|
|
543
543
|
};
|
|
544
544
|
|
|
545
|
-
|
|
545
|
+
// Run main() only when executed directly, never on import. Compare by REAL path: an entry point
|
|
546
|
+
// reached through a symlink resolves to its target, so a raw string compare reads the two as
|
|
547
|
+
// different and the CLI never runs. realpathSync collapses the link so both sides match.
|
|
548
|
+
const isDirectRun = (() => {
|
|
549
|
+
const invoked = process.argv[1];
|
|
550
|
+
if (!invoked) return false;
|
|
551
|
+
try {
|
|
552
|
+
return realpathSync(invoked) === realpathSync(fileURLToPath(import.meta.url));
|
|
553
|
+
} catch {
|
|
554
|
+
return false;
|
|
555
|
+
}
|
|
556
|
+
})();
|
|
546
557
|
if (isDirectRun) process.exitCode = runCli(process.argv.slice(2));
|
|
@@ -75,9 +75,9 @@
|
|
|
75
75
|
//
|
|
76
76
|
// Dependency-free, Node >= 22. Deployed into a consumer's scripts/ like its siblings.
|
|
77
77
|
|
|
78
|
-
import { readFileSync, writeFileSync, existsSync, mkdirSync, readdirSync, rmSync, statSync } from 'node:fs';
|
|
78
|
+
import { readFileSync, writeFileSync, existsSync, mkdirSync, readdirSync, rmSync, statSync, realpathSync } from 'node:fs';
|
|
79
79
|
import { dirname, resolve, join, posix } from 'node:path';
|
|
80
|
-
import { fileURLToPath
|
|
80
|
+
import { fileURLToPath } from 'node:url';
|
|
81
81
|
import { spawnSync } from 'node:child_process';
|
|
82
82
|
import { createHash } from 'node:crypto';
|
|
83
83
|
import { tmpdir } from 'node:os';
|
|
@@ -1195,5 +1195,16 @@ export const runCli = (argv, deps = {}) => {
|
|
|
1195
1195
|
}
|
|
1196
1196
|
};
|
|
1197
1197
|
|
|
1198
|
-
|
|
1198
|
+
// Run main() only when executed directly, never on import. Compare by REAL path: an entry point
|
|
1199
|
+
// reached through a symlink resolves to its target, so a raw string compare reads the two as
|
|
1200
|
+
// different and the CLI never runs. realpathSync collapses the link so both sides match.
|
|
1201
|
+
const isDirectRun = (() => {
|
|
1202
|
+
const invoked = process.argv[1];
|
|
1203
|
+
if (!invoked) return false;
|
|
1204
|
+
try {
|
|
1205
|
+
return realpathSync(invoked) === realpathSync(fileURLToPath(import.meta.url));
|
|
1206
|
+
} catch {
|
|
1207
|
+
return false;
|
|
1208
|
+
}
|
|
1209
|
+
})();
|
|
1199
1210
|
if (isDirectRun) process.exitCode = runCli(process.argv.slice(2));
|
|
@@ -31,9 +31,9 @@
|
|
|
31
31
|
// --cutoff-days=N (default 14)
|
|
32
32
|
// --today=YYYY-MM-DD (default UTC today)
|
|
33
33
|
|
|
34
|
-
import { readFileSync, writeFileSync, mkdirSync, existsSync } from 'node:fs';
|
|
34
|
+
import { readFileSync, writeFileSync, mkdirSync, existsSync, realpathSync } from 'node:fs';
|
|
35
35
|
import { dirname, resolve, basename } from 'node:path';
|
|
36
|
-
import { fileURLToPath
|
|
36
|
+
import { fileURLToPath } from 'node:url';
|
|
37
37
|
import { tokenizeMarkdown, fail } from './markdown-blocks.mjs';
|
|
38
38
|
|
|
39
39
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
@@ -411,5 +411,16 @@ export const runCli = (argv, deps = {}) => {
|
|
|
411
411
|
}
|
|
412
412
|
};
|
|
413
413
|
|
|
414
|
-
|
|
414
|
+
// Run main() only when executed directly, never on import. Compare by REAL path: an entry point
|
|
415
|
+
// reached through a symlink resolves to its target, so a raw string compare reads the two as
|
|
416
|
+
// different and the CLI never runs. realpathSync collapses the link so both sides match.
|
|
417
|
+
const isDirectRun = (() => {
|
|
418
|
+
const invoked = process.argv[1];
|
|
419
|
+
if (!invoked) return false;
|
|
420
|
+
try {
|
|
421
|
+
return realpathSync(invoked) === realpathSync(fileURLToPath(import.meta.url));
|
|
422
|
+
} catch {
|
|
423
|
+
return false;
|
|
424
|
+
}
|
|
425
|
+
})();
|
|
415
426
|
if (isDirectRun) process.exitCode = runCli(process.argv.slice(2));
|
|
@@ -25,9 +25,9 @@
|
|
|
25
25
|
// --quiet print only failures (and final summary)
|
|
26
26
|
|
|
27
27
|
import { readFile, writeFile, readdir, stat, rename, rm } from 'node:fs/promises';
|
|
28
|
-
import { existsSync, lstatSync } from 'node:fs';
|
|
28
|
+
import { existsSync, lstatSync, realpathSync } from 'node:fs';
|
|
29
29
|
import { dirname, resolve, relative, join, basename, sep } from 'node:path';
|
|
30
|
-
import { fileURLToPath
|
|
30
|
+
import { fileURLToPath } from 'node:url';
|
|
31
31
|
import { randomBytes } from 'node:crypto';
|
|
32
32
|
|
|
33
33
|
const __filename = fileURLToPath(import.meta.url);
|
|
@@ -571,7 +571,18 @@ export const runCli = async (argv, deps = {}) => {
|
|
|
571
571
|
return result(errorCount > 0 && !flags.report ? 1 : 0);
|
|
572
572
|
};
|
|
573
573
|
|
|
574
|
-
|
|
574
|
+
// Run main() only when executed directly, never on import. Compare by REAL path: an entry point
|
|
575
|
+
// reached through a symlink resolves to its target, so a raw string compare reads the two as
|
|
576
|
+
// different and the CLI never runs. realpathSync collapses the link so both sides match.
|
|
577
|
+
const isDirectRun = (() => {
|
|
578
|
+
const invoked = process.argv[1];
|
|
579
|
+
if (!invoked) return false;
|
|
580
|
+
try {
|
|
581
|
+
return realpathSync(invoked) === realpathSync(fileURLToPath(import.meta.url));
|
|
582
|
+
} catch {
|
|
583
|
+
return false;
|
|
584
|
+
}
|
|
585
|
+
})();
|
|
575
586
|
if (isDirectRun) {
|
|
576
587
|
const { code, stdout, stderr } = await runCli(process.argv.slice(2));
|
|
577
588
|
if (stdout) process.stdout.write(stdout);
|
|
@@ -28,7 +28,7 @@
|
|
|
28
28
|
|
|
29
29
|
import { existsSync, lstatSync, readFileSync, writeFileSync, renameSync, unlinkSync, realpathSync } from 'node:fs';
|
|
30
30
|
import { join, resolve, isAbsolute } from 'node:path';
|
|
31
|
-
import {
|
|
31
|
+
import { fileURLToPath } from 'node:url';
|
|
32
32
|
import { randomBytes } from 'node:crypto';
|
|
33
33
|
import { spawnSync } from 'node:child_process';
|
|
34
34
|
|
|
@@ -718,5 +718,16 @@ export const main = (argv = process.argv.slice(2), io = {}) => {
|
|
|
718
718
|
}
|
|
719
719
|
};
|
|
720
720
|
|
|
721
|
-
|
|
721
|
+
// Run main() only when executed directly, never on import. Compare by REAL path: an entry point
|
|
722
|
+
// reached through a symlink resolves to its target, so a raw string compare reads the two as
|
|
723
|
+
// different and the CLI never runs. realpathSync collapses the link so both sides match.
|
|
724
|
+
const isDirectRun = (() => {
|
|
725
|
+
const invoked = process.argv[1];
|
|
726
|
+
if (!invoked) return false;
|
|
727
|
+
try {
|
|
728
|
+
return realpathSync(invoked) === realpathSync(fileURLToPath(import.meta.url));
|
|
729
|
+
} catch {
|
|
730
|
+
return false;
|
|
731
|
+
}
|
|
732
|
+
})();
|
|
722
733
|
if (isDirectRun) process.exitCode = main();
|
package/tools/ack-write.mjs
CHANGED
|
@@ -27,7 +27,8 @@
|
|
|
27
27
|
|
|
28
28
|
import { lstatSync, readFileSync } from 'node:fs';
|
|
29
29
|
import { dirname, join, resolve } from 'node:path';
|
|
30
|
-
import { fileURLToPath
|
|
30
|
+
import { fileURLToPath } from 'node:url';
|
|
31
|
+
import { isDirectRun } from './direct-run.mjs';
|
|
31
32
|
import { ACKS_FILE, ACK_LANES } from './recommendations.mjs';
|
|
32
33
|
import { assertDocsAiDeployment, writeDocsAiFileAtomic, lstatNoFollow } from './atomic-write.mjs';
|
|
33
34
|
import { shellQuoteArg } from './review-state.mjs';
|
|
@@ -207,5 +208,4 @@ export const main = (argv = process.argv.slice(2), deps = {}) => {
|
|
|
207
208
|
}
|
|
208
209
|
};
|
|
209
210
|
|
|
210
|
-
|
|
211
|
-
if (isDirectRun) process.exit(main(process.argv.slice(2)));
|
|
211
|
+
if (isDirectRun(import.meta.url)) process.exit(main(process.argv.slice(2)));
|
|
@@ -40,7 +40,7 @@
|
|
|
40
40
|
import { spawnSync } from 'node:child_process';
|
|
41
41
|
import { closeSync, lstatSync, openSync } from 'node:fs';
|
|
42
42
|
import { join } from 'node:path';
|
|
43
|
-
import {
|
|
43
|
+
import { isDirectRun } from './direct-run.mjs';
|
|
44
44
|
import { isExecutableFile, probeSandboxAvailability } from './velocity-profile.mjs';
|
|
45
45
|
import { assertDocsAiDeployment } from './atomic-write.mjs';
|
|
46
46
|
|
|
@@ -484,5 +484,4 @@ export const main = (argv, deps = {}) => {
|
|
|
484
484
|
return finish(applied.status, applied.finalPlan);
|
|
485
485
|
};
|
|
486
486
|
|
|
487
|
-
|
|
488
|
-
if (isDirectRun) process.exit(main(process.argv.slice(2)));
|
|
487
|
+
if (isDirectRun(import.meta.url)) process.exit(main(process.argv.slice(2)));
|
|
@@ -23,7 +23,7 @@
|
|
|
23
23
|
//
|
|
24
24
|
// Dependency-free, Node >= 22. No side effects on import (the isDirectRun idiom).
|
|
25
25
|
|
|
26
|
-
import {
|
|
26
|
+
import { isDirectRun } from './direct-run.mjs';
|
|
27
27
|
import { settingValueValid } from './manifest/validate.mjs';
|
|
28
28
|
import { writeHostConfigFileAtomic } from './atomic-write.mjs';
|
|
29
29
|
import {
|
|
@@ -294,8 +294,7 @@ export const main = (argv = [], ctx = {}) => {
|
|
|
294
294
|
}
|
|
295
295
|
};
|
|
296
296
|
|
|
297
|
-
|
|
298
|
-
if (isDirectRun) {
|
|
297
|
+
if (isDirectRun(import.meta.url)) {
|
|
299
298
|
const r = main(process.argv.slice(2));
|
|
300
299
|
if (r.stdout) console.log(r.stdout);
|
|
301
300
|
if (r.stderr) console.error(r.stderr);
|
package/tools/cheap-agents.mjs
CHANGED
|
@@ -27,7 +27,8 @@
|
|
|
27
27
|
|
|
28
28
|
import { existsSync, lstatSync, mkdirSync, readFileSync, writeFileSync, readdirSync } from 'node:fs';
|
|
29
29
|
import { join, resolve, dirname } from 'node:path';
|
|
30
|
-
import { fileURLToPath
|
|
30
|
+
import { fileURLToPath } from 'node:url';
|
|
31
|
+
import { isDirectRun } from './direct-run.mjs';
|
|
31
32
|
import { shellQuoteArg } from './repo-lex.mjs';
|
|
32
33
|
|
|
33
34
|
const HERE = dirname(fileURLToPath(import.meta.url));
|
|
@@ -251,5 +252,4 @@ export const main = (argv = process.argv.slice(2), deps = {}) => {
|
|
|
251
252
|
}
|
|
252
253
|
};
|
|
253
254
|
|
|
254
|
-
|
|
255
|
-
if (isDirectRun) process.exit(main(process.argv.slice(2)));
|
|
255
|
+
if (isDirectRun(import.meta.url)) process.exit(main(process.argv.slice(2)));
|
package/tools/commands.mjs
CHANGED
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
// the `### Mode:` headers in SKILL.md, so the catalog cannot silently drift from the documented modes.
|
|
20
20
|
// Pure, dependency-free, Node >= 22. No side effects on import (the isDirectRun idiom).
|
|
21
21
|
|
|
22
|
-
import {
|
|
22
|
+
import { isDirectRun } from './direct-run.mjs';
|
|
23
23
|
|
|
24
24
|
const SKILL_NAME = 'agent-workflow-kit';
|
|
25
25
|
const BARE_INVOCATION = `/${SKILL_NAME}`;
|
|
@@ -380,7 +380,6 @@ const main = (argv) => {
|
|
|
380
380
|
console.log(formatHelp());
|
|
381
381
|
};
|
|
382
382
|
|
|
383
|
-
|
|
384
|
-
if (isDirectRun) main(process.argv.slice(2));
|
|
383
|
+
if (isDirectRun(import.meta.url)) main(process.argv.slice(2));
|
|
385
384
|
|
|
386
385
|
export { KINDS, SKILL_NAME, BARE_INVOCATION };
|
package/tools/commit-guard.mjs
CHANGED
|
@@ -42,9 +42,10 @@
|
|
|
42
42
|
|
|
43
43
|
import { readFileSync, lstatSync } from 'node:fs';
|
|
44
44
|
import { resolve } from 'node:path';
|
|
45
|
-
import {
|
|
45
|
+
import { fileURLToPath } from 'node:url';
|
|
46
46
|
import { spawnSync } from 'node:child_process';
|
|
47
47
|
import { createHash } from 'node:crypto';
|
|
48
|
+
import { isDirectRun } from './direct-run.mjs';
|
|
48
49
|
import { computeTreeFingerprint, buildState, decideCheck, quoteReportName, shellQuoteArg } from './review-state.mjs';
|
|
49
50
|
import {
|
|
50
51
|
resolveEvidencePath, readEvidence, authoritativeOfKind, canonicalKindSerialization,
|
|
@@ -381,8 +382,7 @@ export const main = (argv, ctx = {}) => {
|
|
|
381
382
|
}
|
|
382
383
|
};
|
|
383
384
|
|
|
384
|
-
|
|
385
|
-
if (isDirectRun) {
|
|
385
|
+
if (isDirectRun(import.meta.url)) {
|
|
386
386
|
const r = main(process.argv.slice(2));
|
|
387
387
|
if (r.stdout) process.stdout.write(r.stdout.endsWith('\n') ? r.stdout : `${r.stdout}\n`);
|
|
388
388
|
if (r.stderr) process.stderr.write(r.stderr.endsWith('\n') ? r.stderr : `${r.stderr}\n`);
|
package/tools/core-evidence.mjs
CHANGED
|
@@ -36,11 +36,11 @@
|
|
|
36
36
|
|
|
37
37
|
import { readFileSync, lstatSync, realpathSync, readlinkSync, openSync, readSync, closeSync } from 'node:fs';
|
|
38
38
|
import { join, dirname, normalize, sep, basename } from 'node:path';
|
|
39
|
-
import { pathToFileURL } from 'node:url';
|
|
40
39
|
import { spawnSync } from 'node:child_process';
|
|
41
40
|
import { createHash } from 'node:crypto';
|
|
42
41
|
import { writeContainedFileAtomic } from './atomic-write.mjs';
|
|
43
42
|
import { parsePositiveIntKnob, probeVerdict } from './changed-surface.mjs';
|
|
43
|
+
import { isDirectRun } from './direct-run.mjs';
|
|
44
44
|
import { readRegularFileNoFollow } from './fs-read-nofollow.mjs';
|
|
45
45
|
import { lexicalRepoRelative } from './repo-lex.mjs';
|
|
46
46
|
// The coverage vocabulary leaf: run-gates RECORDS the token this validator checks, and run-gates
|
|
@@ -1230,8 +1230,7 @@ export const main = (argv, ctx = {}) => {
|
|
|
1230
1230
|
}
|
|
1231
1231
|
};
|
|
1232
1232
|
|
|
1233
|
-
|
|
1234
|
-
if (isDirectRun) {
|
|
1233
|
+
if (isDirectRun(import.meta.url)) {
|
|
1235
1234
|
const r = main(process.argv.slice(2));
|
|
1236
1235
|
if (r.stdout) process.stdout.write(r.stdout.endsWith('\n') ? r.stdout : `${r.stdout}\n`);
|
|
1237
1236
|
if (r.stderr) process.stderr.write(r.stderr.endsWith('\n') ? r.stderr : `${r.stderr}\n`);
|
package/tools/coverage-check.mjs
CHANGED
|
@@ -23,9 +23,9 @@
|
|
|
23
23
|
import { lstatSync, readFileSync, realpathSync } from 'node:fs';
|
|
24
24
|
import { createHash } from 'node:crypto';
|
|
25
25
|
import { join } from 'node:path';
|
|
26
|
-
import { pathToFileURL } from 'node:url';
|
|
27
26
|
import { spawnSync } from 'node:child_process';
|
|
28
27
|
import { computeChangedSurface } from './changed-surface.mjs';
|
|
28
|
+
import { isDirectRun } from './direct-run.mjs';
|
|
29
29
|
import { lcovCoveredMap, uncoveredChangedFromLcov } from './lcov.mjs';
|
|
30
30
|
import {
|
|
31
31
|
computeTreeFingerprint,
|
|
@@ -365,8 +365,7 @@ export const main = (argv, ctx = {}) => {
|
|
|
365
365
|
}
|
|
366
366
|
};
|
|
367
367
|
|
|
368
|
-
|
|
369
|
-
if (isDirectRun) {
|
|
368
|
+
if (isDirectRun(import.meta.url)) {
|
|
370
369
|
// The capability is CONSUMED here: snapshot it, then remove it from this process's environment
|
|
371
370
|
// before anything spawns. Every `git` query and every bound-test probe below inherits
|
|
372
371
|
// process.env, so leaving it in place would hand a live attestation context to each of them —
|
package/tools/delegation.mjs
CHANGED
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
|
|
11
11
|
import { statSync } from 'node:fs';
|
|
12
12
|
import { join, resolve } from 'node:path';
|
|
13
|
-
import {
|
|
13
|
+
import { isDirectRun } from './direct-run.mjs';
|
|
14
14
|
import { validateManifest, VALID } from './manifest/validate.mjs';
|
|
15
15
|
|
|
16
16
|
// The exact skill name a delegable memory candidate must declare (guards against a wrong-name
|
|
@@ -120,5 +120,4 @@ const main = (argv) => {
|
|
|
120
120
|
console.log(`[delegation] commit gate: ${plan.commitGate} (memory raises its own gate: ${plan.memoryRaisesCommitGate})`);
|
|
121
121
|
};
|
|
122
122
|
|
|
123
|
-
|
|
124
|
-
if (isDirectRun) main(process.argv.slice(2));
|
|
123
|
+
if (isDirectRun(import.meta.url)) main(process.argv.slice(2));
|
|
@@ -21,8 +21,8 @@
|
|
|
21
21
|
|
|
22
22
|
import { existsSync, statSync, accessSync, realpathSync, constants } from 'node:fs';
|
|
23
23
|
import { join } from 'node:path';
|
|
24
|
-
import { pathToFileURL } from 'node:url';
|
|
25
24
|
import os from 'node:os';
|
|
25
|
+
import { isDirectRun } from './direct-run.mjs';
|
|
26
26
|
import { validateManifest, UNSUPPORTED, INVALID } from './manifest/validate.mjs';
|
|
27
27
|
|
|
28
28
|
// Probe states. `unknown` (a wrapped fs error) NEVER counts as present in any readiness rule.
|
|
@@ -439,5 +439,4 @@ const main = (_argv, deps = {}) => {
|
|
|
439
439
|
process.exit(0); // informational, like validate.mjs non-strict — never blocks anything
|
|
440
440
|
};
|
|
441
441
|
|
|
442
|
-
|
|
443
|
-
if (isDirectRun) main(process.argv.slice(2));
|
|
442
|
+
if (isDirectRun(import.meta.url)) main(process.argv.slice(2));
|
package/tools/doc-parity.mjs
CHANGED
|
@@ -19,7 +19,6 @@
|
|
|
19
19
|
// Read-only: never writes, never commits, never runs a subscription CLI, spawns nothing. Dependency-
|
|
20
20
|
// free, Node >= 22. No side effects on import (the isDirectRun idiom).
|
|
21
21
|
|
|
22
|
-
import { pathToFileURL } from 'node:url';
|
|
23
22
|
import { EXIT as DOCTOR_EXIT, STATUS as DOCTOR_STATUS, TRUSTED_DIRS as DOCTOR_TRUSTED_DIRS } from './autonomy-doctor.mjs';
|
|
24
23
|
import {
|
|
25
24
|
RECOMMENDATIONS_SECTION_HEADER,
|
|
@@ -30,6 +29,7 @@ import {
|
|
|
30
29
|
VERDICT_SKIPS_TEMPLATE,
|
|
31
30
|
ACKS_FILE,
|
|
32
31
|
} from './recommendations.mjs';
|
|
32
|
+
import { isDirectRun } from './direct-run.mjs';
|
|
33
33
|
import { SKIPPED_READONLY } from './setup-backends.mjs';
|
|
34
34
|
// The parity verdicts that read-only skip may report — a CLOSED set the same two mode docs enumerate.
|
|
35
35
|
import { PARITY } from './refresh-parity.mjs';
|
|
@@ -338,8 +338,7 @@ export const main = (argv, ctx = {}) => {
|
|
|
338
338
|
}
|
|
339
339
|
};
|
|
340
340
|
|
|
341
|
-
|
|
342
|
-
if (isDirectRun) {
|
|
341
|
+
if (isDirectRun(import.meta.url)) {
|
|
343
342
|
const r = main(process.argv.slice(2));
|
|
344
343
|
if (r.stdout) process.stdout.write(r.stdout.endsWith('\n') ? r.stdout : `${r.stdout}\n`);
|
|
345
344
|
if (r.stderr) process.stderr.write(r.stderr.endsWith('\n') ? r.stderr : `${r.stderr}\n`);
|
|
@@ -18,9 +18,10 @@
|
|
|
18
18
|
|
|
19
19
|
import { existsSync, statSync, readFileSync, lstatSync } from 'node:fs';
|
|
20
20
|
import { join, resolve, dirname } from 'node:path';
|
|
21
|
-
import {
|
|
21
|
+
import { fileURLToPath } from 'node:url';
|
|
22
22
|
import os from 'node:os';
|
|
23
23
|
import { resolveDir, detectBackends, findOnPath } from './detect-backends.mjs';
|
|
24
|
+
import { isDirectRun } from './direct-run.mjs';
|
|
24
25
|
// The ONE dependency-free semver (shared with bin/install.mjs) — the bridge freshness probe compares
|
|
25
26
|
// the placed version against the kit-bundled mirror; null-on-unparseable maps to 'unknown' (INV-B).
|
|
26
27
|
import { parseSemver, compareSemver } from './semver-lite.mjs';
|
|
@@ -773,5 +774,4 @@ const main = (argv) => {
|
|
|
773
774
|
console.log(args.surface.mode === 'json' ? JSON.stringify(envelope, null, 2) : render(toViewModel(envelope), args.surface));
|
|
774
775
|
};
|
|
775
776
|
|
|
776
|
-
|
|
777
|
-
if (isDirectRun) main(process.argv.slice(2));
|
|
777
|
+
if (isDirectRun(import.meta.url)) main(process.argv.slice(2));
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
// flow-adoption-mint.mjs — the adoption mint (#58): the frontmatter planId reader and mintAdoption,
|
|
2
|
+
// which binds a plan's chain identity to {frontmatter planId, plan content digest} and lands the
|
|
3
|
+
// chain's FIRST record. Split out of flow-store.mjs unchanged (baseline-practices tranche 2); the
|
|
4
|
+
// facade re-exports both names.
|
|
5
|
+
//
|
|
6
|
+
// The plan file is READ, never written. Imports run ONE way: this leaf mints through the store's
|
|
7
|
+
// ONE append door (flow-append.mjs) and never reaches the flow-store.mjs facade or its sibling mint
|
|
8
|
+
// leaf — the one-line sha256Hex below is a deliberate copy rather than a sideways import.
|
|
9
|
+
|
|
10
|
+
import { createHash } from 'node:crypto';
|
|
11
|
+
import { readFileSync } from 'node:fs';
|
|
12
|
+
import { resolve } from 'node:path';
|
|
13
|
+
import { FLOW_SCHEMA_VERSION, CHAIN_KIND, canonicalFlowDigest } from './flow-record.mjs';
|
|
14
|
+
import { resolveBase, computeTreeFingerprint } from './core-evidence.mjs';
|
|
15
|
+
import { flowStoreStop, resolveFlowStorePath, readFlowStore, deriveFlowOwner } from './flow-store-read.mjs';
|
|
16
|
+
import { appendFlowRecord } from './flow-append.mjs';
|
|
17
|
+
|
|
18
|
+
const stop = flowStoreStop;
|
|
19
|
+
const sha256Hex = (bytes) => createHash('sha256').update(bytes).digest('hex');
|
|
20
|
+
|
|
21
|
+
const PLAN_ID_FRONTMATTER_HINT = 'planId: <your-stable-plan-id>';
|
|
22
|
+
|
|
23
|
+
// Identity binds only a CLOSED leading frontmatter block — an unterminated block never yields an
|
|
24
|
+
// id; CRLF is normalized per line so line endings never fork chain identity.
|
|
25
|
+
export const readPlanFrontmatterId = (text) => {
|
|
26
|
+
const lines = text.split('\n').map((line) => line.replace(/\r$/, ''));
|
|
27
|
+
if (lines[0]?.trim() !== '---') return null;
|
|
28
|
+
const close = lines.findIndex((line, i) => i > 0 && line.trim() === '---');
|
|
29
|
+
if (close === -1) return null;
|
|
30
|
+
for (const line of lines.slice(1, close)) {
|
|
31
|
+
const m = /^planId:[ \t]*(\S+)[ \t]*$/.exec(line);
|
|
32
|
+
if (m) return m[1];
|
|
33
|
+
}
|
|
34
|
+
return null;
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
export const mintAdoption = ({ cwd = process.cwd(), env = process.env, deps = {}, planPath, planLabel, cycle = 1, commitEpoch = 0, timestamp = new Date().toISOString() } = {}) => {
|
|
38
|
+
const owner = deriveFlowOwner(cwd);
|
|
39
|
+
if (owner == null) throw stop('not inside a git work tree — the adoption mint derives the owning worktree and the tree fingerprint from git (fail closed)');
|
|
40
|
+
let planBytes;
|
|
41
|
+
try {
|
|
42
|
+
planBytes = readFileSync(resolve(cwd, planPath));
|
|
43
|
+
} catch (err) {
|
|
44
|
+
throw stop(`cannot read the plan file ${planPath} (${(err && err.code) || (err && err.message) || err}) — the adoption mint READS an existing plan file (fail closed)`);
|
|
45
|
+
}
|
|
46
|
+
const planId = readPlanFrontmatterId(planBytes.toString('utf8'));
|
|
47
|
+
if (planId == null) {
|
|
48
|
+
throw stop(`the plan file ${planPath} carries no frontmatter planId — plan filenames are never chain identity. Add this line inside a leading "---" frontmatter block:\n${PLAN_ID_FRONTMATTER_HINT}\nand re-run; the plan file is never written by this mint (fail closed)`);
|
|
49
|
+
}
|
|
50
|
+
const planDigest = sha256Hex(planBytes);
|
|
51
|
+
// A pre-append read purely for the NAMED refusal: the locked append would refuse a second
|
|
52
|
+
// adoption anyway, but only this comparison can surface whether the plan content still matches.
|
|
53
|
+
const resolved = resolveFlowStorePath(cwd, env);
|
|
54
|
+
const adopted = resolved == null ? undefined : readFlowStore(resolved).records
|
|
55
|
+
.find((r) => r.kind === CHAIN_KIND && r.purpose === 'adoption' && r.planId === planId);
|
|
56
|
+
if (adopted !== undefined) {
|
|
57
|
+
throw stop(adopted.planDigest === planDigest
|
|
58
|
+
? `plan "${planId}" is already adopted (content digest unchanged — a rename never resets chain identity); adoption is only ever the chain's first record`
|
|
59
|
+
: `plan "${planId}" is already adopted and the plan file content no longer matches its adoption record (recorded ${adopted.planDigest.slice(0, 12)}…, current ${planDigest.slice(0, 12)}…) — re-adopting edited plan content is refused; the digest mismatch is surfaced, never silent`);
|
|
60
|
+
}
|
|
61
|
+
const fingerprint = computeTreeFingerprint(cwd);
|
|
62
|
+
if (fingerprint == null) throw stop('cannot compute the tree fingerprint — the adoption record binds {base, fingerprint} (fail closed)');
|
|
63
|
+
const record = {
|
|
64
|
+
schema: FLOW_SCHEMA_VERSION, kind: CHAIN_KIND, purpose: 'adoption', planId, cycle, round: 0,
|
|
65
|
+
commitEpoch, owner, base: resolveBase(cwd), timestamp, stepId: null, fingerprint,
|
|
66
|
+
planLabel: planLabel ?? planId, createdAt: timestamp, planDigest,
|
|
67
|
+
};
|
|
68
|
+
const { writtenPath } = appendFlowRecord({ cwd, record, env, deps });
|
|
69
|
+
return { writtenPath, record, digest: canonicalFlowDigest(record) };
|
|
70
|
+
};
|