@holmes-lab/holmes-kit 0.12.2 → 0.13.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 +80 -0
- package/README.md +12 -3
- package/dist/.build-id +1 -1
- package/dist/holmes/cli/approve-context.js +10 -10
- package/dist/holmes/cli/approve-ref.js +5 -5
- package/dist/holmes/cli/approve-watch.d.ts +1 -1
- package/dist/holmes/cli/approve-watch.js +6 -6
- package/dist/holmes/cli/approve.d.ts +3 -3
- package/dist/holmes/cli/approve.js +57 -57
- package/dist/holmes/cli/autonomy.d.ts +22 -0
- package/dist/holmes/cli/autonomy.js +145 -0
- package/dist/holmes/cli/colophon.d.ts +6 -0
- package/dist/holmes/cli/colophon.js +24 -0
- package/dist/holmes/cli/doctor.d.ts +2 -2
- package/dist/holmes/cli/doctor.js +104 -87
- package/dist/holmes/cli/index.js +122 -63
- package/dist/holmes/cli/init.d.ts +2 -0
- package/dist/holmes/cli/init.js +31 -19
- package/dist/holmes/cli/interactive-prompt.d.ts +8 -0
- package/dist/holmes/cli/interactive-prompt.js +23 -0
- package/dist/holmes/cli/semantic-key.js +9 -9
- package/dist/holmes/cli/settings-merge.d.ts +2 -1
- package/dist/holmes/cli/settings-merge.js +15 -3
- package/dist/holmes/cli/upgrade.js +7 -7
- package/dist/holmes/cpg/proposed-content.js +2 -2
- package/dist/holmes/governance/autonomy.d.ts +9 -2
- package/dist/holmes/governance/autonomy.js +166 -5
- package/dist/holmes/guardrail/blind-spots.js +15 -15
- package/dist/holmes/hooks/pre-tool-use.js +111 -42
- package/dist/holmes/hooks/session-start.js +17 -0
- package/dist/holmes/hooks/stop.d.ts +1 -1
- package/dist/holmes/hooks/stop.js +12 -12
- package/dist/holmes/mcp/handlers.js +14 -1
- package/dist/holmes/semantic/credentials.js +1 -1
- package/dist/holmes/spec/id-collision.js +2 -2
- package/package.json +2 -2
- package/playbooks/publish/PLAYBOOK.md +47 -35
- package/playbooks/remediation/PLAYBOOK.md +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,86 @@ All notable changes to this project will be documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
<!-- @implements A-SPEC-209 -->
|
|
8
|
+
## [0.13.0] - 2026-09-06
|
|
9
|
+
|
|
10
|
+
Autonomy grows from a single env switch into a **three-layer, always-bounded** posture — so a team can
|
|
11
|
+
let the agent self-drive the SDLC without ever losing the human gate on governance-critical, high-risk,
|
|
12
|
+
or irreversible decisions — and that same risk grade now governs **releases**. Also: a uniform English
|
|
13
|
+
operator surface, and two shell-write gate bypasses closed. Additive; a project that never opts into
|
|
14
|
+
autonomy is byte-identical to before.
|
|
15
|
+
|
|
16
|
+
### Added
|
|
17
|
+
|
|
18
|
+
- **Three-layer autonomous approval** (REQ-552 / REQ-553 / REQ-554): autonomy is now a posture the agent
|
|
19
|
+
can hold at two scopes above the always-escalate boundary. A **project default** you opt into at
|
|
20
|
+
`holmes-kit init --autonomy` (an interactive prompt defaulting to No; `--autonomy`/`--no-autonomy` skip
|
|
21
|
+
it) persists as the `HOLMES_AUTONOMOUS_APPROVAL` env in `.mcp.json`. A **per-session envelope** you
|
|
22
|
+
grant with `holmes-kit autonomy on [--for 2h]` writes an **expiring marker** under the
|
|
23
|
+
agent-write-protected `.ax/state/`; `holmes-kit autonomy status`/`off` read and end it. The active
|
|
24
|
+
posture is **surfaced at every session start** (SessionStart hook) and the stale marker is pruned
|
|
25
|
+
there. An agent still cannot grant autonomy to itself: the env is env-only (blocked like
|
|
26
|
+
`HOLMES_ROLE`), the session command needs a real TTY or an out-of-band `HOLMES_APPROVAL`, and the
|
|
27
|
+
marker lives where agents can't write. `doctor` reports the project posture.
|
|
28
|
+
- **Release autonomy classifier + docs-currency gate** (REQ-555): `releaseAutonomy(specs, versionBump,
|
|
29
|
+
env, root, now)` decides a release by **what it contains** — reusing the same per-spec risk grade —
|
|
30
|
+
returning `auto` only when autonomy is on, the bump is patch/minor, and every spec is auto-grade; a
|
|
31
|
+
**major** bump, any `gate-behavior`/security/architecture spec, or an upstream `REQ`/`H-SPEC` returns
|
|
32
|
+
`hitl`. `versionBumpKind(from, to)` classifies semver major/minor/patch/none. The `holmes-publish`
|
|
33
|
+
playbook adds a **docs-currency gate** (diff the specs since the last tag; block if a user-facing
|
|
34
|
+
change never reached `README`/`CHANGELOG`) and reworks its step 3 to this classification. `npm publish`
|
|
35
|
+
remains human-approved by default — it is irreversible and outward.
|
|
36
|
+
|
|
37
|
+
- **A maker's colophon** (REQ-548): an undocumented subcommand prints a short detective-themed
|
|
38
|
+
colophon and credit. Deliberately absent from `--help` — a mark for those who go looking.
|
|
39
|
+
|
|
40
|
+
### Changed
|
|
41
|
+
|
|
42
|
+
- **Autonomy hitl boundary is now enforced through the out-of-band queue** (REQ-551): under autonomy, a
|
|
43
|
+
governance-critical spec used to be reachable through the in-session elicitation prompt (a side-door
|
|
44
|
+
that a test even blessed). It is now **refused and routed to the `holmes-kit approve` queue** instead —
|
|
45
|
+
`gate-behavior`/breaking A-SPECs, architecture/gate/taint files, and every upstream `REQ`/`H-SPEC`/
|
|
46
|
+
`C-SPEC` never self-approve, whatever the autonomy posture. This supersedes the README's earlier
|
|
47
|
+
"still ask through the in-session TUI" description.
|
|
48
|
+
- **Uniform English operator surface** (REQ-549): every operator-facing message — `doctor` output, the
|
|
49
|
+
CLI usage/errors, the hook `deny` reasons and ART citations, and the interactive `approve`/`init`/
|
|
50
|
+
`upgrade`/`semantic-key` prompts — is now English, guarded by a hangul-absence test over the
|
|
51
|
+
**rendered runtime output** (not just source scan, which misses `\u`-escaped strings). Playbook
|
|
52
|
+
triggers pinned to former Korean deny phrases were re-pinned to the new English wording.
|
|
53
|
+
|
|
54
|
+
### Fixed
|
|
55
|
+
|
|
56
|
+
- **shell-write gate: two bypasses closed** (REQ-550): the gate now treats a project as **governed when
|
|
57
|
+
any spec exists** — a fresh project holding only *unapproved drafts* was previously ungoverned, so a
|
|
58
|
+
code write slipped through the draft window — and it classifies `cp`/`mv` by their **destination**
|
|
59
|
+
(a copy/move landing on a source path is sealed even when the source file isn't code, which the old
|
|
60
|
+
"≥2 code paths" heuristic missed). Reproduced in the reported draft-only state before the fix.
|
|
61
|
+
- **Release classifier & `autonomy off` fail CLOSED on the edges** (REQ-558): a pre-release adversarial
|
|
62
|
+
audit found the release-autonomy classifier failing OPEN — `releaseAutonomy([], …)` returned `auto`
|
|
63
|
+
(an empty/unknown spec set self-publishing), a same-major **downgrade** (`1.5.0`→`1.4.0`) graded as a
|
|
64
|
+
normal `minor`, and a `none` bump reached `auto` — and `holmes-kit autonomy off` swallowing an
|
|
65
|
+
`rmSync` failure to report "OFF" while the marker (and autonomy) survived. Now: an empty spec set,
|
|
66
|
+
any non-`patch`/`minor` bump, and a downgrade are all `hitl`; and `off` re-checks the marker and
|
|
67
|
+
reports `still ON` (exit 1) if it cannot be removed. A dead `void` stub was removed; the README's
|
|
68
|
+
English-surface claim was narrowed to the CLI/hook surfaces it actually covers.
|
|
69
|
+
|
|
70
|
+
### Security
|
|
71
|
+
|
|
72
|
+
- **Autonomy grader / enforcer parity** (REQ-556): under autonomy, the spec-approval risk grader
|
|
73
|
+
(`isHighRiskPath`) judged an A-SPEC's `Files to Touch` with a literal `startsWith`, while the write
|
|
74
|
+
authorization that same field confers is decided by the glob-aware, prose-permissive enforcer
|
|
75
|
+
(`matchesFtt`). The gap let an agent **self-approve a spec that writes the gate's own code** by
|
|
76
|
+
spelling `Files to Touch` as a glob (`src/holmes/**/*.ts`), as prose (no path tokens), empty, or in a
|
|
77
|
+
different case (`src/holmes/Governance/…`) — all graded `auto`. The grader now judges by what a token
|
|
78
|
+
can **admit**: zero path tokens ⇒ `hitl` (unconstrained scope), a glob whose prefix overlaps a
|
|
79
|
+
gate/governance/taint root ⇒ high-risk, and paths are case-folded. Verified with two consecutive clean
|
|
80
|
+
adversarial rounds.
|
|
81
|
+
- **shell-write gate: directory-destination and `-t` bypasses closed** (REQ-557): `cp /tmp/evil.ts src/`
|
|
82
|
+
(a directory destination) and `cp -t src /tmp/evil.ts` (GNU `--target-directory`) landed a code file
|
|
83
|
+
inside the governed tree **unjudged**, because the REQ-550 rule only inspected the last token's
|
|
84
|
+
extension. The gate now parses `-t`/`--target-directory` as the destination and, for a directory
|
|
85
|
+
destination, judges each source's landing path `dir/basename(src)`. Read-out backups, out-of-tree and
|
|
86
|
+
non-code landings, and ungoverned repos are unaffected.
|
|
87
|
+
|
|
8
88
|
## [0.12.2] - 2026-09-05
|
|
9
89
|
|
|
10
90
|
Makes the update-notification actually work. Measured this release: the "new version available" notice
|
package/README.md
CHANGED
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
|
|
15
15
|
---
|
|
16
16
|
|
|
17
|
-
### 🛡️ Currently Supported Features (v0.
|
|
17
|
+
### 🛡️ Currently Supported Features (v0.13.x Production Features)
|
|
18
18
|
|
|
19
19
|
- 📋 **Requirements & Specification Governance**: Strict **"No Spec, No Code"** enforcement with 4-tier spec chain traceability (`REQ ➔ H-SPEC ➔ A-SPEC ➔ T-SPEC`) and `// @implements A-SPEC-XXX` code anchors (comma-lists and every anchor in a file participate in the gate).
|
|
20
20
|
- 🔴 **Inbuilt TDD — RED-first, enforced not asked** *(new in 0.9.0)*: the test-first discipline is a holmes-installed `holmes-tdd-slice` skill **and** a new constitution article **ART-8**. A changed A-SPEC must show a recorded `red-assertion → green` sequence in the ledger; a `red-error` (a test that could not run) is not a valid RED, so "the covering test failed *correctly*" is judged mechanically, not on trust. `test_run` classifies each covered file (`red-assertion`/`red-error`/`green`) and records per-A-SPEC outcomes the Stop hook reads. Ships at `redFirstEvidence: track` (observe-first, non-blocking; `strict`/`off` per repo), evidence-gated and jest-only for now. A T-SPEC may also declare `kills:` mutations and `test_run --mutate` reports which SURVIVED (a coverage gap). Where superpowers *asks* for RED-first and discriminating power, holmes-kit *proves* them.
|
|
@@ -22,9 +22,11 @@
|
|
|
22
22
|
- ⬆️ **Zero-config upgrades** *(new in 0.11.0)*: `holmes-kit upgrade` moves **every** wired workspace to the latest in one command — plan → confirm → install → re-pin all recorded workspaces (`--dry-run`/`--yes` supported). Preparation is automatic (each `init` records the workspace; a session whose pin is behind nudges you to upgrade); the re-pin **write** stays your explicit choice, never a silent auto-install. Opt out of the nudge with `HOLMES_NO_AUTO_REPIN`.
|
|
23
23
|
- 📊 **World-top-tier RTM dashboard** *(new in 0.12.0–0.12.1)*: `holmes-kit serve` — ask to *see* the RTM heatmap and the `rtm_dashboard` MCP tool launches the server idempotently and hands back the URL plus an honesty **census** (requirement/pipeline counts, coverage %, what's excluded). The heatmap is a real **2D coverage matrix** (requirements × pipeline stages, rows seriated by completeness, sequential-ramp cells with the percent printed in each). Drilling into a symbol renders that function's **CFG as a layered DAG** with **PDG (data/control-dependence) colour overlays**, served by `/api/cfg?file=&symbol=` from the same engine the taint lane uses — a non-CFG language is named, never faked. Tokenised palette (sequential ramp, status colours, UI/mono pairing) with light/dark.
|
|
24
24
|
- 🔢 **Sensible spec numbering** *(new in 0.12.1)*: a brand-new project's first slice is now **REQ-100**, not REQ-201 — `spec_slice_init` shares the same id allocator as the reverse-draft path (`nextIdBase`, floor 100). Existing projects are untouched: the next id is always `max(existing)+1`, so a repo already numbering from 201 keeps the exact same sequence. Numbering past 999 yields 4-digit ids cleanly, and ADR references now recognise 4-digit ADRs (`ADR-1000+`).
|
|
25
|
-
- 🤖 **Autonomous Approval** *(
|
|
25
|
+
- 🤖 **Autonomous Approval — three layers, always bounded** *(reworked in 0.13.0; foundation 0.8.0)*: for teams that want the agent to self-drive the SDLC, autonomy is a posture the agent holds at two scopes — a **project default** you opt into at `holmes-kit init --autonomy` (persisted as the `HOLMES_AUTONOMOUS_APPROVAL` env in `.mcp.json`), and a **per-session envelope** you grant on the spot with `holmes-kit autonomy on --for 2h` (an expiring marker under the agent-write-protected `.ax/state/`). Under either, the agent seals **low-risk** specs itself (ledgered under an `autonomous:<client>` actor); every **governance-critical, high-risk, or irreversible** decision — `gate-behavior`/breaking A-SPECs, architecture/gate/taint files, and every upstream `REQ`/`H-SPEC`/`C-SPEC` — is instead **refused and routed to the out-of-band `holmes-kit approve` queue** for a human, never silently self-approved. The active posture is **surfaced at every session start** so it can't be forgotten, and an agent can never grant it to itself: the env is env-only (blocked like `HOLMES_ROLE`), the session command needs a real TTY or an out-of-band `HOLMES_APPROVAL`, and the marker lives where agents can't write. Off = byte-identical to a fully human-gated project. *(new in 0.10.0)* `HOLMES_ELICIT=off` routes every decision straight to the same queue.
|
|
26
|
+
- 🚢 **Release Autonomy + Docs-Currency Gate** *(new in 0.13.0)*: publishing is irreversible and outward, so `npm publish` stays **human-approved by default** — but a deterministic classifier (`releaseAutonomy`, reusing the same per-spec risk grade) lets a **low-risk** release (patch/minor, every spec auto-grade, autonomy on) self-publish under the ledger, while a **major** bump, any `gate-behavior`/security/architecture spec, or an upstream `REQ`/`H-SPEC` forces HITL. The `holmes-publish` playbook also gains a **docs-currency gate**: before any release it diffs the specs since the last tag and blocks if a user-facing change never reached `README`/`CHANGELOG` — a stale doc is a false claim.
|
|
27
|
+
- 🌐 **English CLI & hook surface** *(new in 0.13.0)*: the operator-facing CLI and hook messages — `doctor` output, the CLI usage/errors, the hook `deny` reasons and ART citations, and the interactive `approve`/`init`/`upgrade`/`semantic-key` prompts — are now English, guarded by a hangul-absence test over the **rendered runtime output** (not just a source scan, which misses `\u`-escaped strings). The MCP tool responses (`spec_create`/`spec_approve`/ledger/review) are still being migrated and are next.
|
|
26
28
|
- 🪧 **Session Banner + Update Notice** *(new in 0.8.0; refresh implemented + made uniform in 0.12.2)*: every session start emits an English intro (version + governance rule + npm URL) to both the human transcript and the agent context (SessionStart hook + MCP `instructions`); when a newer published version is on npm, an install-mode-aware `holmes-kit upgrade` command is appended. The registry refresh (dist-tags query → cached in `~/.holmes/update-check.json`) is detached, TTL-gated, and fail-silent, and now fires from **every harness's MCP-server startup** — not just Claude's SessionStart hook — so Claude / Antigravity / Codex are notified alike. Opts out via `HOLMES_NO_UPDATE_CHECK`/`CI`. Upgrade execution stays your explicit choice (`holmes-kit upgrade`), never a silent auto-install.
|
|
27
|
-
- 🧱 **Deterministic Gate, Hardened** *(new in 0.8.0)*: shell writes are judged at the segment's **effective working directory** (`cd sub && cat > ../src/x.ts` is sealed, legitimate out-of-tree scratch writes are freed); the governing anchor is the **whole set**, not the first match. Every gate change ships with two consecutive clean adversarial rounds.
|
|
29
|
+
- 🧱 **Deterministic Gate, Hardened** *(new in 0.8.0; further hardened in 0.13.0)*: shell writes are judged at the segment's **effective working directory** (`cd sub && cat > ../src/x.ts` is sealed, legitimate out-of-tree scratch writes are freed); the governing anchor is the **whole set**, not the first match. *(0.13.0)* Two more bypasses are closed: the gate treats a project as **governed when any spec exists** (a fresh project holding only unapproved drafts is no longer an ungoverned free-for-all), and it classifies `cp`/`mv` by their **destination** (a copy/move landing on a source path is sealed even when the source file isn't code). Every gate change ships with two consecutive clean adversarial rounds.
|
|
28
30
|
- 🧠 **3-Tier Semantic Layer** *(new in 0.3.0)*: knowledge-graph semantic search with an explicit consent ladder — `none` (default, **zero egress**), `local` (bge-m3, no egress, optional module), `cloud` (gemini-embedding-001, opt-in via `GEMINI_API_KEY`). Measured on 305 traceability cases: recall 0.486 (lexical) → 0.667 (local) → **0.887 (cloud)**; on lexical-zero requests: 0% → 52% → **92%**. Surfaced only additively — rerank, evidence (`semCos`), and `semanticAlternates` — never as a hard filter.
|
|
29
31
|
- 🎯 **Graded Impact Surface** *(new in 0.3.0)*: `rankedImpact` (personalized-PageRank over the spec/code graph) beat its pre-registered naive baseline on **both recall and precision across 3 corpora (×1.6–×17)** — the necessary condition for any better-than-a-person phrasing, measured before claimed.
|
|
30
32
|
- 🐞 **Causal Defect Localization & CPG** *(equalized in 0.5–0.7)*: AST Code Property Graph (CFG/DDG/CDG) & Dataflow Taint reachability across 7 languages (TS/JS, Python, Go, Rust, Java, C/C++, C#) — **42 language×layer cells graded on measured evidence** (11 corpora, 39,344 functions, zero invariant violations; C++ conditional on 67.9% parse coverage, disclosed in the matrix).
|
|
@@ -118,6 +120,9 @@ npx holmes-kit init # drop the npx prefix if you installed with -g
|
|
|
118
120
|
[X] 🚀 Antigravity CLI (AGY) (.agents/mcp_config.json, hooks.json, skills)
|
|
119
121
|
[ ] 💻 Codex CLI (.codex/config.toml)
|
|
120
122
|
```
|
|
123
|
+
*It then asks — defaulting to **No** — whether to grant **autonomous spec approval** (low-risk specs
|
|
124
|
+
self-seal; governance-critical, high-risk and irreversible always stay human). Skip the prompt with
|
|
125
|
+
`--autonomy` / `--no-autonomy`, or turn it on later per-session with `holmes-kit autonomy on`.*
|
|
121
126
|
|
|
122
127
|
### 3. Verify Health
|
|
123
128
|
```bash
|
|
@@ -191,7 +196,11 @@ flowchart LR
|
|
|
191
196
|
| :--- | :--- |
|
|
192
197
|
| `holmes-kit init` | Interactive agent harness setup (Claude, Antigravity, Codex) |
|
|
193
198
|
| `holmes-kit init --agent all` | Non-interactive instant setup for all supported agents |
|
|
199
|
+
| `holmes-kit init --autonomy` | Opt the project into autonomous spec approval (low-risk only; governance-critical still HITL) |
|
|
194
200
|
| `holmes-kit init --dry-run` | Preview files and configuration changes without writing |
|
|
201
|
+
| `holmes-kit autonomy status` | Show the current autonomous-approval posture (project default + session envelope) |
|
|
202
|
+
| `holmes-kit autonomy on --for 2h` | Self-drive low-risk specs for this session only (expiring; needs a TTY or `HOLMES_APPROVAL`) |
|
|
203
|
+
| `holmes-kit autonomy off` | End session autonomy immediately |
|
|
195
204
|
| `holmes-kit doctor` | Comprehensive health check of specs, hooks, MCP, and anchors |
|
|
196
205
|
| `holmes-kit doctor --fix` | Automatically self-heal and repair broken hooks or missing skills |
|
|
197
206
|
| `holmes-kit ci` | Run non-interactive headless governance gate for GitHub Actions / GitLab CI |
|
package/dist/.build-id
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
|
|
1
|
+
55b0a37-mtp8gdtd
|
|
@@ -58,7 +58,7 @@ const MAX_BODY = 2400;
|
|
|
58
58
|
// Round-6: and folded UNDER A MARGIN, so no body byte can start a line and pose as a template row.
|
|
59
59
|
const BODY_INDENT = ' \u2502 ';
|
|
60
60
|
// Round-10: the clip ran AFTER escaping, so it counted escape-expanded units — 500 non-breaking
|
|
61
|
-
// spaces (503 source characters) reported "603
|
|
61
|
+
// spaces (503 source characters) reported "clipped at 603 chars", a statement false on its face on the screen
|
|
62
62
|
// whose job is saying how much is not shown, and it cut MAX_BODY's real budget by six. It also let
|
|
63
63
|
// the cut land INSIDE an escape, leaving a bare `00` that was the tail of `\u3000` (round-1's
|
|
64
64
|
// severed-surrogate lesson, one representation later). The source is clipped first; the notice then
|
|
@@ -108,20 +108,20 @@ function specContext(root, id) {
|
|
|
108
108
|
walk(specsDir);
|
|
109
109
|
// The absolute specs path alone can exceed a row on a real machine, so it gets one of its own.
|
|
110
110
|
if (found.length === 0) {
|
|
111
|
-
return
|
|
111
|
+
return `cannot read context: spec ${(0, screen_safe_1.rowField)(id, 24)} was not found\n ${(0, screen_safe_1.rowField)(specsDir, 76)}`;
|
|
112
112
|
}
|
|
113
113
|
if (found.length > 1) {
|
|
114
114
|
return [
|
|
115
|
-
|
|
115
|
+
`context is ambiguous: ${found.length} file(s) claim ${(0, screen_safe_1.rowField)(id, 40)}:`,
|
|
116
116
|
...found.map((f) => ` ${(0, screen_safe_1.rowField)(f.file, 72)}`).sort(),
|
|
117
|
-
'
|
|
117
|
+
'the body is not shown until which one is the approval target is settled',
|
|
118
118
|
].join('\n')
|
|
119
|
-
+ `\n (
|
|
119
|
+
+ `\n (check 'spec id preemption collision' in holmes-kit doctor)`;
|
|
120
120
|
}
|
|
121
121
|
const { spec, file } = found[0];
|
|
122
122
|
const body = [spec.preamble ?? '', ...Object.entries(spec.sections).map(([h, t]) => `## ${h}\n${t}`)]
|
|
123
123
|
.filter((s2) => s2.trim() !== '').join('\n\n');
|
|
124
|
-
return clip(`■ ${(0, screen_safe_1.flattenField)(spec.id, 40)} — ${(0, screen_safe_1.flattenField)(spec.title, 120)}\n [
|
|
124
|
+
return clip(`■ ${(0, screen_safe_1.flattenField)(spec.id, 40)} — ${(0, screen_safe_1.flattenField)(spec.title, 120)}\n [current status: ${(0, screen_safe_1.flattenField)(spec.status, 20)}] (${(0, screen_safe_1.flattenField)(file, 60)})\n\n${body}`);
|
|
125
125
|
}
|
|
126
126
|
function findingContext(root, id) {
|
|
127
127
|
const file = path.join(root, '.ax', 'ledger', 'findings.jsonl');
|
|
@@ -136,7 +136,7 @@ function findingContext(root, id) {
|
|
|
136
136
|
// were justified as well.
|
|
137
137
|
catch (e) {
|
|
138
138
|
return [
|
|
139
|
-
'
|
|
139
|
+
'cannot read context: could not read the findings ledger',
|
|
140
140
|
` ${(0, screen_safe_1.rowField)(file, 76)}`,
|
|
141
141
|
` ${(0, screen_safe_1.rowField)(String(e instanceof Error ? e.message : e).split('\n')[0], 76)}`,
|
|
142
142
|
].join('\n');
|
|
@@ -155,8 +155,8 @@ function findingContext(root, id) {
|
|
|
155
155
|
// Round-3: this was the one sibling site that skipped flattenField, and it is outside clip() —
|
|
156
156
|
// a review-resolve target that is not in the ledger repainted the whole interactive screen.
|
|
157
157
|
if (!latest)
|
|
158
|
-
return
|
|
159
|
-
return clip(`■
|
|
158
|
+
return `cannot read context: no record of finding ${(0, screen_safe_1.rowField)(id, 24)} in the ledger`;
|
|
159
|
+
return clip(`■ finding ${(0, screen_safe_1.flattenField)(id, 60)}\n [${(0, screen_safe_1.flattenField)(String(latest.severity ?? '?'), 20)} · ${(0, screen_safe_1.flattenField)(String(latest.status ?? '?'), 20)}${latest.category ? ' · ' + (0, screen_safe_1.flattenField)(String(latest.category), 40) : ''}]\n\n${(0, screen_safe_1.flattenField)(String(latest.summary ?? '(no summary)'), 2000)}`);
|
|
160
160
|
}
|
|
161
161
|
function renderDecisionContext(root, entry) {
|
|
162
162
|
try {
|
|
@@ -175,6 +175,6 @@ function renderDecisionContext(root, entry) {
|
|
|
175
175
|
}
|
|
176
176
|
catch (e) {
|
|
177
177
|
// Belt and braces: no context failure may kill the decision surface.
|
|
178
|
-
return
|
|
178
|
+
return `cannot read context:\n ${(0, screen_safe_1.rowField)(String(e instanceof Error ? e.message : e).split('\n')[0], 76)}`;
|
|
179
179
|
}
|
|
180
180
|
}
|
|
@@ -5,13 +5,13 @@ const screen_safe_1 = require("./screen-safe");
|
|
|
5
5
|
function resolveRequestRef(pending, ref) {
|
|
6
6
|
const r = ref.trim();
|
|
7
7
|
if (r === '')
|
|
8
|
-
return { ok: false, reason: '
|
|
8
|
+
return { ok: false, reason: 'the reference is empty — give a list number or an id prefix' };
|
|
9
9
|
if (pending.length === 0)
|
|
10
|
-
return { ok: false, reason: '
|
|
10
|
+
return { ok: false, reason: 'no requests awaiting approval' };
|
|
11
11
|
if (/^\d+$/.test(r)) {
|
|
12
12
|
const n = Number(r);
|
|
13
13
|
if (n < 1 || n > pending.length) {
|
|
14
|
-
return { ok: false, reason:
|
|
14
|
+
return { ok: false, reason: `number ${(0, screen_safe_1.rowField)(r, 20)} is out of the list — the valid range is 1..${pending.length}` };
|
|
15
15
|
}
|
|
16
16
|
return { ok: true, entry: pending[n - 1] };
|
|
17
17
|
}
|
|
@@ -24,7 +24,7 @@ function resolveRequestRef(pending, ref) {
|
|
|
24
24
|
if (matches.length === 1)
|
|
25
25
|
return { ok: true, entry: matches[0] };
|
|
26
26
|
if (matches.length === 0) {
|
|
27
|
-
return { ok: false, reason:
|
|
27
|
+
return { ok: false, reason: `no pending request matches reference '${(0, screen_safe_1.rowField)(r, 60)}' — check a list number or an id prefix` };
|
|
28
28
|
}
|
|
29
29
|
return {
|
|
30
30
|
ok: false,
|
|
@@ -33,7 +33,7 @@ function resolveRequestRef(pending, ref) {
|
|
|
33
33
|
// id) wrapped it past a row and pushed the question off the top of the screen. The refusal keeps
|
|
34
34
|
// one row; each candidate gets its own, which is also how the operator copies one.
|
|
35
35
|
reason: [
|
|
36
|
-
|
|
36
|
+
`reference '${(0, screen_safe_1.rowField)(r, 30)}' matches ${matches.length} — give a longer one:`,
|
|
37
37
|
...matches.map((m) => ` ${(0, screen_safe_1.rowField)(m.id, 60)}`),
|
|
38
38
|
].join('\n'),
|
|
39
39
|
};
|
|
@@ -24,7 +24,7 @@ export declare function decideByKey(stream: KeyStream, _item: Pick<PendingReques
|
|
|
24
24
|
*/
|
|
25
25
|
export declare function presentByKey(root: string, io: ApproveIO, actor: string, stream: KeyStream, state: ReturnType<typeof readQueue>): Promise<void>;
|
|
26
26
|
/** The idle line, printed once on entering idle (not on every poll — flood is the template's enemy). */
|
|
27
|
-
export declare const WATCH_IDLE = "\u25C6
|
|
27
|
+
export declare const WATCH_IDLE = "\u25C6 awaiting approval\u2026 (Ctrl-C to exit)";
|
|
28
28
|
/** Default idle poll cadence. The reaction DEADLINE is a tested judgment (T-SPEC), not this number. */
|
|
29
29
|
export declare const DEFAULT_POLL_MS = 500;
|
|
30
30
|
export interface WatchOpts {
|
|
@@ -85,25 +85,25 @@ function decideByKey(stream, _item) {
|
|
|
85
85
|
async function presentByKey(root, io, actor, stream, state) {
|
|
86
86
|
for (const p of state.pending) {
|
|
87
87
|
io.print(`\n─ ${(0, approve_1.subjectCells)('─ ', p)}`);
|
|
88
|
-
io.print('[a]
|
|
88
|
+
io.print('[a]approve [d]deny [q]question [s]skip > ');
|
|
89
89
|
const d = await decideByKey(stream, p);
|
|
90
90
|
if (d === 'granted') {
|
|
91
91
|
const r = (0, approve_1.grantRequest)(root, p.id, { actor });
|
|
92
|
-
io.print(r.ok ? `✓
|
|
92
|
+
io.print(r.ok ? `✓ approved — ${(0, approve_1.subjectCells)('✓ approved — ', p)}` : `✗ ${r.reason}`);
|
|
93
93
|
}
|
|
94
94
|
else if (d === 'denied') {
|
|
95
|
-
const r = (0, approve_1.denyRequest)(root, p.id, '
|
|
96
|
-
io.print(r.ok ? `✗
|
|
95
|
+
const r = (0, approve_1.denyRequest)(root, p.id, 'denied by the operator with a single key in watch (no immediate reason)', actor);
|
|
96
|
+
io.print(r.ok ? `✗ denied — ${(0, approve_1.subjectCells)('✗ denied — ', p)}` : `✗ ${r.reason}`);
|
|
97
97
|
}
|
|
98
98
|
else if (typeof d === 'object') {
|
|
99
99
|
const r = (0, approve_1.holdRequest)(root, p.id, d.question, actor);
|
|
100
|
-
io.print(r.ok ? `?
|
|
100
|
+
io.print(r.ok ? `? held — the question rides on the next denial message` : `✗ ${r.reason}`);
|
|
101
101
|
}
|
|
102
102
|
// skipped: leave it pending, silently
|
|
103
103
|
}
|
|
104
104
|
}
|
|
105
105
|
/** The idle line, printed once on entering idle (not on every poll — flood is the template's enemy). */
|
|
106
|
-
exports.WATCH_IDLE = '◆
|
|
106
|
+
exports.WATCH_IDLE = '◆ awaiting approval… (Ctrl-C to exit)';
|
|
107
107
|
/** Default idle poll cadence. The reaction DEADLINE is a tested judgment (T-SPEC), not this number. */
|
|
108
108
|
exports.DEFAULT_POLL_MS = 500;
|
|
109
109
|
/** Exported for test: the shipped idle wait must resolve at once on abort, not after the timer. */
|
|
@@ -12,7 +12,7 @@ export declare function grantRequest(root: string, id: string, opts: {
|
|
|
12
12
|
rationale?: string;
|
|
13
13
|
}): DecisionResult;
|
|
14
14
|
export declare function denyRequest(root: string, id: string, reason: string, actor: string): DecisionResult;
|
|
15
|
-
/** Hold with a question —
|
|
15
|
+
/** Hold with a question — approve/deny later. The agent carries the question to the user. */
|
|
16
16
|
export declare function holdRequest(root: string, id: string, question: string, actor: string): DecisionResult;
|
|
17
17
|
/**
|
|
18
18
|
* The review screen. Pure.
|
|
@@ -36,7 +36,7 @@ export declare function renderPending(state: QueueState, view?: {
|
|
|
36
36
|
}): string;
|
|
37
37
|
/**
|
|
38
38
|
* @implements A-SPEC-262.1
|
|
39
|
-
* What a decision line must say. Round-1: `✓
|
|
39
|
+
* What a decision line must say. Round-1: `✓ approved — valid until <expires>` named NOTHING, so an index
|
|
40
40
|
* that resolved against a shifted queue granted a different request than the operator read, with no
|
|
41
41
|
* way to see it from the screen. Every decision now echoes the request it acted on.
|
|
42
42
|
*/
|
|
@@ -44,7 +44,7 @@ export declare function renderPending(state: QueueState, view?: {
|
|
|
44
44
|
* @implements A-SPEC-262.1
|
|
45
45
|
* The kind cell and the subject, fitted to what is LEFT OF THE ROW after the caller's own prefix.
|
|
46
46
|
*
|
|
47
|
-
* Round-8: the budgets were global constants derived once, from `✓
|
|
47
|
+
* Round-8: the budgets were global constants derived once, from `✓ approved — ` — and `✓ denied — `
|
|
48
48
|
* is seven columns wider, so every denial echo ran to 88 columns and wrapped. Round-7 had widened
|
|
49
49
|
* the kind cell without re-measuring any line that carries it. A constant cannot know which prefix
|
|
50
50
|
* it will be printed behind, so the line computes its own room and the prefix is passed in. The
|