@holmes-lab/holmes-kit 0.10.1 → 0.12.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 -1
- package/README.md +2 -0
- package/dist/.build-id +1 -1
- package/dist/holmes/cli/approve.js +2 -1
- package/dist/holmes/cli/doctor.js +6 -3
- package/dist/holmes/cli/index.js +49 -0
- package/dist/holmes/cli/upgrade.d.ts +14 -0
- package/dist/holmes/cli/upgrade.js +177 -0
- package/dist/holmes/governance/approval-queue.js +3 -2
- package/dist/holmes/hooks/session-start.js +45 -0
- package/dist/holmes/hooks/stop.js +2 -1
- package/dist/holmes/mcp/elicit-approval.js +4 -3
- package/dist/holmes/mcp/handlers.d.ts +17 -1
- package/dist/holmes/mcp/handlers.js +34 -0
- package/dist/holmes/mcp/tool-schemas.js +10 -0
- package/dist/holmes/project/npx-bin.d.ts +10 -0
- package/dist/holmes/project/npx-bin.js +16 -0
- package/dist/holmes/server/cfg-view.d.ts +45 -0
- package/dist/holmes/server/cfg-view.js +57 -0
- package/dist/holmes/server/dashboard-launcher.d.ts +40 -0
- package/dist/holmes/server/dashboard-launcher.js +54 -0
- package/dist/holmes/server/dashboard.js +206 -152
- package/dist/holmes/server/rtm-matrix.d.ts +47 -0
- package/dist/holmes/server/rtm-matrix.js +83 -0
- package/dist/holmes/update/update-notice.js +4 -2
- package/dist/holmes/update/upgrade-plan.d.ts +43 -0
- package/dist/holmes/update/upgrade-plan.js +45 -0
- package/dist/holmes/update/workspaces.d.ts +35 -0
- package/dist/holmes/update/workspaces.js +109 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -5,7 +5,64 @@ 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.
|
|
8
|
+
## [0.12.0] - 2026-09-05
|
|
9
|
+
|
|
10
|
+
World-top-tier RTM dashboard, in flight. The `holmes-kit serve` dashboard stops being a table of
|
|
11
|
+
accordions and becomes a real coverage matrix, gains a one-call launcher, and learns to serve CFG/PDG
|
|
12
|
+
for a function. Additive and backward-compatible — every existing `/api/*` payload keeps its shape and
|
|
13
|
+
values; these are new fields, a new endpoint, a new tool, and a rebuilt heatmap view.
|
|
14
|
+
|
|
15
|
+
### Added
|
|
16
|
+
|
|
17
|
+
- **2D coverage matrix** (REQ-545): the heatmap tab is now a single **requirement × pipeline-stage
|
|
18
|
+
grid** — rows are requirements ordered by completeness (seriation), each cell a sequential-ramp
|
|
19
|
+
colour with the coverage percent printed in it (so magnitude never rides on colour alone). Replaces
|
|
20
|
+
the per-REQ accordion-of-tables. `/api/rtm/heatmap` gains an additive `matrix` field
|
|
21
|
+
(`{reqs, stages, values, order, buckets}`) built by pure, unit-tested `buildStageMatrix` +
|
|
22
|
+
`seriateRows` + `coverageBucket`.
|
|
23
|
+
- **`rtm_dashboard` MCP tool** (REQ-545): ask to *see* the RTM heatmap / coverage matrix and the
|
|
24
|
+
server launches (idempotently — a second call reuses the live one) and returns `{ url, running,
|
|
25
|
+
census }`, where `census` is an honesty summary (`reqCount, pipelineCount, coveredCount,
|
|
26
|
+
coveragePct, retiredCount, findingsScanned`). No more starting `serve` by hand.
|
|
27
|
+
- **`/api/cfg` — control-flow + program-dependence for one function** (REQ-545): `?file=&symbol=`
|
|
28
|
+
returns that function's CFG (blocks with source line/label, control-flow edges) and PDG (block-level
|
|
29
|
+
control deps + reaching-def data deps), consumed from the same `cfgOf`/`pdgOf` the taint lane uses.
|
|
30
|
+
A non-CFG language is named as `unsupported`, never faked as an empty graph. (Graph-view rendering
|
|
31
|
+
of this lands next.)
|
|
32
|
+
|
|
33
|
+
### Changed
|
|
34
|
+
|
|
35
|
+
- **Dashboard design tokens** (REQ-545): the palette moved off the Tailwind-default + emoji-marker
|
|
36
|
+
look to a tokenised system — a single-hue sequential coverage ramp, reserved status colours, a
|
|
37
|
+
UI/mono type pairing (data in tabular-nums), surfaces and ink, with a light and a dark palette via
|
|
38
|
+
`prefers-color-scheme`. Applied to the new matrix view and the tab bar; structure emoji removed
|
|
39
|
+
(meaning markers only). The rest of the dashboard tokenises incrementally in the follow-on slice.
|
|
40
|
+
- **Upgrade nudges point at one command** (REQ-544): the session banner's drift line and doctor's
|
|
41
|
+
version-drift fix hint now name `holmes-kit upgrade` (the right binary per platform — `npx.cmd` on
|
|
42
|
+
win32) instead of spelling out a manual `npm i` + `init --force`.
|
|
43
|
+
|
|
44
|
+
## [0.11.0] - 2026-09-05
|
|
45
|
+
|
|
46
|
+
Zero-config upgrades — preparation is automatic, execution is your explicit choice. Additive and
|
|
47
|
+
backward-compatible; nothing changes at the default settings until you run `holmes-kit upgrade`.
|
|
48
|
+
|
|
49
|
+
### Added
|
|
50
|
+
|
|
51
|
+
- **`holmes-kit upgrade`** (REQ-543): one command to move every wired workspace to the latest — it
|
|
52
|
+
shows a plan (current → latest, and which workspaces it will re-pin), asks for confirmation, then
|
|
53
|
+
installs per install-mode, clears the stale npx cache, re-pins each recorded workspace, and prints
|
|
54
|
+
a per-workspace result table. `--dry-run` prints the plan and changes nothing; `--yes` skips the
|
|
55
|
+
prompt (required in a non-interactive shell — the execution is a deliberate choice, never a silent
|
|
56
|
+
auto-install of a gate-enforcing engine). A source checkout is told to upgrade via git.
|
|
57
|
+
- **Workspace registry** (REQ-543): every successful `init` now records where holmes-kit is wired
|
|
58
|
+
(`~/.holmes/workspaces.json` — target, agents, version), so `upgrade` knows every workspace to
|
|
59
|
+
re-pin. Best-effort and side-channel: a registry problem never affects `init`.
|
|
60
|
+
- **Session drift nudge** (REQ-543): when a session starts in a workspace whose `.mcp.json` pin is
|
|
61
|
+
OLDER than the installed package, the banner adds one line pointing at `holmes-kit upgrade`.
|
|
62
|
+
Detection is automatic; the re-pin **write** stays behind your `upgrade`/`init --force` choice —
|
|
63
|
+
a session-start force-write would bypass the governance-wiring approval, so it is not done silently.
|
|
64
|
+
Opt out with `HOLMES_NO_AUTO_REPIN`; no drift, opt-out, or unreadable pin leaves the banner
|
|
65
|
+
byte-identical.
|
|
9
66
|
|
|
10
67
|
### Fixed
|
|
11
68
|
|
package/README.md
CHANGED
|
@@ -19,6 +19,8 @@
|
|
|
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.
|
|
21
21
|
- 🧰 **Governance UX tools** *(new in 0.10.0)*: `spec_unseal` (the inverse of `spec_approve` — return a sealed spec to editable `draft` in one act, out-of-band approval required, refuses approved dependents), `approval_status` and `ledger_timeline` (read-only observability into a spec's seal state and the governance history), and a structured `conflict` on `spec_approve`'s optimistic-concurrency refusal (read vs. current version + retry). See CHANGELOG for details.
|
|
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
|
+
- 📊 **World-top-tier RTM dashboard** *(new in 0.12.0, in flight)*: `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 now a real **2D coverage matrix** (requirements × pipeline stages, rows seriated by completeness, sequential-ramp cells with the percent printed in each), and `/api/cfg?file=&symbol=` serves a function's **CFG + PDG** (control-flow blocks + data/control dependence) from the same engine the taint lane uses — a non-CFG language is named, never faked. Tokenised palette + light/dark; graph-view rendering of CFG/PDG lands next.
|
|
22
24
|
- 🤖 **Autonomous Approval** *(new in 0.8.0)*: with the out-of-band `HOLMES_AUTONOMOUS_APPROVAL` switch on, an agent seals **low/mid-risk** specs itself (ledgered under an `autonomous:<client>` actor) — while `gate-behavior` changes, architecture/gate/taint files, and every upstream `REQ`/`H-SPEC`/`C-SPEC` still ask a human through the in-session TUI. The switch is env-only; a session cannot set it (blocked like `HOLMES_ROLE`). Off = byte-identical to before. *(new in 0.10.0)* Set **`HOLMES_ELICIT=off`** to skip the in-session prompt entirely and route every decision to the out-of-band `holmes-kit approve` queue instead — one decision surface that behaves identically across Claude Code / Antigravity / Codex.
|
|
23
25
|
- 🪧 **Session Banner + Update Notice** *(new in 0.8.0)*: 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 cached, an install-mode-aware update command is appended. Registry check is detached, fail-silent, and opts out via `HOLMES_NO_UPDATE_CHECK`/`CI`.
|
|
24
26
|
- 🧱 **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.
|
package/dist/.build-id
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
|
|
1
|
+
85d7b53-mtnr6dpp
|
|
@@ -46,6 +46,7 @@ exports.renderNonTtyHint = renderNonTtyHint;
|
|
|
46
46
|
exports.runInteractive = runInteractive;
|
|
47
47
|
// @implements A-SPEC-246
|
|
48
48
|
const fs = __importStar(require("node:fs"));
|
|
49
|
+
const npx_bin_1 = require("../project/npx-bin");
|
|
49
50
|
const path = __importStar(require("node:path"));
|
|
50
51
|
const approval_queue_1 = require("../governance/approval-queue");
|
|
51
52
|
const screen_safe_1 = require("./screen-safe");
|
|
@@ -143,7 +144,7 @@ function grantRequest(root, id, opts) {
|
|
|
143
144
|
}
|
|
144
145
|
const { entry } = findPending(root, id);
|
|
145
146
|
if (!entry)
|
|
146
|
-
return { ok: false, reason: `대기 중인 요청 ${(0, screen_safe_1.rowField)(id, 40)} 가 없습니다 —
|
|
147
|
+
return { ok: false, reason: `대기 중인 요청 ${(0, screen_safe_1.rowField)(id, 40)} 가 없습니다 — ${(0, npx_bin_1.npxBin)()} holmes-kit approve --list 로 확인하십시오` };
|
|
147
148
|
if (!recordIsSelfConsistent(entry))
|
|
148
149
|
return { ok: false, reason: TAMPERED };
|
|
149
150
|
// A "does this match what the operator saw" parameter was written here too and then removed: the
|
|
@@ -49,6 +49,7 @@ exports.detectTreeKeyTemporary = detectTreeKeyTemporary;
|
|
|
49
49
|
// @implements A-SPEC-207
|
|
50
50
|
// @implements A-SPEC-100.2
|
|
51
51
|
const fs = __importStar(require("node:fs"));
|
|
52
|
+
const npx_bin_1 = require("../project/npx-bin");
|
|
52
53
|
const tier_1 = require("../semantic/tier");
|
|
53
54
|
const path = __importStar(require("node:path"));
|
|
54
55
|
const role_policy_1 = require("../governance/role-policy");
|
|
@@ -756,7 +757,9 @@ async function runDoctor(packageRoot, target, opts, extraChecks) {
|
|
|
756
757
|
const launch = (0, mcp_version_1.mcpLaunchVersion)({ command: server.command, args: server.args }, readPkgVersionAt);
|
|
757
758
|
const verdict = (0, mcp_version_1.versionDriftVerdict)(launch, cli);
|
|
758
759
|
if (verdict === 'drift') {
|
|
759
|
-
add('mcp server version', 'WARN', `이 프로젝트의 MCP 서버는 holmes-kit ${launch} 를 띄우지만 이 CLI 는 ${cli} 입니다 — 재배선 전까지 옛 서버가 계속 뜹니다`,
|
|
760
|
+
add('mcp server version', 'WARN', `이 프로젝트의 MCP 서버는 holmes-kit ${launch} 를 띄우지만 이 CLI 는 ${cli} 입니다 — 재배선 전까지 옛 서버가 계속 뜹니다`,
|
|
761
|
+
// @implements A-SPEC-544.1 — one command installs + re-pins every workspace; init --force is the per-workspace path.
|
|
762
|
+
`${(0, npx_bin_1.npxBin)()} holmes-kit upgrade 로 최신 설치 + 전 워크스페이스 재핀 (이 워크스페이스만 고치려면 ${(0, npx_bin_1.npxBin)()} holmes-kit init --force).`);
|
|
760
763
|
}
|
|
761
764
|
else if (verdict === 'match') {
|
|
762
765
|
add('mcp server version', 'PASS', `MCP 서버 버전 ${launch} 가 이 CLI 와 일치합니다`);
|
|
@@ -810,7 +813,7 @@ async function runDoctor(packageRoot, target, opts, extraChecks) {
|
|
|
810
813
|
const h = JSON.parse(fs.readFileSync(agyHooks, 'utf8'));
|
|
811
814
|
const entry = h['holmes-kit'];
|
|
812
815
|
const pre = entry?.PreToolUse?.[0];
|
|
813
|
-
// @implements A-SPEC-
|
|
816
|
+
// @implements A-SPEC-541.1 — the hook script path now lives in args[0] (command is bare
|
|
814
817
|
// 'node'); older wirings joined it into the command string. Read args first, fall back to
|
|
815
818
|
// parsing the string so a pre-539 hooks.json is still judged correctly.
|
|
816
819
|
const scriptOf = (e) => (Array.isArray(e?.args) && typeof e?.args[0] === 'string') ? e.args[0] : (0, settings_merge_1.hookScriptPath)(e?.command ?? '');
|
|
@@ -1280,7 +1283,7 @@ function pushGateCheck(target) {
|
|
|
1280
1283
|
: {
|
|
1281
1284
|
name: 'push gate', level: 'WARN',
|
|
1282
1285
|
detail: 'pre-push 증빙 게이트가 설치되어 있지 않습니다 — 미검증 HEAD 가 다른 에이전트의 기준선이 될 수 있습니다',
|
|
1283
|
-
fix:
|
|
1286
|
+
fix: `${(0, npx_bin_1.npxBin)()} holmes-kit install-push-gate 를 실행하십시오 (기존 훅은 덮지 않습니다)`,
|
|
1284
1287
|
};
|
|
1285
1288
|
}
|
|
1286
1289
|
catch {
|
package/dist/holmes/cli/index.js
CHANGED
|
@@ -82,6 +82,8 @@ const KNOWN_FLAGS = {
|
|
|
82
82
|
'semantic-key': ['help'],
|
|
83
83
|
// @implements A-SPEC-509.1 — the explicit installation act for the pre-push evidence gate.
|
|
84
84
|
'install-push-gate': ['help', 'target'],
|
|
85
|
+
// @implements A-SPEC-543.2 — one-command upgrade: install + re-pin every registered workspace.
|
|
86
|
+
upgrade: ['help', 'dry-run', 'yes'],
|
|
85
87
|
};
|
|
86
88
|
// @implements A-SPEC-171 — subcommands that render their OWN usage on `--help`. A-SPEC-171 governs
|
|
87
89
|
// "help before any side effect"; a command whose usage lives past this handler (approve, whose
|
|
@@ -212,6 +214,10 @@ const USAGE = `holmes-kit — deterministic ASE governance for a project
|
|
|
212
214
|
--target <dir> also verify that target's wiring points at this install
|
|
213
215
|
--json machine-readable output
|
|
214
216
|
|
|
217
|
+
holmes-kit upgrade [flags] install the latest and re-pin every recorded workspace (one command)
|
|
218
|
+
--dry-run show the plan (current→latest, workspaces to re-pin), change nothing
|
|
219
|
+
--yes skip the confirmation prompt (required in a non-interactive shell)
|
|
220
|
+
|
|
215
221
|
holmes-kit skills refresh [flags] refresh the recovery skills holmes-kit owns
|
|
216
222
|
--target <dir> target project (default: cwd; must exist). Touches ONLY marker-owned
|
|
217
223
|
skill files (installs absent ones, REWRITES all marker-owned ones,
|
|
@@ -1038,9 +1044,52 @@ async function main(argv) {
|
|
|
1038
1044
|
process.stdout.write(c.after.split('\n').map((l) => ` | ${l}`).join('\n') + '\n');
|
|
1039
1045
|
}
|
|
1040
1046
|
}
|
|
1047
|
+
// @implements A-SPEC-543.1 — zero-config upgrade PREPARATION: a successful real wiring records
|
|
1048
|
+
// WHERE holmes-kit is wired, so `upgrade` later knows every workspace to re-pin. Best-effort by
|
|
1049
|
+
// contract: a registry problem must never change init's output or exit code.
|
|
1050
|
+
if (!opts.dryRun && !opts.remove) {
|
|
1051
|
+
try {
|
|
1052
|
+
const { recordWorkspace } = require('../update/workspaces');
|
|
1053
|
+
const os2 = require('node:os');
|
|
1054
|
+
const pkgVersion = (() => {
|
|
1055
|
+
try {
|
|
1056
|
+
return String(JSON.parse(fs.readFileSync(path.join(opts.packageRoot, 'package.json'), 'utf8')).version ?? '');
|
|
1057
|
+
}
|
|
1058
|
+
catch {
|
|
1059
|
+
return '';
|
|
1060
|
+
}
|
|
1061
|
+
})();
|
|
1062
|
+
recordWorkspace(os2.homedir(), { target: opts.target, agents: [...agents], version: pkgVersion, ts: new Date().toISOString() });
|
|
1063
|
+
}
|
|
1064
|
+
catch { /* registry failure never fails init */ }
|
|
1065
|
+
}
|
|
1041
1066
|
}
|
|
1042
1067
|
return res.exitCode;
|
|
1043
1068
|
}
|
|
1069
|
+
// @implements A-SPEC-543.2 — one-command upgrade. Preparation is zero-config (init records the
|
|
1070
|
+
// registry); the EXECUTION is the user's explicit choice — a plan is shown and confirmed (or a
|
|
1071
|
+
// non-TTY passes --yes), never a silent auto-install of a gate-enforcing engine.
|
|
1072
|
+
if (cmd === 'upgrade') {
|
|
1073
|
+
const { runUpgrade } = require('./upgrade');
|
|
1074
|
+
return await runUpgrade({
|
|
1075
|
+
dryRun: flags['dry-run'] === true,
|
|
1076
|
+
yes: flags.yes === true,
|
|
1077
|
+
stdout: (s) => process.stdout.write(s),
|
|
1078
|
+
stderr: (s) => process.stderr.write(s),
|
|
1079
|
+
confirm: async (prompt) => {
|
|
1080
|
+
if (!process.stdin.isTTY || !process.stdout.isTTY)
|
|
1081
|
+
return false;
|
|
1082
|
+
const rl = require('node:readline/promises').createInterface({ input: process.stdin, output: process.stdout });
|
|
1083
|
+
try {
|
|
1084
|
+
const ans = (await rl.question(prompt)).trim().toLowerCase();
|
|
1085
|
+
return ans === 'y' || ans === 'yes';
|
|
1086
|
+
}
|
|
1087
|
+
finally {
|
|
1088
|
+
rl.close();
|
|
1089
|
+
}
|
|
1090
|
+
},
|
|
1091
|
+
});
|
|
1092
|
+
}
|
|
1044
1093
|
// round-8: `holmes-kit --target x` 나 `holmes-kit --` 처럼 명령 자리에 플래그가 오면 exit 1,
|
|
1045
1094
|
// 같은 토큰이 하위 명령 뒤에 오면 exit 2 였다 — 같은 실수에 두 가지 진단.
|
|
1046
1095
|
process.stderr.write(cmd.startsWith('-')
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export interface UpgradeIO {
|
|
2
|
+
dryRun: boolean;
|
|
3
|
+
yes: boolean;
|
|
4
|
+
stdout: (s: string) => void;
|
|
5
|
+
stderr: (s: string) => void;
|
|
6
|
+
confirm: (prompt: string) => Promise<boolean>;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* @implements A-SPEC-543.2
|
|
10
|
+
* Thin orchestration around the pure `planUpgrade`. Resolves latest from the update cache, plans,
|
|
11
|
+
* shows/confirms (the user's choice), then installs + re-pins every registered workspace. Install
|
|
12
|
+
* failure aborts BEFORE any re-pin (never re-pin to a version that did not install).
|
|
13
|
+
*/
|
|
14
|
+
export declare function runUpgrade(io: UpgradeIO): Promise<number>;
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.runUpgrade = runUpgrade;
|
|
37
|
+
// @implements A-SPEC-543.2
|
|
38
|
+
const fs = __importStar(require("node:fs"));
|
|
39
|
+
const os = __importStar(require("node:os"));
|
|
40
|
+
const path = __importStar(require("node:path"));
|
|
41
|
+
const node_child_process_1 = require("node:child_process");
|
|
42
|
+
const update_notice_1 = require("../update/update-notice");
|
|
43
|
+
const workspaces_1 = require("../update/workspaces");
|
|
44
|
+
const upgrade_plan_1 = require("../update/upgrade-plan");
|
|
45
|
+
const session_start_1 = require("../hooks/session-start");
|
|
46
|
+
const npx_bin_1 = require("../project/npx-bin");
|
|
47
|
+
const PKG = '@holmes-lab/holmes-kit';
|
|
48
|
+
// Local copy of index.ts's packageRoot (dist/holmes/cli/upgrade.js → 3 up) — avoids a require cycle,
|
|
49
|
+
// since index.ts lazily requires THIS module.
|
|
50
|
+
function packageRoot() {
|
|
51
|
+
return path.resolve(__dirname, '..', '..', '..');
|
|
52
|
+
}
|
|
53
|
+
function currentVersion(root) {
|
|
54
|
+
try {
|
|
55
|
+
return String(JSON.parse(fs.readFileSync(path.join(root, 'package.json'), 'utf8')).version ?? '0.0.0');
|
|
56
|
+
}
|
|
57
|
+
catch {
|
|
58
|
+
return '0.0.0';
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
/** `npm` on POSIX, `npm.cmd` on win32 — the shim a spawn resolves (same binary-name reasoning as npxBin). */
|
|
62
|
+
function npmBin() { return process.platform === 'win32' ? 'npm.cmd' : 'npm'; }
|
|
63
|
+
/** Render the plan for the human, one line per fact. */
|
|
64
|
+
function renderPlan(plan) {
|
|
65
|
+
const lines = [
|
|
66
|
+
`Holmes-Kit upgrade: ${plan.current} → ${plan.latest} (install mode: ${plan.installMode})`,
|
|
67
|
+
plan.repins.length
|
|
68
|
+
? `Will re-pin ${plan.repins.length} workspace(s):`
|
|
69
|
+
: 'No wired workspaces recorded yet — install only.',
|
|
70
|
+
...plan.repins.map((w) => ` • ${w.target} [${w.agents.join(', ')}]`),
|
|
71
|
+
];
|
|
72
|
+
return lines.join('\n') + '\n';
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* @implements A-SPEC-543.2
|
|
76
|
+
* Thin orchestration around the pure `planUpgrade`. Resolves latest from the update cache, plans,
|
|
77
|
+
* shows/confirms (the user's choice), then installs + re-pins every registered workspace. Install
|
|
78
|
+
* failure aborts BEFORE any re-pin (never re-pin to a version that did not install).
|
|
79
|
+
*/
|
|
80
|
+
async function runUpgrade(io) {
|
|
81
|
+
const root = packageRoot();
|
|
82
|
+
const current = currentVersion(root);
|
|
83
|
+
const mode = (0, session_start_1.detectInstallMode)(root);
|
|
84
|
+
const cached = (0, update_notice_1.readCache)(os.homedir(), (p) => fs.readFileSync(p, 'utf8'));
|
|
85
|
+
const registry = (() => {
|
|
86
|
+
try {
|
|
87
|
+
return (0, workspaces_1.readRegistry)(fs.readFileSync(path.join(os.homedir(), '.holmes', 'workspaces.json'), 'utf8'));
|
|
88
|
+
}
|
|
89
|
+
catch {
|
|
90
|
+
return (0, workspaces_1.readRegistry)(null);
|
|
91
|
+
}
|
|
92
|
+
})();
|
|
93
|
+
const plan = (0, upgrade_plan_1.planUpgrade)({ current, latest: cached?.latest ?? null, registry, installMode: mode });
|
|
94
|
+
if (plan.kind === 'noop') {
|
|
95
|
+
io.stdout(`${plan.reason}\n`);
|
|
96
|
+
return 0;
|
|
97
|
+
}
|
|
98
|
+
io.stdout(renderPlan(plan));
|
|
99
|
+
if (io.dryRun) {
|
|
100
|
+
io.stdout('(dry-run — nothing installed or re-pinned)\n');
|
|
101
|
+
return 0;
|
|
102
|
+
}
|
|
103
|
+
if (plan.installMode === 'source') {
|
|
104
|
+
io.stderr('source checkout: upgrade via git (git pull && npm run build), not npm install.\n');
|
|
105
|
+
return 1;
|
|
106
|
+
}
|
|
107
|
+
const go = io.yes || await io.confirm('계속합니까? [y/N] ');
|
|
108
|
+
if (!go) {
|
|
109
|
+
io.stdout(io.yes ? '' : 'aborted — nothing installed or re-pinned.\n');
|
|
110
|
+
if (!io.yes && (!process.stdin.isTTY))
|
|
111
|
+
io.stderr('non-interactive: pass --yes to upgrade without a prompt.\n');
|
|
112
|
+
return io.yes ? 0 : (process.stdin.isTTY ? 0 : 1);
|
|
113
|
+
}
|
|
114
|
+
// (1) install — failure aborts before any re-pin.
|
|
115
|
+
try {
|
|
116
|
+
const args = plan.installMode === 'global-npx'
|
|
117
|
+
? ['i', '-g', `${PKG}@${plan.latest}`]
|
|
118
|
+
: ['i', '-D', `${PKG}@${plan.latest}`];
|
|
119
|
+
(0, node_child_process_1.execFileSync)(npmBin(), args, { stdio: 'inherit', cwd: process.cwd() });
|
|
120
|
+
}
|
|
121
|
+
catch {
|
|
122
|
+
io.stderr(`install failed — nothing re-pinned. Try manually: ${npmBin()} i ${plan.installMode === 'global-npx' ? '-g' : '-D'} ${PKG}@${plan.latest}\n`);
|
|
123
|
+
return 1;
|
|
124
|
+
}
|
|
125
|
+
// (2) best-effort: clear our stale npx cache so a subsequent npx picks up the new version.
|
|
126
|
+
try {
|
|
127
|
+
const npxDir = path.join(os.homedir(), '.npm', '_npx');
|
|
128
|
+
for (const d of fs.readdirSync(npxDir)) {
|
|
129
|
+
const pkgJson = path.join(npxDir, d, 'node_modules', PKG, 'package.json');
|
|
130
|
+
if (fs.existsSync(pkgJson))
|
|
131
|
+
fs.rmSync(path.join(npxDir, d), { recursive: true, force: true });
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
catch { /* cache clear is best-effort */ }
|
|
135
|
+
// (3) re-pin every registered workspace that still exists.
|
|
136
|
+
const { runInit } = require('./init');
|
|
137
|
+
const newRoot = packageRoot(); // may differ after a global install; re-resolve
|
|
138
|
+
let repinned = 0, skipped = 0, failed = 0;
|
|
139
|
+
io.stdout('\nRe-pinning workspaces:\n');
|
|
140
|
+
for (const w of registry.workspaces) {
|
|
141
|
+
if (!fs.existsSync(w.target)) {
|
|
142
|
+
io.stdout(` 건너뜀 (경로 없음): ${w.target}\n`);
|
|
143
|
+
skipped++;
|
|
144
|
+
continue;
|
|
145
|
+
}
|
|
146
|
+
try {
|
|
147
|
+
const res = runInit({
|
|
148
|
+
target: w.target,
|
|
149
|
+
packageRoot: newRoot,
|
|
150
|
+
mode: 'guardrail',
|
|
151
|
+
specsDir: '.ax/specs',
|
|
152
|
+
settingsFile: 'local',
|
|
153
|
+
mcp: true,
|
|
154
|
+
dryRun: false,
|
|
155
|
+
force: true,
|
|
156
|
+
remove: false,
|
|
157
|
+
agents: w.agents,
|
|
158
|
+
allowAdditive: true,
|
|
159
|
+
});
|
|
160
|
+
if (res.ok) {
|
|
161
|
+
io.stdout(` 재핀: ${w.target}\n`);
|
|
162
|
+
repinned++;
|
|
163
|
+
}
|
|
164
|
+
else {
|
|
165
|
+
io.stdout(` 실패: ${w.target}\n`);
|
|
166
|
+
failed++;
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
catch {
|
|
170
|
+
io.stdout(` 실패: ${w.target}\n`);
|
|
171
|
+
failed++;
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
io.stdout(`\nUpgraded to ${plan.latest}. 재핀 ${repinned} · 건너뜀 ${skipped} · 실패 ${failed}. 새 세션에서 훅/MCP 가 새 배선을 읽습니다.\n`);
|
|
175
|
+
io.stdout(`(개별 워크스페이스는 언제든 ${(0, npx_bin_1.npxBin)()} holmes-kit init --agent <…> --force 로 재배선할 수 있습니다.)\n`);
|
|
176
|
+
return 0;
|
|
177
|
+
}
|
|
@@ -41,6 +41,7 @@ exports.readQueue = readQueue;
|
|
|
41
41
|
exports.queueHint = queueHint;
|
|
42
42
|
// @implements A-SPEC-244
|
|
43
43
|
const node_crypto_1 = require("node:crypto");
|
|
44
|
+
const npx_bin_1 = require("../project/npx-bin");
|
|
44
45
|
const fs = __importStar(require("node:fs"));
|
|
45
46
|
const path = __importStar(require("node:path"));
|
|
46
47
|
const screen_safe_1 = require("../cli/screen-safe");
|
|
@@ -281,13 +282,13 @@ function queueHint(root, req) {
|
|
|
281
282
|
// template-owned margin; this producer, read by the operator AND relayed by the agent, never got
|
|
282
283
|
// one. The first row keeps the template's own opening; the rest sit under the margin.
|
|
283
284
|
return (0, screen_safe_1.wrapColumns)(`\n[보류 — 운영자 질문: ${(0, screen_safe_1.flattenField)(held.question ?? '(질문 없음)', 300)}] 게이트는 닫혀 있습니다.`
|
|
284
|
-
+
|
|
285
|
+
+ ` 이 질문에 대한 답을 사용자에게 보고하십시오. 결정은 운영자가 ${(0, npx_bin_1.npxBin)()} holmes-kit approve 에서 내립니다.`, 76, ' \u2502 ');
|
|
285
286
|
}
|
|
286
287
|
const decided = state.decisions[id];
|
|
287
288
|
const denialLine = decided?.event === 'denied'
|
|
288
289
|
? (0, screen_safe_1.wrapColumns)(`\n[거부됨${decided.reason ? `: ${(0, screen_safe_1.flattenField)(decided.reason, 300)}` : ''}] 같은 요청을 반복하지 말고 거부 사유를 해소해 사용자와 상의하십시오.`, 76, ' \u2502 ')
|
|
289
290
|
: '';
|
|
290
291
|
return enqueueApprovalRequest(root, req)
|
|
291
|
-
? `${denialLine}\n[승인 대기 ${id}] 운영자:
|
|
292
|
+
? `${denialLine}\n[승인 대기 ${id}] 운영자: ${(0, npx_bin_1.npxBin)()} holmes-kit approve`
|
|
292
293
|
: denialLine;
|
|
293
294
|
}
|
|
@@ -76,6 +76,41 @@ function pkgVersion() {
|
|
|
76
76
|
return '0.0.0';
|
|
77
77
|
}
|
|
78
78
|
}
|
|
79
|
+
/**
|
|
80
|
+
* @implements A-SPEC-543.3
|
|
81
|
+
* If this workspace's holmes pin is behind the installed package, return a one-line NUDGE to run
|
|
82
|
+
* `holmes-kit upgrade`; otherwise return ''. Detection is zero-config (automatic every session), but
|
|
83
|
+
* the re-pin WRITE is NOT done here: `runInit({force:true})` is gated by a governance-wiring approval,
|
|
84
|
+
* so a silent session-start write would bypass it. The write stays the user's explicit choice via
|
|
85
|
+
* `upgrade`/`init --force`. Forward-drift + opt-out judgment is the pure `repinDecision`; every
|
|
86
|
+
* failure returns '' (fail-open — a banner is never a gate).
|
|
87
|
+
*/
|
|
88
|
+
function driftNudge(cwd, installed, env) {
|
|
89
|
+
try {
|
|
90
|
+
const { mcpLaunchVersion } = require('../cli/mcp-version');
|
|
91
|
+
const { repinDecision } = require('../update/upgrade-plan');
|
|
92
|
+
const { npxBin } = require('../project/npx-bin');
|
|
93
|
+
const mcpPath = path.join(cwd, '.mcp.json');
|
|
94
|
+
if (!fs.existsSync(mcpPath))
|
|
95
|
+
return '';
|
|
96
|
+
const mcp = JSON.parse(fs.readFileSync(mcpPath, 'utf8'));
|
|
97
|
+
const entry = mcp.mcpServers?.['holmes-kit'];
|
|
98
|
+
if (!entry || typeof entry.command !== 'string' || !Array.isArray(entry.args))
|
|
99
|
+
return '';
|
|
100
|
+
const pinned = mcpLaunchVersion({ command: entry.command, args: entry.args }, (dir) => { try {
|
|
101
|
+
return String(JSON.parse(fs.readFileSync(path.join(dir, 'package.json'), 'utf8')).version);
|
|
102
|
+
}
|
|
103
|
+
catch {
|
|
104
|
+
return undefined;
|
|
105
|
+
} });
|
|
106
|
+
if (!repinDecision({ pinned, installed, env }).repin)
|
|
107
|
+
return '';
|
|
108
|
+
return `\n[Holmes-Kit] this workspace is pinned to ${pinned}, but ${installed} is installed — run \`${npxBin()} holmes-kit upgrade\` to re-pin.`;
|
|
109
|
+
}
|
|
110
|
+
catch {
|
|
111
|
+
return '';
|
|
112
|
+
}
|
|
113
|
+
}
|
|
79
114
|
// CLI entry: read the hook JSON on stdin (unused beyond triggering), emit the banner, and — when
|
|
80
115
|
// allowed — fire the detached refresh. Wrapped so any failure is an empty output + exit 0.
|
|
81
116
|
if (require.main === module) {
|
|
@@ -91,6 +126,16 @@ if (require.main === module) {
|
|
|
91
126
|
readFile: (p) => fs.readFileSync(p, 'utf8'),
|
|
92
127
|
mode: detectInstallMode(pkgRootFromEntry()),
|
|
93
128
|
});
|
|
129
|
+
// @implements A-SPEC-543.3 — self-healing re-pin: when this workspace's holmes pin is OLDER
|
|
130
|
+
// than the installed package, the user already chose to install; complete that choice by
|
|
131
|
+
// re-pinning the derived artifacts and telling the operator to restart. Forward-only, opt-out
|
|
132
|
+
// via HOLMES_NO_AUTO_REPIN, and FAIL-OPEN — any failure leaves the banner exactly as it was.
|
|
133
|
+
try {
|
|
134
|
+
const notice = driftNudge(process.cwd(), pkgVersion(), process.env);
|
|
135
|
+
if (notice)
|
|
136
|
+
out.hookSpecificOutput.additionalContext += notice;
|
|
137
|
+
}
|
|
138
|
+
catch { /* the banner is never a gate */ }
|
|
94
139
|
process.stdout.write(JSON.stringify({ hookSpecificOutput: out.hookSpecificOutput }));
|
|
95
140
|
if (out.shouldRefresh) {
|
|
96
141
|
// Detached, unref'd child so the session start does not wait on the network. The refresh
|
|
@@ -49,6 +49,7 @@ exports.guardCountOrZero = guardCountOrZero;
|
|
|
49
49
|
exports.readGuardCount = readGuardCount;
|
|
50
50
|
exports.writeGuardCount = writeGuardCount;
|
|
51
51
|
const fs = __importStar(require("node:fs"));
|
|
52
|
+
const npx_bin_1 = require("../project/npx-bin");
|
|
52
53
|
const risk_gate_1 = require("../guardrail/risk-gate");
|
|
53
54
|
const json_state_1 = require("../project/json-state");
|
|
54
55
|
const node_child_process_1 = require("node:child_process");
|
|
@@ -813,7 +814,7 @@ if (require.main === module) {
|
|
|
813
814
|
else if (ackWaiting.length > 0) {
|
|
814
815
|
// @implements A-SPEC-247 — acknowledged, not clean: name what is waiting so the user sees
|
|
815
816
|
// the standing approval debt exactly once, and the agent knows the ball is not in its court.
|
|
816
|
-
process.stderr.write(`[Holmes-Kit] 승인 대기 중 — 오너 결정을 기다리는 항목: ${ackWaiting.join(', ')}.
|
|
817
|
+
process.stderr.write(`[Holmes-Kit] 승인 대기 중 — 오너 결정을 기다리는 항목: ${ackWaiting.join(', ')}. ${(0, npx_bin_1.npxBin)()} holmes-kit approve 에서 결재하십시오. (이 부채로는 재차단하지 않습니다)\n`);
|
|
817
818
|
}
|
|
818
819
|
else if (guard.capped) {
|
|
819
820
|
process.stderr.write(`[Holmes-Kit] governance gate YIELDING after ${exports.MAX_CONSECUTIVE_BLOCKS} consecutive blocks — issues remain UNRESOLVED:\n${out.reason ?? ''}\n`);
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
// @implements A-SPEC-263.1
|
|
3
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
3
|
exports.MAX_PRESENTATIONS = exports.ELICIT_TIMEOUT_MS = exports.ELICITABLE_KINDS = void 0;
|
|
5
4
|
exports.elicitationDisabled = elicitationDisabled;
|
|
@@ -10,6 +9,8 @@ exports.buildDecisionForm = buildDecisionForm;
|
|
|
10
9
|
exports.buildReasonForm = buildReasonForm;
|
|
11
10
|
exports.runElicitFlow = runElicitFlow;
|
|
12
11
|
exports.interpretElicitResult = interpretElicitResult;
|
|
12
|
+
// @implements A-SPEC-263.1
|
|
13
|
+
const npx_bin_1 = require("../project/npx-bin");
|
|
13
14
|
/**
|
|
14
15
|
* The decision comes INTO the session (REQ-263): an approval-shaped refusal (spec_approve without a
|
|
15
16
|
* covering HOLMES_APPROVAL) becomes an in-session question when the client advertises the MCP
|
|
@@ -89,7 +90,7 @@ function classifyElicitError(e, timeoutMs = exports.ELICIT_TIMEOUT_MS) {
|
|
|
89
90
|
* Template-owned text with no interpolated attacker data.
|
|
90
91
|
*/
|
|
91
92
|
function expiredNotice(waitedMs) {
|
|
92
|
-
return `[세션 승인 다이얼로그 만료 — ${Math.round(waitedMs / 1000)}초 무응답] 요청은 승인 큐로 회송되었습니다. 결정:
|
|
93
|
+
return `[세션 승인 다이얼로그 만료 — ${Math.round(waitedMs / 1000)}초 무응답] 요청은 승인 큐로 회송되었습니다. 결정: ${(0, npx_bin_1.npxBin)()} holmes-kit approve. `;
|
|
93
94
|
}
|
|
94
95
|
// FLATTEN attacker-influenced text (round-1): the model chooses spec titles and finding ids, and
|
|
95
96
|
// interpolating them raw let a crafted title inject fake lines ("[시스템] … approve.") into the
|
|
@@ -111,7 +112,7 @@ function buildDecisionForm(req, attempt, timeoutMs) {
|
|
|
111
112
|
// @implements A-SPEC-497.1 — the forewarning is the LAST line and template-owned: it survives a
|
|
112
113
|
// cap-filling summary (appended after the caps) and tells the human, before the clock runs out,
|
|
113
114
|
// where an undecided request goes and where the decision still lives.
|
|
114
|
-
message: `${redisplay}[Holmes-Kit 승인 요청] ${flat(req.kind, 40)} — ${flat(req.target, 80)}\n${flat(req.summary, 200)}\n승인(approve) / 거부(deny) / 질문(question) 을 선택하세요. 사유는 거부·질문을 고르면 이어서 묻습니다.\n⏱ ${timeoutMs / 1000}초 내 미결정 시 승인 큐로 회송됩니다(운영자:
|
|
115
|
+
message: `${redisplay}[Holmes-Kit 승인 요청] ${flat(req.kind, 40)} — ${flat(req.target, 80)}\n${flat(req.summary, 200)}\n승인(approve) / 거부(deny) / 질문(question) 을 선택하세요. 사유는 거부·질문을 고르면 이어서 묻습니다.\n⏱ ${timeoutMs / 1000}초 내 미결정 시 승인 큐로 회송됩니다(운영자: ${(0, npx_bin_1.npxBin)()} holmes-kit approve).`,
|
|
115
116
|
requestedSchema: {
|
|
116
117
|
type: 'object',
|
|
117
118
|
properties: {
|
|
@@ -237,6 +237,22 @@ declare function makeRawHandlers(store: SpecStore, opts?: ElicitOpts): {
|
|
|
237
237
|
events: import("../governance/ledger-timeline").TimelineEntry[];
|
|
238
238
|
reason?: undefined;
|
|
239
239
|
}>;
|
|
240
|
+
rtm_dashboard(a: {
|
|
241
|
+
root?: string;
|
|
242
|
+
port?: number;
|
|
243
|
+
}): Promise<{
|
|
244
|
+
ok: boolean;
|
|
245
|
+
reason: string;
|
|
246
|
+
url?: undefined;
|
|
247
|
+
running?: undefined;
|
|
248
|
+
census?: undefined;
|
|
249
|
+
} | {
|
|
250
|
+
ok: boolean;
|
|
251
|
+
url: string;
|
|
252
|
+
running: boolean;
|
|
253
|
+
census: import("../server/dashboard-launcher").DashboardCensus;
|
|
254
|
+
reason?: undefined;
|
|
255
|
+
}>;
|
|
240
256
|
spec_approve(a: {
|
|
241
257
|
root?: string;
|
|
242
258
|
id: string;
|
|
@@ -458,7 +474,7 @@ declare function makeRawHandlers(store: SpecStore, opts?: ElicitOpts): {
|
|
|
458
474
|
baselineRecorded?: string | undefined;
|
|
459
475
|
scopeFallback?: "full" | undefined;
|
|
460
476
|
tier: import("../rtm/test-scope").RegressionTier;
|
|
461
|
-
mode: "full" | "
|
|
477
|
+
mode: "full" | "noop" | "scoped";
|
|
462
478
|
passed: boolean;
|
|
463
479
|
skipped: boolean;
|
|
464
480
|
ranFiles: string[];
|
|
@@ -45,6 +45,7 @@ exports.makeHandlers = makeHandlers;
|
|
|
45
45
|
// @implements A-SPEC-269
|
|
46
46
|
// @implements A-SPEC-267
|
|
47
47
|
const fs = __importStar(require("node:fs"));
|
|
48
|
+
const http = __importStar(require("node:http"));
|
|
48
49
|
const assoc_arm_1 = require("../assoc/assoc-arm");
|
|
49
50
|
// @implements A-SPEC-478 — the semantic tier runtime, resolved HERE (the async edge) and injected
|
|
50
51
|
// into the sync analysis; the replay harness injects nothing, keeping every pin untouched.
|
|
@@ -213,6 +214,21 @@ class HandlerRefusal extends Error {
|
|
|
213
214
|
constructor(message) { super(message); this.name = 'HandlerRefusal'; }
|
|
214
215
|
}
|
|
215
216
|
exports.HandlerRefusal = HandlerRefusal;
|
|
217
|
+
/** GET a localhost JSON endpoint (used by rtm_dashboard to read the census off the server it launched). */
|
|
218
|
+
function fetchJson(url) {
|
|
219
|
+
return new Promise((resolve, reject) => {
|
|
220
|
+
http.get(url, (res) => {
|
|
221
|
+
let data = '';
|
|
222
|
+
res.on('data', (c) => (data += c));
|
|
223
|
+
res.on('end', () => { try {
|
|
224
|
+
resolve(JSON.parse(data));
|
|
225
|
+
}
|
|
226
|
+
catch (e) {
|
|
227
|
+
reject(e);
|
|
228
|
+
} });
|
|
229
|
+
}).on('error', reject);
|
|
230
|
+
});
|
|
231
|
+
}
|
|
216
232
|
/** Marker test that survives realm boundaries (jest's instanceof does not). */
|
|
217
233
|
function isHandlerRefusal(e) {
|
|
218
234
|
return !!e && typeof e === 'object' && e.holmesRefusal === true;
|
|
@@ -1322,6 +1338,24 @@ function makeRawHandlers(store, opts) {
|
|
|
1322
1338
|
const events = fs.existsSync(dir) ? new ledger_store_1.FileLedgerStore(dir).loadAll() : [];
|
|
1323
1339
|
return { ok: true, events: (0, ledger_timeline_1.timelineFrom)(events, a.id) };
|
|
1324
1340
|
},
|
|
1341
|
+
// @implements A-SPEC-545.3 — "show me the RTM dashboard" launches (idempotently) the server the
|
|
1342
|
+
// agent would otherwise start by hand, and returns its URL plus an honesty census of what it shows.
|
|
1343
|
+
async rtm_dashboard(a) {
|
|
1344
|
+
const dest = resolveLedgerRoot(a.root);
|
|
1345
|
+
if (!dest.ok)
|
|
1346
|
+
return { ok: false, reason: dest.reason };
|
|
1347
|
+
try {
|
|
1348
|
+
const { startDashboardServer } = await Promise.resolve().then(() => __importStar(require('../server/dashboard')));
|
|
1349
|
+
const { ensureDashboard, dashboardCensus } = await Promise.resolve().then(() => __importStar(require('../server/dashboard-launcher')));
|
|
1350
|
+
const launch = await ensureDashboard(dest.root, a.port, (opts) => startDashboardServer(opts));
|
|
1351
|
+
const rtm = await fetchJson(`${launch.url}/api/rtm`);
|
|
1352
|
+
const heatmap = await fetchJson(`${launch.url}/api/rtm/heatmap`);
|
|
1353
|
+
return { ok: true, url: launch.url, running: launch.running, census: dashboardCensus(rtm, heatmap) };
|
|
1354
|
+
}
|
|
1355
|
+
catch (err) {
|
|
1356
|
+
return { ok: false, reason: `대시보드 기동 실패: ${err?.message ?? String(err)}` };
|
|
1357
|
+
}
|
|
1358
|
+
},
|
|
1325
1359
|
async spec_approve(a) {
|
|
1326
1360
|
const approvalRaw = process.env.HOLMES_APPROVAL;
|
|
1327
1361
|
let approval;
|