@sabaiway/agent-workflow-kit 10.1.0 → 10.3.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 +58 -0
- package/README.md +2 -2
- package/SKILL.md +1 -1
- package/bridges/antigravity-cli-bridge/SKILL.md +4 -2
- package/bridges/antigravity-cli-bridge/bin/agy-review-harness.test.mjs +288 -0
- package/bridges/antigravity-cli-bridge/bin/agy-review-verdict.test.mjs +109 -0
- package/bridges/antigravity-cli-bridge/bin/agy-review.sh +19 -1
- package/bridges/antigravity-cli-bridge/bin/agy-review.test.mjs +5 -336
- package/bridges/antigravity-cli-bridge/capability.json +1 -1
- package/capability.json +1 -1
- package/package.json +1 -1
- package/references/modes/recommendations.md +1 -0
- package/references/modes/status.md +1 -1
- package/references/modes/upgrade.md +6 -4
- package/references/shared/deploy-tail.md +1 -1
- package/references/templates/agent_rules.md +3 -2
- package/tools/ack-store.mjs +57 -0
- package/tools/ack-write.mjs +1 -1
- package/tools/doc-parity.mjs +8 -0
- package/tools/ensure-ops.mjs +18 -9
- package/tools/ensure-specs.mjs +3 -4
- package/tools/ensure-vocabulary.mjs +5 -2
- package/tools/family-registry.mjs +32 -3
- package/tools/lens-region.mjs +4 -1
- package/tools/node-evidence.mjs +77 -0
- package/tools/recommendations.mjs +68 -67
- package/tools/renderers.mjs +9 -0
- package/tools/spec-adoption.mjs +71 -0
- package/tools/spec-check.mjs +2 -2
- package/tools/upgrade-runlist.mjs +1 -1
- package/tools/view-model.mjs +2 -0
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
// spec-adoption.mjs — which of the four adoption states the feature-spec store is in, and whether the layer
|
|
2
|
+
// was declined. Contract: docs/ai/specs/kit/spec-adoption.md. The census and the per-document read are
|
|
3
|
+
// spec-check's own exported seam; this module walks nothing of its own. Read-only, Node >= 22.
|
|
4
|
+
|
|
5
|
+
import { join } from 'node:path';
|
|
6
|
+
import { SPEC_SCHEMA } from '../references/scripts/spec-schema.mjs';
|
|
7
|
+
import { readRegularFileNoFollow } from './fs-read-nofollow.mjs';
|
|
8
|
+
import { probe as probePath, realpath as realpathOf, list as listDir } from './spec-check-cli.mjs';
|
|
9
|
+
import { walkStore, readClosure } from './spec-check.mjs';
|
|
10
|
+
import { ACKS_SPEC_ADOPTION_KEY, factFingerprint, readAckValue } from './ack-store.mjs';
|
|
11
|
+
|
|
12
|
+
export const ADOPTION = Object.freeze({
|
|
13
|
+
NOT_ADOPTED: 'not-adopted',
|
|
14
|
+
ADOPTING: 'adopting',
|
|
15
|
+
ADOPTED: 'adopted',
|
|
16
|
+
UNREADABLE: 'unreadable',
|
|
17
|
+
});
|
|
18
|
+
export const ADOPTION_STATES = Object.freeze(Object.values(ADOPTION));
|
|
19
|
+
|
|
20
|
+
export const STORE_DIR_REL = SPEC_SCHEMA.storePrefix.slice(0, -1);
|
|
21
|
+
export const SPEC_ADOPTION_LANE = 'spec-adoption';
|
|
22
|
+
export const DECLINE_FACT = `spec-adoption:declined:${SPEC_SCHEMA.storePrefix}`;
|
|
23
|
+
|
|
24
|
+
const CONTRACT_KIND = 'spec';
|
|
25
|
+
const LIVE = 'live';
|
|
26
|
+
const DRAFT = 'draft';
|
|
27
|
+
const RETIRED = 'retired';
|
|
28
|
+
|
|
29
|
+
const DEFAULT_IO = Object.freeze({ read: readRegularFileNoFollow, probe: probePath, realpath: realpathOf, list: listDir });
|
|
30
|
+
|
|
31
|
+
const verdict = (state, counts = { live: 0, draft: 0, retired: 0 }, reason = null) => Object.freeze({ state, ...counts, reason });
|
|
32
|
+
|
|
33
|
+
// surveySpecAdoption(root, deps) -> { state, live, draft, retired, reason }. `deps.io` overrides the four
|
|
34
|
+
// IO primitives (tests); every other answer comes from the store bytes through the one reader.
|
|
35
|
+
export const surveySpecAdoption = (root, deps = {}) => {
|
|
36
|
+
const io = { ...DEFAULT_IO, ...(deps.io ?? {}) };
|
|
37
|
+
const dirState = io.probe(join(root, STORE_DIR_REL));
|
|
38
|
+
if (dirState === 'absent') return verdict(ADOPTION.NOT_ADOPTED);
|
|
39
|
+
if (dirState !== 'dir') return verdict(ADOPTION.UNREADABLE, undefined, `${STORE_DIR_REL} is ${dirState === 'file' ? 'a file' : dirState}, not a directory`);
|
|
40
|
+
const rootReal = io.realpath(root);
|
|
41
|
+
if (rootReal === null) return verdict(ADOPTION.UNREADABLE, undefined, 'the project root does not resolve');
|
|
42
|
+
const findings = [];
|
|
43
|
+
const ctx = { io, at: (rel) => (rel === '' ? root : `${root}/${rel}`), rootReal, add: (rule, path, message) => findings.push({ rule, path, message }) };
|
|
44
|
+
const closure = walkStore(ctx).map((path) => ({ path, roles: ['present'] }));
|
|
45
|
+
const docs = findings.length === 0 ? readClosure(closure, ctx) : new Map();
|
|
46
|
+
if (findings.length > 0) return verdict(ADOPTION.UNREADABLE, undefined, `${findings[0].path}: ${findings[0].message}`);
|
|
47
|
+
const counts = { live: 0, draft: 0, retired: 0 };
|
|
48
|
+
for (const doc of docs.values()) {
|
|
49
|
+
if (doc.verdict?.kind !== CONTRACT_KIND) continue;
|
|
50
|
+
if (doc.verdict.status === LIVE) counts.live += 1;
|
|
51
|
+
else if (doc.verdict.status === DRAFT) counts.draft += 1;
|
|
52
|
+
else if (doc.verdict.status === RETIRED) counts.retired += 1;
|
|
53
|
+
}
|
|
54
|
+
return verdict(counts.live > 0 ? ADOPTION.ADOPTED : ADOPTION.ADOPTING, counts);
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
export const declineFingerprint = () => factFingerprint(DECLINE_FACT);
|
|
58
|
+
|
|
59
|
+
// True when the store's decline is recorded; the guarded reader's refusals propagate to the caller.
|
|
60
|
+
export const readDeclineAck = (root, deps = {}) => readAckValue(root, deps, ACKS_SPEC_ADOPTION_KEY) === declineFingerprint();
|
|
61
|
+
|
|
62
|
+
const plural = (n, noun) => `${n} ${noun}`;
|
|
63
|
+
|
|
64
|
+
// The one status line body, per state (the caller prefixes its own label).
|
|
65
|
+
export const describeAdoption = ({ state, live, draft, reason }, { declined = false } = {}) => {
|
|
66
|
+
const suffix = declined && state !== ADOPTION.ADOPTED ? ' — declined' : '';
|
|
67
|
+
if (state === ADOPTION.NOT_ADOPTED) return `not adopted${suffix}`;
|
|
68
|
+
if (state === ADOPTION.ADOPTING) return `adopting (${plural(draft, 'draft')})${suffix}`;
|
|
69
|
+
if (state === ADOPTION.ADOPTED) return `adopted (${plural(live, 'live')}, ${plural(draft, 'draft')})`;
|
|
70
|
+
return `could not be read — ${reason}`;
|
|
71
|
+
};
|
package/tools/spec-check.mjs
CHANGED
|
@@ -87,7 +87,7 @@ const refusal = (message) => ({ verdict: 'REFUSE', exit: 2, findings: [], docume
|
|
|
87
87
|
// Every document of the closure, read ONCE: probe, then (only for a regular file) the descriptor-
|
|
88
88
|
// bound read and the reader verdict. Containment of the containing directory is decided BEFORE the
|
|
89
89
|
// read, so a directory that resolves outside the root is never opened through.
|
|
90
|
-
const readClosure = (closure, ctx) => {
|
|
90
|
+
export const readClosure = (closure, ctx) => {
|
|
91
91
|
const { io, at, rootReal, add } = ctx;
|
|
92
92
|
const docs = new Map();
|
|
93
93
|
for (const { path, roles } of closure) {
|
|
@@ -247,7 +247,7 @@ const judgeListing = (doc, docs, add) => {
|
|
|
247
247
|
// FINDING, never an empty directory quietly walked past: an incomplete census that reported a clean
|
|
248
248
|
// store would be the one answer this lane must never give. A directory is contained BEFORE it is
|
|
249
249
|
// listed, and a non-regular `.md` sitting in the store is stated rather than skipped.
|
|
250
|
-
const walkStore = (ctx) => {
|
|
250
|
+
export const walkStore = (ctx) => {
|
|
251
251
|
const { io, at, rootReal, add } = ctx;
|
|
252
252
|
const found = [];
|
|
253
253
|
const stack = [STORE_DIR];
|
package/tools/view-model.mjs
CHANGED
|
@@ -114,6 +114,8 @@ const projectVm = (p) =>
|
|
|
114
114
|
deployed: p.deployed,
|
|
115
115
|
docsAi: p.docsAi,
|
|
116
116
|
adrLayout: p.adrLayout ?? null,
|
|
117
|
+
// null = an envelope predating the field (unknown), never a state.
|
|
118
|
+
specs: p.specs ?? null,
|
|
117
119
|
deployStamps: (p.deployStamps ?? []).map((st) => ({ display: st.display, version: st.version ?? null })),
|
|
118
120
|
visibility: visibilityVm(p.visibility),
|
|
119
121
|
settings: settingsVm(p.settings),
|