@sabaiway/agent-workflow-kit 5.9.0 → 5.10.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 +48 -0
- package/SKILL.md +1 -1
- package/bridges/antigravity-cli-bridge/SKILL.md +32 -11
- package/bridges/antigravity-cli-bridge/bin/agy-envelope.mjs +160 -0
- package/bridges/antigravity-cli-bridge/bin/agy-envelope.test.mjs +235 -0
- package/bridges/antigravity-cli-bridge/bin/agy-review-honesty.test.mjs +23 -1
- package/bridges/antigravity-cli-bridge/bin/agy-review.sh +242 -38
- package/bridges/antigravity-cli-bridge/bin/agy-review.test.mjs +482 -38
- package/bridges/antigravity-cli-bridge/capability.json +3 -2
- package/bridges/antigravity-cli-bridge/references/models-and-flags.md +45 -12
- package/bridges/antigravity-cli-bridge/references/review-prompt.md +6 -3
- package/bridges/antigravity-cli-bridge/setup/README.md +18 -5
- package/bridges/codex-cli-bridge/bin/codex-review.test.mjs +1 -1
- package/capability.json +1 -1
- package/package.json +1 -1
- package/references/hooks/state-block-guard.mjs +107 -45
- package/references/modes/set-recipe.md +8 -5
- package/references/modes/state-block-guard.md +39 -31
- package/references/scripts/check-docs-size-cli.test.mjs +2 -2
- package/references/shared/report-footer.md +2 -2
- package/references/templates/agent_rules.md +1 -0
- package/tools/detect-backends.mjs +1 -0
- package/tools/ensure-configs.mjs +4 -4
- package/tools/lens-region.mjs +13 -1
- package/tools/source-size-scope.mjs +3 -1
|
@@ -19,12 +19,12 @@ this as preventing the defect is describing it wrongly.
|
|
|
19
19
|
- **The «what I need from you» slot answering "nothing".** A turn that ends is by definition idle, so
|
|
20
20
|
a resume from the reader IS required; a slot saying otherwise states something untrue. Judged over
|
|
21
21
|
the slot's **first clause only** — a slot that names a real ask and then, after a `;` or a full
|
|
22
|
-
stop, adds
|
|
23
|
-
the comma-joined version of that sentence is flagged; see the residuals. Hedged forms count:
|
|
24
|
-
|
|
25
|
-
- **Announce-and-stop.** The «what next» slot promising first-person imminent work (
|
|
26
|
-
|
|
27
|
-
|
|
22
|
+
stop, adds «nothing else is needed» is honest and passes. A COMMA does not open a new clause, so
|
|
23
|
+
the comma-joined version of that sentence is flagged; see the residuals. Hedged forms count: «not
|
|
24
|
+
required», «your presence is not required right now», «nothing right now», «n/a».
|
|
25
|
+
- **Announce-and-stop.** The «what next» slot promising first-person imminent work («I take the
|
|
26
|
+
class…», «I'll start…») while the turn ends. A promise **gated on something named** («after your
|
|
27
|
+
yes — I publish», «once CI finishes…») states a dependency instead of a false start, and passes.
|
|
28
28
|
|
|
29
29
|
**A message with no recognisable block at all is SILENT by default.** This kit does not mandate the
|
|
30
30
|
three-part closing block — it is a per-project dialogue contract — so warning whenever a block is
|
|
@@ -75,10 +75,13 @@ gets no detection at all**, silently — not a warning on every turn, which is w
|
|
|
75
75
|
condition would produce. An EMPTY delivered message is different: it is text, so a turn that ended
|
|
76
76
|
with no prose is judged as having no block, which `--require-block` will report.
|
|
77
77
|
|
|
78
|
-
**Language
|
|
79
|
-
|
|
80
|
-
dialogue
|
|
81
|
-
|
|
78
|
+
**Language — one vocabulary, English, and no list of others.** The slot labels and every phrase set
|
|
79
|
+
are English. The kit enumerates no second language: a shipped language list is a guess about somebody
|
|
80
|
+
else's dialogue, and it never stops growing. A deployment whose dialogue language is not English
|
|
81
|
+
extends the arrays **in its own placed copy** — the runtime is copied into the project and
|
|
82
|
+
self-contained, so that edit is local and survives an uninstalled kit. Until it is made, such a
|
|
83
|
+
deployment gets **no detection**, silently. That is a real limit, stated rather than hidden, and it is
|
|
84
|
+
not a configuration you can set today.
|
|
82
85
|
|
|
83
86
|
**No writer yet — and why, plainly.** Every other placed thing in this kit arrives through a
|
|
84
87
|
consent-gated writer. This one does not, deliberately:
|
|
@@ -162,28 +165,33 @@ the warning text. It is not a sandbox and not a permission control.
|
|
|
162
165
|
|
|
163
166
|
- **Detection, not prevention** — restated because it is the one thing that must not blur.
|
|
164
167
|
- **The judgement is LEXICAL, and that is a layer with limits, not a temporary weakness.** It matches
|
|
165
|
-
slot labels and phrase sets on Unicode-aware word boundaries
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
168
|
+
slot labels and phrase sets on **Unicode-aware** word boundaries — the markers are English but the
|
|
169
|
+
message is the project's dialogue language, which need not be ASCII, and JavaScript's `\b` would
|
|
170
|
+
read every non-ASCII letter as a word break and match a marker sitting inside a longer word. On the
|
|
171
|
+
English side the same class is what stops «none» matching inside «nonexistent». It cannot parse a
|
|
172
|
+
sentence, and it cannot recognise a wording it has never been told about. Two rules that tried to
|
|
173
|
+
close an edge here were **deleted rather than tightened a third time**, because each next version
|
|
174
|
+
needed a second classifier — the residuals below are what replaced them. A named specimen of the
|
|
175
|
+
cost: «confirm that nothing was missed» is a real ask and is FLAGGED, because the banned word sits
|
|
176
|
+
inside it. Phrase the ask without the word.
|
|
177
|
+
- **A comma-joined qualifier is flagged.** «one yes, nothing else is needed» warns; the same sentence
|
|
178
|
+
with a `;` or a full stop passes. The rule that tried to exempt the comma form kept letting a real
|
|
179
|
+
"nothing" through behind a harmless prefix, so it was removed and the false flag is accepted
|
|
180
|
+
instead. It costs one line and names its own fix.
|
|
176
181
|
- **A condition is bound to a promise by TOKEN ORDER inside one segment, which is an approximation.**
|
|
177
|
-
|
|
178
|
-
known misreadings follow from the approximation, both accepted: an honest TRAILING
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
first; the second is a miss this layer cannot close without
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
unusual way of saying "I am
|
|
182
|
+
«after your yes — I take the class» passes; «I take the class, and if the test fails I'll report»
|
|
183
|
+
is flagged. Two known misreadings follow from the approximation, both accepted: an honest TRAILING
|
|
184
|
+
gate («I take it when you say so») is flagged, and a gate belonging to an earlier comma-clause («if
|
|
185
|
+
the test fails I'll report, and now I start the next class») wrongly excuses the promise after it.
|
|
186
|
+
Leading with the gate avoids the first; the second is a miss this layer cannot close without
|
|
187
|
+
parsing.
|
|
188
|
+
- **The promise markers are a MIXED set, and each half misses differently.** Some are pronoun+modal
|
|
189
|
+
(«I'll», «I will», «I'm going to»), which name no verb of their own and so cannot tell starting
|
|
190
|
+
from waiting; waiting is therefore excluded by an explicit list — «I'll wait for your approval»
|
|
191
|
+
passes, because waiting is what a turn that ends actually does — and an unusual way of saying "I am
|
|
192
|
+
waiting" is flagged. The others are pronoun+verb («I take», «I start», «I begin», «I move on»),
|
|
193
|
+
which name the action but only in the exact wording listed: a synonym nobody wrote down is missed
|
|
194
|
+
entirely, and a stative use of a listed verb («I take that as settled») is a false flag.
|
|
187
195
|
- **A host that does not deliver `last_assistant_message` gets no detection**, silently. There is no
|
|
188
196
|
transcript fallback, on purpose — see above.
|
|
189
197
|
- **Without `--require-block`, a turn that drops the block entirely is not detected.** That is the
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// check-docs-size-cli.test.mjs — runCli branch pins the subprocess smokes cannot reach
|
|
2
2
|
// in-process (Phase-5 coverage fill; the main spec file is parity-frozen, so these ride a
|
|
3
|
-
// colocated file): the unknown-argument refusal and the
|
|
3
|
+
// colocated file): the unknown-argument refusal and the pre-write symlink refusal on the index path.
|
|
4
4
|
import { describe, it } from 'node:test';
|
|
5
5
|
import assert from 'node:assert/strict';
|
|
6
6
|
import { mkdtempSync, mkdirSync, writeFileSync, symlinkSync, rmSync } from 'node:fs';
|
|
@@ -20,7 +20,7 @@ describe('check-docs-size runCli — refusal branches', () => {
|
|
|
20
20
|
assert.match(stderr, /Unknown argument: --bogus/);
|
|
21
21
|
});
|
|
22
22
|
|
|
23
|
-
it('--write-index
|
|
23
|
+
it('--write-index refuses a symlinked index path BEFORE writing, naming the path', async () => {
|
|
24
24
|
const root = mkdtempSync(join(tmpdir(), 'cds-cli-'));
|
|
25
25
|
try {
|
|
26
26
|
mkdirSync(join(root, 'docs', 'ai'), { recursive: true });
|
|
@@ -51,8 +51,8 @@ the project's internal `docs/ai` structure version, the stamp filename, or the i
|
|
|
51
51
|
vocabulary — that number is inert here and only confuses; it belongs to *Version disclosure* (below).
|
|
52
52
|
Frame the success itself plainly, in the **user's conversational language** (never hardcode a phrase):
|
|
53
53
|
- a **zero-diff no-op `upgrade`** (step 4) → **settings already current — no update is required**
|
|
54
|
-
(
|
|
55
|
-
|
|
54
|
+
(that is the MEANING to convey, not a literal string to embed — say it in the user's conversational
|
|
55
|
+
language, in your own words);
|
|
56
56
|
- a **fresh `bootstrap`** → its normal "deployed and ready" success, minus the number.
|
|
57
57
|
|
|
58
58
|
**Version block — the installed package versions, fed from `--json`** (the `docs/ai` structure version
|
|
@@ -71,6 +71,7 @@ Apply this as part of §2 before any user-facing summary:
|
|
|
71
71
|
- **No condescension, no filler.** Own a miss plainly and fix it in the same message.
|
|
72
72
|
- **Large artifact (≈>100 lines):** deliver a real summary or the key excerpt inline **and** link the file — never flood the reader with a 2000-line paste, never hide the answer behind a bare pointer.
|
|
73
73
|
- **Live host/session facts are tool-composed only.** Any claim about the current host or session state (prompts fired, sandbox scope, whether a bypass was needed, network reachability, approval counts) must trace to **live tool output** from **this session**; a memory/handover snapshot is **context, never report facts**, and a claim with no live signal is **omitted or explicitly marked unverified** — never asserted from recollection.
|
|
74
|
+
- **The closing state block answers three DIFFERENT questions.** Close a user-facing message with three labelled slots — *now* · *what I need from you* · *what's next*. The slot LABELS stay ENGLISH — an English label is what lets a state-block checker FIND the block and its slots at all; everything written INTO a slot is in the project's dialogue language; when that language is not English, the checker's English phrase sets do not judge those values. **Now** = the state at this instant: what is RUNNING, or what the work is stopped on. It is **never a report of finished work** — what you completed goes in the message BODY, above the block. **From you** = the real unblocker, named; a turn that is ENDING always has one. **Next** = what follows. A *now* slot that opens with what was completed buries the one fact the reader opened the message for, and the three slots collapse into one restatement.
|
|
74
75
|
|
|
75
76
|
### 2.6. Planning, review & process-fidelity invariants
|
|
76
77
|
Apply these when authoring a plan, reviewing, folding a finding, or editing code — the layer read **before any code change**. (Full canon: the project's planning / workflow-methodology + orchestration canon. This section is rendered from that canon and refreshed on upgrade; a custom edit is preserved verbatim, but flagged.)
|
|
@@ -141,6 +141,7 @@ const RAW_BACKENDS = [
|
|
|
141
141
|
],
|
|
142
142
|
receipt: "side effect — a successful review appends one JSON receipt line to <git dir>/agent-workflow-review-receipts.jsonl (AW_REVIEW_RECEIPTS overrides; plan/diff outside a git tree: warn + skip unless overridden): fingerprint = sha256 over the canonical uncommitted-state payload (staged diff + unstaged diff + untracked-not-ignored contents — the review-payload domain; never-committable untracked paths — character/block devices, FIFOs, sockets — are excluded from the domain entirely, untracked symlinks/directories ride as name-only notes) in code mode, the artifact-file sha256 in plan/diff mode; verdict recorded verbatim from the mandated '### Verdict' section (SHIP / SHIP WITH NITS / REWORK); grounded = whether a NON-EMPTY --facts payload was supplied (code mode refuses pre-spend without one — no run, no receipt — unless --ungrounded/AGY_PROBE=1; in plan/diff an empty payload records grounded:false — fail-closed, the state gate rejects it), factsHash = sha256 of the facts payload; a continuation receipt is fresh:false (informational-only — it cannot attest the folded tree); probe = whether the run relaxed the quality guards (AGY_PROBE=1), written on EVERY receipt so it self-declares — the kit's review-state gate rejects a probe-marked receipt (a probe review never attests) and equally rejects an unmarked one (silence is not a declaration); posture = the ACTUAL run posture {model} (agy has no tier), written on EVERY receipt (D5) — the gate rejects a receipt with an absent/invalid posture (a pre-D5 wrapper minted it; re-run the review), one stderr banner line states the same posture, an ATTESTING review with AGY_MODEL explicitly emptied refuses pre-spend, and a model string carrying control bytes refuses pre-spend in every mode; delivery = how the change set REACHED the model, currently emitted as 'inline' (the whole set rode one prompt — proven by construction) or 'fed' (a chunked feed whose per-part echo proof verified); REQUIRED on every agy code receipt and its ABSENCE is what stops a pre-fed-lane receipt attesting, while the gate accepts any well-formed declaration rather than a particular value; absent by construction on plan/diff/continuation receipts, which carry no change set; a run whose output carries NO recognized '### Verdict' section — empty output included — exits 4 with NO receipt (D4: a FAILED review to RE-RUN, never a fatal session error); when the dispatch nonce seam is supplied — the AW_REVIEW_NONCE environment value or its plain-argument equivalent --nonce <n> (one seam: the flag assigns the same value; supplying both with different values refuses pre-spend) — under the safe grammar [A-Za-z0-9._-]{1,64} (anything else refuses pre-spend), the wrapper first mints the finding MANIFEST {schema, backend, nonce, fingerprint, findings} beside the receipts file (agent-workflow-finding-manifest-<backend>-<nonce>.json; atomic, no-clobber — a byte-identical rewrite is an idempotent no-op, different bytes refuse loudly) ORDERED before the receipt append — a failed manifest write EXCLUDES the receipt append, so a nonce-supplied dispatch can never land a receipt without its readable manifest; a nonce-less invocation adds NO nonce field and mints NO finding manifest (the existing wrapperVersion field still changes with each bridge release); a write failure warns, never fails the review",
|
|
143
143
|
notes: [
|
|
144
|
+
'transport: every review dispatch drives the CLI in --output-format json (plus --disable-slash-commands) and the returned envelope is parsed in node (bin/agy-envelope.mjs) — the operator-facing invocations and flags above do NOT change, and on a ZERO exit the wrapper still PRINTS the review text, never JSON. A missing or unreadable envelope on a zero exit is a loud failure with NO receipt, never a downgraded verdict and never a fallback to raw-stdout parsing; a non-zero CLI exit keeps its own code and message, and publishes the captured stdout unchanged from the SINGLE dispatch or the FINAL fed turn (which may therefore be a JSON or partial payload — the envelope is parsed only on a zero exit); an INTERMEDIATE feed turn is the exception, its output stays private (Invariant E) and its failure prints only a named error. Enforced by a PRE-SPEND capability probe, not a version floor: agy --help must advertise --output-format and --disable-slash-commands, node must be >= 22, and bin/agy-envelope.mjs must be present — otherwise the review refuses before any run is spent and names the missing capability',
|
|
144
145
|
'pre-dispatch host-diff: before the FIRST dispatch of this bridge, diff its declared networkHosts against the live sandbox allow-list — a missing host is surfaced to the maintainer BEFORE dispatching, never fired into a known prompt',
|
|
145
146
|
'the review posture banner appends a banner-only timeout=<duration> field — exactly the duration agy-run hands to timeout(1); the hard-timeout preflight fails CLOSED when no timeout/gtimeout binary exists (the wrapper refuses by name before any CLI run, so an uncapped review run can no longer happen), and the field never enters the receipt posture or the D5 banner↔receipt parity',
|
|
146
147
|
'quote the posture banner verbatim when labeling this dispatch — the banner is the machine-stated posture; a prose re-type drifts',
|
package/tools/ensure-configs.mjs
CHANGED
|
@@ -8,8 +8,8 @@
|
|
|
8
8
|
// index docs/ai/index.md regenerate-if-missing-or-stale (a GENERATED artifact)
|
|
9
9
|
//
|
|
10
10
|
// Each was prose in references/modes/upgrade.md that an agent performed by hand. One command instead
|
|
11
|
-
// of
|
|
12
|
-
// has a single invocation point whose
|
|
11
|
+
// of five is deliberate: five independent runs would be five chances to skip one, and the mode doc now
|
|
12
|
+
// has a single invocation point whose five outcome lines it relays.
|
|
13
13
|
//
|
|
14
14
|
// The contract (pinned by this module's tests):
|
|
15
15
|
// • --reconcile is REQUIRED. A bare run is a usage error, so nothing writes by accident.
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
// • The ops run in a FIXED order and one op's failure NEVER skips the rest: every op reports its own
|
|
18
18
|
// token, and the exit is non-zero when any of them failed.
|
|
19
19
|
// • The deployment gate runs ONCE, before any op: an absent/symlinked docs/ai stops the whole run
|
|
20
|
-
// with the gate's own message rather than
|
|
20
|
+
// with the gate's own message rather than five copies of it.
|
|
21
21
|
//
|
|
22
22
|
// Output is ENGLISH/structured (repo-artifact Hard Constraint); the agent localizes when narrating.
|
|
23
23
|
// Exit codes: 0 every op fine · 1 an op failed, or the deployment gate stopped the run · 2 usage.
|
|
@@ -138,7 +138,7 @@ export const main = (argv = [], ctx = {}) => {
|
|
|
138
138
|
const cwd = resolve(args.cwd ?? ctx.cwd ?? process.cwd());
|
|
139
139
|
const deps = ctx.deps ?? {};
|
|
140
140
|
// ONE deployment gate for the whole run (see the header): with no docs/ai there is nothing to
|
|
141
|
-
// reconcile, and
|
|
141
|
+
// reconcile, and five identical STOPs would read as five separate problems.
|
|
142
142
|
assertDocsAiDeployment(cwd, deps, { noun: 'the project configuration', rel: 'under docs/ai' });
|
|
143
143
|
const outcomes = runEnsures({ cwd, kitRoot: ctx.kitRoot ?? KIT_ROOT, dryRun: args.dryRun, deps, only: args.only });
|
|
144
144
|
return {
|
package/tools/lens-region.mjs
CHANGED
|
@@ -49,7 +49,19 @@ Apply this as part of §2 before any user-facing summary:
|
|
|
49
49
|
- **Large artifact (≈>100 lines):** deliver a real summary or the key excerpt inline **and** link the file — never flood the reader with a 2000-line paste, never hide the answer behind a bare pointer.`;
|
|
50
50
|
const COMMS_PRIOR_AD054 = `${COMMS_PRIOR_PRE_AD054}
|
|
51
51
|
- **Live host/session facts are tool-composed only.** Any claim about the current host or session state (prompts fired, sandbox scope, whether a bypass was needed, network reachability, approval counts) must trace to **live tool output** from **this session**; a memory/handover snapshot is **context, never report facts**, and a claim with no live signal is **omitted or explicitly marked unverified** — never asserted from recollection.`;
|
|
52
|
-
|
|
52
|
+
// The canon that shipped between the plain-language bullet and the closing-state-block contract.
|
|
53
|
+
// Written out in full rather than composed from the constant above: the plain-language bullet LEADS
|
|
54
|
+
// the block, so an append-based composition would produce a body that never shipped and would then
|
|
55
|
+
// match no deployed file at all.
|
|
56
|
+
const COMMS_PRIOR_PLAIN_LANGUAGE = `### 2.x. Communication (user-facing messages)
|
|
57
|
+
Apply this as part of §2 before any user-facing summary:
|
|
58
|
+
- **Plain language.** User-facing narration is short, clear, plain words of the dialogue language; when the dialogue language is not English, transliterated English jargon is banned — an English term survives only as the NAME of a thing (a flag / command / file / test), glossed in plain words when helpful; plain English stays plain for English-dialogue users.
|
|
59
|
+
- **Deliver the artifact IN the message** — paste the prompt / diff / version / command inline; never "see §X / open the file / run it and you'll see" as a *substitute* for showing what was asked.
|
|
60
|
+
- **Lead with the result**, then the details; show exactly what was asked — no deflection, no "almost done" when the ask was the finished thing.
|
|
61
|
+
- **No condescension, no filler.** Own a miss plainly and fix it in the same message.
|
|
62
|
+
- **Large artifact (≈>100 lines):** deliver a real summary or the key excerpt inline **and** link the file — never flood the reader with a 2000-line paste, never hide the answer behind a bare pointer.
|
|
63
|
+
- **Live host/session facts are tool-composed only.** Any claim about the current host or session state (prompts fired, sandbox scope, whether a bypass was needed, network reachability, approval counts) must trace to **live tool output** from **this session**; a memory/handover snapshot is **context, never report facts**, and a claim with no live signal is **omitted or explicitly marked unverified** — never asserted from recollection.`;
|
|
64
|
+
export const COMMS_PRIORS = [COMMS_PRIOR_PRE_AD054, COMMS_PRIOR_AD054, COMMS_PRIOR_PLAIN_LANGUAGE];
|
|
53
65
|
|
|
54
66
|
const stripCr = (line) => (line.endsWith('\r') ? line.slice(0, -1) : line);
|
|
55
67
|
const isBoundary = (bareLine) => bareLine === '---' || /^#{2,3} /.test(bareLine);
|
|
@@ -31,6 +31,8 @@ const GITLINK_MODE = '160000';
|
|
|
31
31
|
// Raw bytes in, raw bytes out: `ls-files -s -z` emits mode, object and stage, then a TAB, then the
|
|
32
32
|
// path, then a NUL — and the path half is never decoded before it has been matched, so a name
|
|
33
33
|
// carrying a tab or a newline survives intact (splitting on lines would mangle it).
|
|
34
|
+
// The object id is carried through rather than dropped: the record already holds it, and a consumer
|
|
35
|
+
// that must read what the index HOLDS (not what the worktree happens to show) needs exactly that.
|
|
34
36
|
const parseIndexEntries = (buf) => {
|
|
35
37
|
const entries = [];
|
|
36
38
|
let start = 0;
|
|
@@ -41,7 +43,7 @@ const parseIndexEntries = (buf) => {
|
|
|
41
43
|
const tab = record.indexOf(TAB);
|
|
42
44
|
if (tab !== -1) {
|
|
43
45
|
const head = record.subarray(0, tab).toString('utf8').split(' ');
|
|
44
|
-
entries.push({ mode: head[0], stage: Number(head[2]), path: record.subarray(tab + 1) });
|
|
46
|
+
entries.push({ mode: head[0], sha: head[1], stage: Number(head[2]), path: record.subarray(tab + 1) });
|
|
45
47
|
}
|
|
46
48
|
start = end + 1;
|
|
47
49
|
}
|