@sabaiway/agent-workflow-kit 1.45.1 → 1.47.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 +82 -0
- package/README.md +2 -2
- package/SKILL.md +1 -1
- package/bridges/antigravity-cli-bridge/SKILL.md +1 -1
- package/bridges/antigravity-cli-bridge/bin/agy-review.sh +6 -1
- package/bridges/antigravity-cli-bridge/bin/agy-review.test.mjs +6 -0
- package/bridges/antigravity-cli-bridge/capability.json +6 -2
- package/bridges/codex-cli-bridge/SKILL.md +1 -1
- package/bridges/codex-cli-bridge/bin/codex-exec.sh +20 -0
- package/bridges/codex-cli-bridge/bin/codex-exec.test.mjs +50 -0
- package/bridges/codex-cli-bridge/bin/codex-review.sh +1 -1
- package/bridges/codex-cli-bridge/capability.json +6 -2
- package/capability.json +1 -1
- package/package.json +1 -1
- package/references/modes/recommendations.md +25 -5
- package/references/modes/review-ledger.md +2 -1
- package/references/modes/upgrade.md +3 -2
- package/references/modes/velocity.md +1 -1
- package/references/shared/report-footer.md +14 -0
- package/references/templates/agent_rules.md +1 -1
- package/tools/detect-backends.mjs +6 -0
- package/tools/doc-parity.mjs +16 -4
- package/tools/manifest/schema.md +29 -2
- package/tools/manifest/validate.mjs +38 -0
- package/tools/procedures.mjs +8 -1
- package/tools/recommendations.mjs +279 -79
- package/tools/review-ledger-write.mjs +112 -6
- package/tools/review-ledger.mjs +1 -1
- package/tools/velocity-profile.mjs +12 -10
|
@@ -80,6 +80,9 @@ const RAW_BACKENDS = [
|
|
|
80
80
|
blocked: ['-c*', '--config*', '-s*', '--sandbox*', '--dangerously-bypass-approvals-and-sandbox', '--dangerously-bypass-hook-trust', '--full-auto', '--oss', '--local-provider*', '-p*', '--profile*', '-m*', '--model*', '-o*', '--output-last-message*', '--json*', '--color*', '--output-schema*', '--ephemeral*'],
|
|
81
81
|
probeRelaxed: ['--add-dir*', '-C*', '--cd*', '--skip-git-repo-check', '--ignore-rules', '--enable*', '--disable*'],
|
|
82
82
|
},
|
|
83
|
+
notes: [
|
|
84
|
+
'nested-sandbox limit: codex-exec ships its OWN OS sandbox (bwrap workspace-write) and cannot run nested inside a harness sandbox (the FS turns read-only) — route it OUTSIDE the harness sandbox (excludedCommands / a per-run consented bypass) on the OBSERVED bwrap/EPERM failure, never a preemptive blanket',
|
|
85
|
+
],
|
|
83
86
|
},
|
|
84
87
|
review: {
|
|
85
88
|
invocations: [
|
|
@@ -122,6 +125,9 @@ const RAW_BACKENDS = [
|
|
|
122
125
|
'agy-review --conversation <id> [--decided @f] [--focus "…"]',
|
|
123
126
|
],
|
|
124
127
|
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 (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); a write failure warns, never fails the review",
|
|
128
|
+
notes: [
|
|
129
|
+
'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',
|
|
130
|
+
],
|
|
125
131
|
},
|
|
126
132
|
},
|
|
127
133
|
bin: 'agy',
|
package/tools/doc-parity.mjs
CHANGED
|
@@ -27,7 +27,14 @@ import { SCHEMA_VERSION, REVIEW_CAP, V4_CLASSES, V4_OVERRIDE_SCOPES } from './re
|
|
|
27
27
|
import { HARD_MAX, DEFAULT_DIFF_CAP } from './review-ledger-write.mjs';
|
|
28
28
|
import { RESULT_SCHEMA_VERSION } from './fold-completeness.mjs';
|
|
29
29
|
import { EXIT as DOCTOR_EXIT, STATUS as DOCTOR_STATUS, TRUSTED_DIRS as DOCTOR_TRUSTED_DIRS } from './autonomy-doctor.mjs';
|
|
30
|
-
import {
|
|
30
|
+
import {
|
|
31
|
+
RECOMMENDATIONS_SECTION_HEADER,
|
|
32
|
+
RECOMMENDATIONS_EMPTY_LINE,
|
|
33
|
+
VERDICT_ATTENTION_TEMPLATE,
|
|
34
|
+
VERDICT_NOTHING_BROKEN,
|
|
35
|
+
VERDICT_OPTIONAL_TEMPLATE,
|
|
36
|
+
VERDICT_SKIPS_TEMPLATE,
|
|
37
|
+
} from './recommendations.mjs';
|
|
31
38
|
|
|
32
39
|
const KIT_ROOT = resolve(dirname(fileURLToPath(import.meta.url)), '..');
|
|
33
40
|
|
|
@@ -75,11 +82,16 @@ export const BINDINGS = Object.freeze([
|
|
|
75
82
|
.filter((token) => token !== DOCTOR_STATUS.usage)
|
|
76
83
|
.map((token) => valueBinding(`doctor-status:${token}`, token, token, [AUTONOMY_DOCTOR_DOC])),
|
|
77
84
|
valueBinding('doctor-trusted-dirs', DOCTOR_TRUSTED_DIRS.join(':'), DOCTOR_TRUSTED_DIRS.join(':'), [AUTONOMY_DOCTOR_DOC]),
|
|
78
|
-
// The upgrade Recommendations section contract (AD-044 Plan 4): the section
|
|
79
|
-
// exact empty-state line must render in BOTH the
|
|
80
|
-
// them) — a reworded doc would silently break the
|
|
85
|
+
// The upgrade Recommendations section contract (AD-044 Plan 4 + REC-UX-REWORK D1): the section
|
|
86
|
+
// header, the exact empty-state line, and the frozen verdict templates must render in BOTH the
|
|
87
|
+
// mode doc and upgrade.md (both exits reference them) — a reworded doc would silently break the
|
|
88
|
+
// presentation contract (facts/counts complete, commands byte-exact).
|
|
81
89
|
valueBinding('recommendations-header', RECOMMENDATIONS_SECTION_HEADER, RECOMMENDATIONS_SECTION_HEADER, [RECOMMENDATIONS_DOC, UPGRADE_DOC]),
|
|
82
90
|
valueBinding('recommendations-empty-line', RECOMMENDATIONS_EMPTY_LINE, RECOMMENDATIONS_EMPTY_LINE, [RECOMMENDATIONS_DOC, UPGRADE_DOC]),
|
|
91
|
+
valueBinding('verdict-attention', VERDICT_ATTENTION_TEMPLATE, VERDICT_ATTENTION_TEMPLATE, [RECOMMENDATIONS_DOC, UPGRADE_DOC]),
|
|
92
|
+
valueBinding('verdict-nothing-broken', VERDICT_NOTHING_BROKEN, VERDICT_NOTHING_BROKEN, [RECOMMENDATIONS_DOC, UPGRADE_DOC]),
|
|
93
|
+
valueBinding('verdict-optional', VERDICT_OPTIONAL_TEMPLATE, VERDICT_OPTIONAL_TEMPLATE, [RECOMMENDATIONS_DOC, UPGRADE_DOC]),
|
|
94
|
+
valueBinding('verdict-skips', VERDICT_SKIPS_TEMPLATE, VERDICT_SKIPS_TEMPLATE, [RECOMMENDATIONS_DOC, UPGRADE_DOC]),
|
|
83
95
|
].map((b) => Object.freeze(b)));
|
|
84
96
|
|
|
85
97
|
// ── the pure checker (readText is injectable for hermetic tests) ────────────────────────
|
package/tools/manifest/schema.md
CHANGED
|
@@ -19,6 +19,7 @@ expansion), never via the shell. The validator is [`validate.mjs`](./validate.mj
|
|
|
19
19
|
| `detect` | object | no | `installed` (skill on the machine) + `deployed` (substrate set up in cwd) |
|
|
20
20
|
| `settings` | array | no | the bridge's **settings-file surface** (typed; see *Settings*). Unlike `contract`, a malformed entry **fails** `--strict`. |
|
|
21
21
|
| `networkHosts` | string[] | no | the backend CLI's **observed egress host families** (see *Network hosts*). A malformed list **fails** `--strict`. |
|
|
22
|
+
| `writableDirs` | object[] | no | the backend CLI's **writable state-dir declarations** — `{env, default}` entries (see *Writable dirs*). A malformed list **fails** `--strict`. |
|
|
22
23
|
| `install` / `uninstall` | object | no | `install.npm` is a package name, not a path |
|
|
23
24
|
| `cost` / `quota` / `provenance` | misc | no | informational |
|
|
24
25
|
| `available` | boolean | no | `false` = a declared-but-not-installed stub; skips fs/version checks |
|
|
@@ -105,10 +106,36 @@ source** for a hand-applied sandbox/network allowlist (session sandbox config, o
|
|
|
105
106
|
- Observed-minimal, honestly incomplete: a blocked host names itself at run time — extend the
|
|
106
107
|
hand-applied list by hand; the manifest list records what was actually observed.
|
|
107
108
|
|
|
109
|
+
## Writable dirs (REC-UX-REWORK, D6)
|
|
110
|
+
|
|
111
|
+
`writableDirs` declares the backend CLI's **writable state dirs** (credentials, session state) as
|
|
112
|
+
`{env, default}` entries — a bare string cannot be truthful, because a wrapper resolves its state
|
|
113
|
+
dir from an env override first (e.g. `CODEX_HOME` as `~`, `~/…`, absolute or relative), so the
|
|
114
|
+
declared path is only the **default under no override**. Rules:
|
|
115
|
+
|
|
116
|
+
- Each entry: `env` is `null` or an **UPPER_SNAKE_CASE env-var name**; `default` is a
|
|
117
|
+
**`~/`-anchored or absolute POSIX path** — no globs, no trailing slash, no `..` traversal.
|
|
118
|
+
Defaults must be unique. A malformed list **fails** `--strict` (the resolved dir is rendered
|
|
119
|
+
into a hand-applied recipe line by the Recommendations advisor).
|
|
120
|
+
- **Resolution is the advisor's, at run time, mirroring the wrapper's byte-semantics:** a
|
|
121
|
+
NON-EMPTY env value wins (an EMPTY env value ≡ unset — the `${VAR:-default}` form); the
|
|
122
|
+
wrapper's exact case-arms apply — `~`, `~/…` and absolute forms ride as-given, EVERY other
|
|
123
|
+
form (including `~user/…`, which the wrappers never resolve as a home path) anchors like a
|
|
124
|
+
relative path. The advisor anchors those to the **target project root** (its pinned `--cwd`),
|
|
125
|
+
matching what a wrapper invoked from the project root resolves — the documented dispatch form
|
|
126
|
+
(the wrapper itself anchors to its invocation `$PWD`); else the `default` applies.
|
|
127
|
+
- Like `networkHosts`, this is a **documentation source**: the kit never seeds
|
|
128
|
+
`sandbox.filesystem.allowWrite` or any other filesystem allowance — the entries feed the
|
|
129
|
+
sandbox-lane discoverability item (session/host sandbox config is hand-applied territory).
|
|
130
|
+
|
|
108
131
|
## Path-field rules (Windows-safe, traversal-safe)
|
|
109
132
|
|
|
110
|
-
|
|
111
|
-
|
|
133
|
+
These rules govern the **repo/project-relative path fields** enumerated below (`source`,
|
|
134
|
+
`template`, `detect.*`) — NOT `writableDirs`, whose `default` entries are deliberately
|
|
135
|
+
`~/`-anchored or absolute **host** paths under their own section's rules above.
|
|
136
|
+
|
|
137
|
+
- **No absolute paths**, **no `..` traversal** in the fields below; **no unresolved placeholders**
|
|
138
|
+
(`{{…}}` / `${…}`) in any field.
|
|
112
139
|
- `source`, `template`, `detect.installed.file` — repo-relative **within the skill**; must exist
|
|
113
140
|
(skipped for `available:false` stubs).
|
|
114
141
|
- `detect.installed.default` — may be **home-relative** (`~/…`); resolved by the installer.
|
|
@@ -339,6 +339,44 @@ export const validateManifest = (skillDir) => {
|
|
|
339
339
|
}
|
|
340
340
|
}
|
|
341
341
|
|
|
342
|
+
// `writableDirs` (REC-UX-REWORK, D6): the backend CLI's writable state-dir declarations —
|
|
343
|
+
// {env, default} entries the Recommendations advisor RESOLVES at run time (a NON-EMPTY env
|
|
344
|
+
// value wins, else the default) and renders into the sandbox-lane recipe. Like `networkHosts`
|
|
345
|
+
// this is a DOCUMENTATION source (the kit never seeds filesystem allowances), and a malformed
|
|
346
|
+
// list FAILS --strict for the same reason: the resolved dir is rendered into a hand-applied line.
|
|
347
|
+
const writableDirs = manifest.writableDirs;
|
|
348
|
+
if (writableDirs != null) {
|
|
349
|
+
if (!Array.isArray(writableDirs) || writableDirs.length === 0) {
|
|
350
|
+
errors.push('`writableDirs` must be a non-empty array of {env, default} entries');
|
|
351
|
+
} else {
|
|
352
|
+
const seenDefaults = new Set();
|
|
353
|
+
writableDirs.forEach((entry, i) => {
|
|
354
|
+
const at = `\`writableDirs[${i}]\``;
|
|
355
|
+
if (entry == null || typeof entry !== 'object' || Array.isArray(entry)) {
|
|
356
|
+
errors.push(`${at} must be an {env, default} object`);
|
|
357
|
+
return;
|
|
358
|
+
}
|
|
359
|
+
if (entry.env !== null && (typeof entry.env !== 'string' || !/^[A-Z][A-Z0-9_]*$/.test(entry.env))) {
|
|
360
|
+
errors.push(`${at}.env must be null or an UPPER_SNAKE_CASE env-var name (${JSON.stringify(entry.env)})`);
|
|
361
|
+
}
|
|
362
|
+
const dir = entry.default;
|
|
363
|
+
if (typeof dir !== 'string' || !(dir.startsWith('~/') || dir.startsWith('/'))) {
|
|
364
|
+
errors.push(`${at}.default must be a \`~/\`-anchored or absolute POSIX path (${JSON.stringify(dir)})`);
|
|
365
|
+
return;
|
|
366
|
+
}
|
|
367
|
+
if (/[*?[\]]/.test(dir)) errors.push(`${at}.default must not carry glob characters ("${dir}")`);
|
|
368
|
+
// The resolved dir is rendered into a ONE-LINE hand-applied recipe — a control character
|
|
369
|
+
// (newline, CR, NUL, …) would break the line or the shell paste.
|
|
370
|
+
// eslint-disable-next-line no-control-regex
|
|
371
|
+
if (/[\x00-\x1f\x7f]/.test(dir)) errors.push(`${at}.default must not carry control characters (${JSON.stringify(dir)})`);
|
|
372
|
+
if (dir.endsWith('/')) errors.push(`${at}.default must not end with a trailing slash ("${dir}")`);
|
|
373
|
+
if (hasTraversal(dir)) errors.push(`${at}.default must not contain ".." traversal ("${dir}")`);
|
|
374
|
+
if (seenDefaults.has(dir)) errors.push(`duplicate writableDirs default "${dir}" (${at})`);
|
|
375
|
+
else seenDefaults.add(dir);
|
|
376
|
+
});
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
|
|
342
380
|
if (!isStub) {
|
|
343
381
|
const auth = readAuthoritativeVersion(skillDir);
|
|
344
382
|
if (auth.version == null) errors.push(`could not resolve an authoritative version (${auth.from})`);
|
package/tools/procedures.mjs
CHANGED
|
@@ -296,7 +296,10 @@ const costLanesAdvice = () => [
|
|
|
296
296
|
' • L2 subscription bridge (codex / agy) — reviews per the resolved recipe above, on frontier bridge models (quality-first).',
|
|
297
297
|
' • L3 frontier — judgment: plan/fold/synthesis, ADR/handover/changelog-entry wording, persuasive copy, go/no-go, real code.',
|
|
298
298
|
' • A step with no named guardrail does not move down a lane; red lines never move down (council review models · real code · memory/copy wording · the maintainer approval asks).',
|
|
299
|
-
' • Sandbox lanes (under an OS sandbox): the L0 surfaces are sandbox-safe — gates/ledger/state/fold checks, git reads, plain no-network tests; the bridge wrappers are genuinely unsandboxed (network); npm-cache-touching commands are COMMAND-SHAPE dependent — first try the sandbox-safe shape (cache under $TMPDIR, offline/notifier off). Move ONLY the failing command out of the sandbox, never its class; BATCH consecutive unsandboxed calls.',
|
|
299
|
+
' • Sandbox lanes (under an OS sandbox): the L0 surfaces are sandbox-safe — gates/ledger/state/fold checks, git reads, plain no-network tests; the bridge wrappers are genuinely unsandboxed (network); npm-cache-touching commands are COMMAND-SHAPE dependent — first try the sandbox-safe shape (cache under $TMPDIR, offline/notifier off). Move ONLY the failing command out of the sandbox, never its class; BATCH consecutive unsandboxed calls. Pre-dispatch host-diff: before the FIRST dispatch of each bridge, diff its manifest networkHosts against the live sandbox allow-list — a missing host is surfaced to the maintainer BEFORE dispatching, never fired into a known prompt. Nested-sandbox honesty: a backend CLI shipping its OWN OS sandbox cannot run nested inside a harness sandbox — route it outside (excludedCommands / a per-run consented bypass) on the OBSERVED failure, never a preemptive blanket.',
|
|
300
|
+
' • Prompt economy (autonomy-preserving dispatch): read-only fan-out (research/sweeps/extraction) runs ONLY on restricted-tool vehicles — a full-tool subagent for read-only work is a forbidden lane downgrade (invisible prompt-flood + blast radius), and a subagent is never told to shell out for facts obtainable read-only; the orchestrator\'s own shell form is ONE plain pipeline per call (a ;/&& chain or env-prefixed invocation never matches a prefix allow rule); a fan-out LAUNCHER that gates per call yields to the agent-spawn lane. Capability-gated: on a host with restricted-tool subagent vehicles use them; without restricted-tool vehicles (a host offering only full-tool agents included), read-only research stays in the orchestrator\'s own context — never a vehicle mandate a host cannot satisfy.',
|
|
301
|
+
' • Writer economy (autonomy-preserving dispatch): a stage\'s repeated WRITER commands batch into ONE invocation — combine a stage\'s writers via one batched write or one launcher per stage; never one writer call at a time (each write is its own prompt).',
|
|
302
|
+
' • The prompt-economy clause narrows TOOLS for read-only work only — judgment, code, synthesis stay at the frontier lane; a task that genuinely needs to run or write keeps a full-tool subagent. Honest limit: no deterministic gate classifies a dispatch — enforcement is the canon at the point of use + the placed vehicles + the retro loop.',
|
|
300
303
|
];
|
|
301
304
|
|
|
302
305
|
// The verbatim per-backend DRIVING CONTRACT block (M-contract): the exact invocation descriptor(s),
|
|
@@ -318,6 +321,10 @@ const contractLines = ({ cmd, contract, settings }) => {
|
|
|
318
321
|
if (contract.passthrough) {
|
|
319
322
|
lines.push(` passthrough after '--' is ${contract.passthrough.policy}: blocked always: ${contract.passthrough.blocked.join(' ')}; relaxed only under CODEX_PROBE=1: ${contract.passthrough.probeRelaxed.join(' ')}`);
|
|
320
323
|
}
|
|
324
|
+
// Contract notes — a TYPED renderer (AD-054): a manifest note MUST render here, else adding a
|
|
325
|
+
// notes[] key would silently disappear (the manifest validator tolerates unknown keys). Drift-
|
|
326
|
+
// guarded both directions against the manifest AND each wrapper's --help.
|
|
327
|
+
for (const note of contract.notes ?? []) lines.push(` note: ${note}`);
|
|
321
328
|
// Host-level settings knobs this wrapper honors (fact-only, from the bundled manifests; explicit
|
|
322
329
|
// branch — contractLines drops any contract key it does not name, so this must be enumerated here).
|
|
323
330
|
if ((settings ?? []).length) {
|