@sabaiway/agent-workflow-kit 5.1.0 → 5.2.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.
- package/CHANGELOG.md +55 -0
- package/SKILL.md +13 -1
- package/bridges/antigravity-cli-bridge/SKILL.md +14 -3
- package/bridges/antigravity-cli-bridge/bin/agy-review.sh +220 -30
- package/bridges/antigravity-cli-bridge/bin/agy-review.test.mjs +264 -8
- package/bridges/antigravity-cli-bridge/bin/agy.sh +12 -2
- package/bridges/antigravity-cli-bridge/bin/agy.test.mjs +18 -0
- package/bridges/antigravity-cli-bridge/capability.json +19 -13
- package/bridges/antigravity-cli-bridge/references/driving-agy.md +3 -2
- package/bridges/codex-cli-bridge/SKILL.md +8 -5
- package/bridges/codex-cli-bridge/bin/codex-exec.sh +3 -2
- package/bridges/codex-cli-bridge/bin/codex-review.sh +205 -34
- package/bridges/codex-cli-bridge/bin/codex-review.test.mjs +276 -5
- package/bridges/codex-cli-bridge/capability.json +8 -6
- package/bridges/codex-cli-bridge/references/driving-codex.md +2 -2
- package/bridges/codex-cli-bridge/references/sandbox-and-flags.md +2 -2
- package/capability.json +1 -1
- package/package.json +1 -1
- package/references/modes/flow-writer.md +37 -0
- package/references/modes/gates.md +4 -4
- package/references/modes/procedures.md +4 -2
- package/references/modes/receipt-deadline.md +16 -0
- package/references/modes/review-state.md +1 -1
- package/references/modes/set-flow.md +22 -0
- package/tools/cheap-agents.mjs +8 -2
- package/tools/commands.mjs +24 -2
- package/tools/commit-guard.mjs +44 -9
- package/tools/core-evidence.mjs +25 -22
- package/tools/detect-backends.mjs +32 -11
- package/tools/doc-parity.mjs +21 -6
- package/tools/flow-check.mjs +806 -0
- package/tools/flow-record.mjs +795 -0
- package/tools/flow-store-read.mjs +114 -0
- package/tools/flow-store.mjs +1178 -0
- package/tools/flow-writer.mjs +1265 -0
- package/tools/fs-read-nofollow.mjs +128 -0
- package/tools/gates-declaration.mjs +184 -0
- package/tools/gates-init.mjs +59 -17
- package/tools/orchestration-config.mjs +87 -10
- package/tools/orchestration-write.mjs +3 -3
- package/tools/plan-files.mjs +35 -0
- package/tools/procedures.mjs +75 -11
- package/tools/receipt-deadline.mjs +242 -0
- package/tools/recipes.mjs +21 -0
- package/tools/repo-lex.mjs +22 -0
- package/tools/review-state.mjs +240 -80
- package/tools/run-gates.mjs +361 -139
- package/tools/set-flow.mjs +465 -0
- package/tools/velocity-profile.mjs +8 -2
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
// flow-store-read.mjs — the read half of the flow store (flow-orchestration, Plan 3 Phase 3):
|
|
2
|
+
// common-dir path resolution, the fail-closed reader, the race-free no-follow file read, and the
|
|
3
|
+
// canonical owning-worktree identity. This module OWNS no write API: the append surface and the
|
|
4
|
+
// lock/CAS stay behind flow-store.mjs (which imports and RE-EXPORTS everything here, so every
|
|
5
|
+
// existing consumer keeps its import site), and the procedures advisor imports ONLY this module.
|
|
6
|
+
// The no-follow read primitive lives in the fs-read-nofollow.mjs LEAF (re-exported here under the
|
|
7
|
+
// same idiom); the transitive read graph is pinned write-module-free and acyclic by
|
|
8
|
+
// test/read-graph-purity.test.mjs (FLOW-READ-GRAPH-PURITY).
|
|
9
|
+
//
|
|
10
|
+
// No CLI, no side effects on import, no fs WRITES of any kind. Dependency-free, Node >= 22.
|
|
11
|
+
|
|
12
|
+
import { join, isAbsolute, normalize, basename, sep } from 'node:path';
|
|
13
|
+
import { spawnSync } from 'node:child_process';
|
|
14
|
+
import { validateFlowRecord, authoritativeFlowRecords } from './flow-record.mjs';
|
|
15
|
+
import { readRegularFileNoFollow } from './fs-read-nofollow.mjs';
|
|
16
|
+
|
|
17
|
+
export { lstatNoFollowRead, describeNonRegular, readRegularFileNoFollow, readFileBytesNoFollow } from './fs-read-nofollow.mjs';
|
|
18
|
+
|
|
19
|
+
export const FLOW_STORE_STOP = 'FLOW_STORE_STOP';
|
|
20
|
+
// The ONE typed-STOP factory for the store's read AND write halves (flow-store.mjs imports it).
|
|
21
|
+
export const flowStoreStop = (message) => Object.assign(new Error(`[agent-workflow-kit] ${message}`), { name: 'FlowStoreStop', code: FLOW_STORE_STOP });
|
|
22
|
+
|
|
23
|
+
export const FLOW_STORE_BASENAME = 'agent-workflow-flow.jsonl';
|
|
24
|
+
export const FLOW_LOCK_SUFFIX = '.lock';
|
|
25
|
+
|
|
26
|
+
const GIT_MAX_BUFFER = 256 * 1024 * 1024;
|
|
27
|
+
const gitBuf = (args, cwd) => {
|
|
28
|
+
const r = spawnSync('git', args, { cwd, maxBuffer: GIT_MAX_BUFFER, windowsHide: true });
|
|
29
|
+
if (r.error || r.status !== 0) return null;
|
|
30
|
+
return r.stdout;
|
|
31
|
+
};
|
|
32
|
+
export const gitLine = (args, cwd) => {
|
|
33
|
+
const buf = gitBuf(args, cwd);
|
|
34
|
+
return buf == null ? null : buf.toString('utf8').replace(/\r?\n$/, '');
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
// ── path resolution (common dir + the AW_FLOW_STORE producer seam) ────────────────────────────────
|
|
38
|
+
|
|
39
|
+
// resolveFlowStorePath(cwd, env) → the ABSOLUTE store path, or null outside a git WORK tree.
|
|
40
|
+
// is-inside-work-tree gates explicitly (--git-common-dir also succeeds in a bare repo, and the
|
|
41
|
+
// probe prints "false" WITH exit 0, so the STRING is compared). AW_FLOW_STORE must be absolute —
|
|
42
|
+
// a relative override would resolve a different store from each cwd.
|
|
43
|
+
export const resolveFlowStorePath = (cwd, env = process.env) => {
|
|
44
|
+
if (env.AW_FLOW_STORE) {
|
|
45
|
+
if (!isAbsolute(env.AW_FLOW_STORE)) {
|
|
46
|
+
throw flowStoreStop(`AW_FLOW_STORE must be an ABSOLUTE path (got "${env.AW_FLOW_STORE}") — a relative override resolves a different store from each worktree/cwd (fail closed)`);
|
|
47
|
+
}
|
|
48
|
+
const normalized = normalize(env.AW_FLOW_STORE);
|
|
49
|
+
// A trailing separator survives normalize() but not the appender's basename/join — refuse the fork.
|
|
50
|
+
if (normalized.endsWith(sep) || normalized.endsWith('/')) {
|
|
51
|
+
throw flowStoreStop(`AW_FLOW_STORE must not end with a path separator (got "${env.AW_FLOW_STORE}") — a store is a file, not a directory (fail closed)`);
|
|
52
|
+
}
|
|
53
|
+
return normalized;
|
|
54
|
+
}
|
|
55
|
+
if (gitLine(['rev-parse', '--is-inside-work-tree'], cwd) !== 'true') return null;
|
|
56
|
+
const commonDir = gitLine(['rev-parse', '--path-format=absolute', '--git-common-dir'], cwd);
|
|
57
|
+
return commonDir == null ? null : join(commonDir, FLOW_STORE_BASENAME);
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
// The lock is a SIBLING derived from the resolved store path — one store, one lock, everywhere.
|
|
61
|
+
export const resolveFlowLockPath = (storePath) => `${storePath}${FLOW_LOCK_SUFFIX}`;
|
|
62
|
+
|
|
63
|
+
// ── the canonical owning-worktree identity (#49) ──────────────────────────────────────────────────
|
|
64
|
+
|
|
65
|
+
// STABLE and git-derived, never caller-supplied and never the raw path: the main tree is "main"
|
|
66
|
+
// (a repo-root relocation keeps it); a linked worktree is "worktree:<admin-dir name>" — the
|
|
67
|
+
// <common>/worktrees/<name> admin dir survives both a path-alias invocation (git canonicalizes)
|
|
68
|
+
// and `git worktree move` (the admin dir is not renamed), so relocation cannot silently turn an
|
|
69
|
+
// own open chain into foreign advisory. Null outside a git work tree.
|
|
70
|
+
export const deriveFlowOwner = (cwd) => {
|
|
71
|
+
if (gitLine(['rev-parse', '--is-inside-work-tree'], cwd) !== 'true') return null;
|
|
72
|
+
const gitDir = gitLine(['rev-parse', '--path-format=absolute', '--git-dir'], cwd);
|
|
73
|
+
const commonDir = gitLine(['rev-parse', '--path-format=absolute', '--git-common-dir'], cwd);
|
|
74
|
+
if (gitDir == null || commonDir == null) return null;
|
|
75
|
+
return gitDir === commonDir ? 'main' : `worktree:${basename(gitDir)}`;
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
// ── the fail-closed reader ────────────────────────────────────────────────────────────────────────
|
|
79
|
+
|
|
80
|
+
// parseFlowStoreText(raw) → { records, authoritative, malformed, malformedReasons }. Both views on
|
|
81
|
+
// every result: `records` is RAW file order (the only view ordering checks may consume, #65),
|
|
82
|
+
// `authoritative` is the latest-per-key selection (#22).
|
|
83
|
+
export const parseFlowStoreText = (raw) => {
|
|
84
|
+
const records = [];
|
|
85
|
+
const malformedReasons = [];
|
|
86
|
+
const lines = String(raw).split('\n');
|
|
87
|
+
for (let i = 0; i < lines.length; i += 1) {
|
|
88
|
+
if (lines[i].trim() === '') continue;
|
|
89
|
+
let parsed;
|
|
90
|
+
try {
|
|
91
|
+
parsed = JSON.parse(lines[i]);
|
|
92
|
+
} catch {
|
|
93
|
+
malformedReasons.push(`line ${i + 1}: invalid JSON`);
|
|
94
|
+
continue;
|
|
95
|
+
}
|
|
96
|
+
const v = validateFlowRecord(parsed);
|
|
97
|
+
if (v.ok) records.push(parsed);
|
|
98
|
+
else malformedReasons.push(`line ${i + 1}: ${v.reason}`);
|
|
99
|
+
}
|
|
100
|
+
return { records, authoritative: authoritativeFlowRecords(records), malformed: malformedReasons.length, malformedReasons };
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
// readFlowStore(path, io?) → { records, authoritative, malformed, malformedReasons, readError? }.
|
|
104
|
+
// Absent → empty (no records yet is not an error); any other failure → readError, and consumers
|
|
105
|
+
// fail closed on malformed > 0 or readError. A dangling symlink must NOT read as an empty store —
|
|
106
|
+
// that would be a fail-open for the checker.
|
|
107
|
+
export const readFlowStore = (path, io = {}) => {
|
|
108
|
+
const empty = () => ({ records: [], authoritative: [], malformed: 0, malformedReasons: [] });
|
|
109
|
+
const read = readRegularFileNoFollow(path, io);
|
|
110
|
+
if (read.outcome === 'absent') return empty();
|
|
111
|
+
if (read.outcome === 'foreign') return { ...empty(), readError: `the store is a ${read.className}, not a regular file — refusing to read it (fail closed)` };
|
|
112
|
+
if (read.outcome === 'error') return { ...empty(), readError: read.code };
|
|
113
|
+
return parseFlowStoreText(read.content);
|
|
114
|
+
};
|