@sabaiway/agent-workflow-kit 5.11.0 → 5.11.2
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 +111 -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/dispatch-record.mjs +1 -1
- 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-finding-manifest.mjs +70 -0
- package/tools/flow-legality.mjs +248 -0
- package/tools/flow-record-identity.mjs +115 -0
- package/tools/flow-record-shape.mjs +283 -0
- package/tools/flow-record.mjs +49 -789
- 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-vocabulary.mjs +96 -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,117 @@ 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.2 — the record vocabulary becomes a facade over five leaves (AD-103)
|
|
8
|
+
|
|
9
|
+
**Nothing you import changes, and that is the whole claim — now certified rather than asserted.**
|
|
10
|
+
`tools/flow-record.mjs` was the kit's most expensive module to read: 795 lines holding eleven of its
|
|
11
|
+
own section seams, from the closed record vocabulary through the field shapes, the identity family,
|
|
12
|
+
the two legality walks and the wrapper finding manifest. It keeps its path and its 29 export names,
|
|
13
|
+
and becomes a 55-line re-export facade over five single-responsibility leaves.
|
|
14
|
+
|
|
15
|
+
- **`tools/flow-record.mjs` keeps its path, its 29 export names and every one of its 30 import
|
|
16
|
+
sites.** The internals moved verbatim into `flow-vocabulary.mjs` (96, the lowest leaf: the closed
|
|
17
|
+
kinds, purposes, terminal lanes, seed assignment and transition table) · `flow-record-shape.mjs`
|
|
18
|
+
(283, the closed per-kind field shapes and `validateFlowRecord`) · `flow-record-identity.mjs` (115,
|
|
19
|
+
every answer to "what identifies this record or this set" — keys, tree identity, the canonical
|
|
20
|
+
digest, the owner-scoped projection) · `flow-legality.mjs` (248, the two raw-order legality walks)
|
|
21
|
+
· `flow-finding-manifest.mjs` (70). The edges run one way and the family is acyclic by
|
|
22
|
+
construction; no leaf reaches a write module, so "pure form" is a structural pin instead of a
|
|
23
|
+
header sentence.
|
|
24
|
+
- **Conservation is certified by ORDER as well as by count.** A comparator normalises both sides
|
|
25
|
+
identically and then checks, per leaf, that its line SEQUENCE equals the concatenation of its
|
|
26
|
+
declared source ranges in declared order — a reordering is caught, not just a loss — AND that the
|
|
27
|
+
multiset over the union of the five leaves equals the pre-split module. Over the 587 executable
|
|
28
|
+
lines: five sequences EQUAL, the difference EMPTY in both directions. It was then run three more
|
|
29
|
+
times — against the unsplit module alone, with one leaf omitted, and with one line duplicated — so
|
|
30
|
+
a comparator that could not report loss or excess could not pass for one that does.
|
|
31
|
+
- **The five shared grammars live in exactly one place.** `HEX64_RE`, `isHex64`, `isPlainObject`,
|
|
32
|
+
`isNonEmptyString` and `refuse` are exported off-surface from the lowest leaf and used by the
|
|
33
|
+
others. They are the record family's named grammars — `isHex64` IS the 64-hex digest grammar every
|
|
34
|
+
consumer takes by reference — and a per-leaf copy is the only way two of them could ever drift.
|
|
35
|
+
The layout suite pins that off-surface set exactly, so it cannot widen unnoticed.
|
|
36
|
+
- **The owning suite never moved.** `tools/flow-record.test.mjs` is BYTE-IDENTICAL and reproduces
|
|
37
|
+
58/58; the fifteen named suites show an empty `git diff --name-only` in the worktree and under
|
|
38
|
+
`--cached`. `test/flow-record-layout.test.mjs` (not shipped) pins the frozen 29-name surface, the
|
|
39
|
+
facade-declared owner of every name, that the facade carries no logic at all, the size caps, and
|
|
40
|
+
the one-way edges including their negative direction — four of its five cases were red-proofed on
|
|
41
|
+
the unsplit tree before the split existed.
|
|
42
|
+
- **`test/package-content.test.mjs` moves its payload pin from 227 to 232 files** and names the five
|
|
43
|
+
leaves: the facade imports all five, so a leaf falling out of the published tarball would break
|
|
44
|
+
every flow read at load, and the pin is what says so.
|
|
45
|
+
- **One consumer line changed in the whole tree, and it is a comment.** `dispatch-record.mjs:15`
|
|
46
|
+
cited the fail-closed rule by line number (`flow-record.mjs:22-24`); the split falsified that
|
|
47
|
+
reference, so it now names the rule semantically.
|
|
48
|
+
|
|
49
|
+
Recorded size effect, reason `tranche 3: flow-record split`:
|
|
50
|
+
|
|
51
|
+
```text
|
|
52
|
+
agent-workflow-kit/test/package-content.test.mjs: lines 600 → 617 (raise)
|
|
53
|
+
agent-workflow-kit/tools/flow-record.mjs: lines 795 → none
|
|
54
|
+
agent-workflow-kit: aggregate lines 125315 → 125669 (raise)
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
## 5.11.1 — the flow store becomes a facade over five leaves, and the direct-run guard fix finally reaches you (AD-102)
|
|
58
|
+
|
|
59
|
+
**A tool invoked through a symlink used to run nothing and exit 0 — and five of the affected tools are
|
|
60
|
+
declared gates.** That fix has been sitting in-repo, unpublished, since the delegation series' own
|
|
61
|
+
measurement came back FAIL and the fix was correctly held back from a release it should not have
|
|
62
|
+
justified (AD-101). It ships here, on a regular train, because it is a bug fix and every deployed
|
|
63
|
+
host still carries the broken form. Beside it, the flow store stops being one 827-line module.
|
|
64
|
+
|
|
65
|
+
- **The direct-run entry guard now decides by REAL PATH, in all 66 frozen sites.** The lexical form
|
|
66
|
+
compared `import.meta.url` against `process.argv[1]`, which is false when the entry point is a
|
|
67
|
+
symlink — so the tool did nothing, said nothing, and exited 0. Every one of the 39 kit tools now
|
|
68
|
+
decides through the shipped `isDirectRun(import.meta.url)` leaf; the seven mirrored
|
|
69
|
+
`references/scripts` and `references/hooks` payload scripts, which cannot reach that leaf from a
|
|
70
|
+
deployed project, inline the same fail-closed realpath guard. `velocity-profile.mjs` also drops a
|
|
71
|
+
vestigial exported predicate with no importer. The new `test/direct-run-sweep.test.mjs` pins list
|
|
72
|
+
completeness, in-process import of all 39, the absence of the lexical byte pattern, and a SPAWNED
|
|
73
|
+
symlink smoke — observed red 3/3 before the fix, so the silent-pass class is proven dead rather than
|
|
74
|
+
believed dead.
|
|
75
|
+
- **`tools/flow-store.mjs` keeps its path, its 29 export names and every one of its 22 import sites,
|
|
76
|
+
and becomes a 50-line re-export facade.** The flow-specific write side moved verbatim into five
|
|
77
|
+
leaves with one responsibility each and one-way edges: `flow-chain-state.mjs` (91, the pure chain
|
|
78
|
+
walk and the prior-terminal reference validator) · `flow-subset-budget.mjs` (81, the pure
|
|
79
|
+
Decision-7/8 counting-context budget) · `flow-append.mjs` (309, the store's ONE write door, keeping
|
|
80
|
+
the locked subset-attempt factory beside the lane it is the only sanctioned entrance to) ·
|
|
81
|
+
`flow-adoption-mint.mjs` (70) · `flow-delta-proof.mjs` (307, the bookkeeping-delta custody proof).
|
|
82
|
+
Nothing about the public surface moved, so nothing you import changes.
|
|
83
|
+
- **The split is proven, not asserted.** A line-multiset conservation check compares the pre-split
|
|
84
|
+
module against the union of the five leaves and expects exactly two lines to have gained a copy —
|
|
85
|
+
and it was run twice more, against the unsplit module alone and with one leaf omitted, so that a
|
|
86
|
+
comparator which cannot report loss could not pass for one that does. The three owning suites are
|
|
87
|
+
BYTE-IDENTICAL and reproduce 169/169; the ten named suites show an empty `git diff --name-only`.
|
|
88
|
+
`test/flow-store-layout.test.mjs` (not shipped) now pins the frozen surface, that every facade name
|
|
89
|
+
is the SAME binding as its leaf's export, that the facade carries no logic at all, the size caps,
|
|
90
|
+
and the one-way import direction including its negative edges.
|
|
91
|
+
- **`test/package-content.test.mjs` moves its payload pin from 222 to 227 files** and names the five
|
|
92
|
+
leaves: the facade imports all five, so a leaf falling out of the published tarball would break
|
|
93
|
+
every append at load, and the pin is what says so.
|
|
94
|
+
|
|
95
|
+
Recorded size effect, reason `tranche 2: flow-store split`:
|
|
96
|
+
|
|
97
|
+
```text
|
|
98
|
+
agent-workflow-kit/test/package-content.test.mjs: lines 583 -> 600 (raise)
|
|
99
|
+
agent-workflow-kit/tools/flow-store.mjs: lines 827 -> none
|
|
100
|
+
agent-workflow-kit: aggregate lines 124960 -> 125315 (raise)
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
Recorded size effect of the direct-run sweep that ships with it (reason: a standalone deployed script
|
|
104
|
+
cannot reach the kit's shared direct-run leaf, so it inlines the realpath guard — one lexical line
|
|
105
|
+
becomes an 11-line fail-closed IIFE; the 39 converted tool guards themselves only shrink):
|
|
106
|
+
|
|
107
|
+
```text
|
|
108
|
+
agent-workflow-kit/references/hooks/gate-approve.mjs: lines 559 -> 570 (raise)
|
|
109
|
+
agent-workflow-kit/references/hooks/state-block-guard.mjs: lines 425 -> 437 (raise)
|
|
110
|
+
agent-workflow-kit/references/scripts/archive-changelog.mjs: lines 546 -> 557 (raise)
|
|
111
|
+
agent-workflow-kit/references/scripts/archive-decisions.mjs: lines 1199 -> 1210 (raise)
|
|
112
|
+
agent-workflow-kit/references/scripts/archive-issues.mjs: lines 415 -> 426 (raise)
|
|
113
|
+
agent-workflow-kit/references/scripts/check-docs-size.mjs: lines 580 -> 591 (raise)
|
|
114
|
+
agent-workflow-kit/references/scripts/migrate-gates.mjs: lines 722 -> 733 (raise)
|
|
115
|
+
agent-workflow-kit: aggregate lines 124726 -> 124805 -> 124925 -> 124960 (raise)
|
|
116
|
+
```
|
|
117
|
+
|
|
7
118
|
## 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
119
|
|
|
9
120
|
**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.2'
|
|
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.2",
|
|
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));
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
//
|
|
13
13
|
// The record family (D3, versioned + closed): pre-registration · dispatch · return · fold ·
|
|
14
14
|
// observation · degrade. Fail-closed in BOTH directions — unknown schema, unknown kind, a missing
|
|
15
|
-
// field, a malformed field and an unknown EXTRA field are all refusals (the flow-record.mjs
|
|
15
|
+
// field, a malformed field and an unknown EXTRA field are all refusals (the flow-record.mjs header's
|
|
16
16
|
// discipline): the per-record canonical digest is the record's identity, so a stray key would fork
|
|
17
17
|
// it.
|
|
18
18
|
//
|
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));
|