@holmes-lab/holmes-kit 0.12.0 → 0.12.1
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 +26 -0
- package/README.md +2 -1
- package/dist/.build-id +1 -1
- package/dist/holmes/mcp/handlers.js +8 -10
- package/dist/holmes/server/cfg-view.d.ts +24 -0
- package/dist/holmes/server/cfg-view.js +50 -0
- package/dist/holmes/server/dashboard.js +89 -1
- package/dist/holmes/spec/adr-refs.d.ts +7 -0
- package/dist/holmes/spec/adr-refs.js +17 -0
- package/package.json +4 -1
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,32 @@ 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.12.1] - 2026-09-05
|
|
9
|
+
|
|
10
|
+
Finishes the RTM dashboard's CFG/PDG story and fixes fresh-project spec numbering. Additive and
|
|
11
|
+
backward-compatible.
|
|
12
|
+
|
|
13
|
+
### Added
|
|
14
|
+
|
|
15
|
+
- **CFG/PDG graph-view drilldown** (REQ-545): drilling into a symbol in the graph tab now renders that
|
|
16
|
+
function's **control-flow graph as a layered DAG** with **program-dependence overlays** — control
|
|
17
|
+
flow, data dependence, and control dependence drawn as three distinct edge styles, unreachable
|
|
18
|
+
blocks dimmed, with a legend. `/api/cfg` gains a server-computed `layout` (deterministic
|
|
19
|
+
`layoutCfg`: BFS layers from entry, cycle-safe, unreachable blocks trailing) so the client only
|
|
20
|
+
paints. This completes REQ-545 (2D matrix · auto-launch · CFG endpoint · CFG render).
|
|
21
|
+
|
|
22
|
+
### Fixed
|
|
23
|
+
|
|
24
|
+
- **Fresh-project spec numbering starts at REQ-100, not REQ-201** (REQ-546): `spec_slice_init` hard-coded
|
|
25
|
+
a `maxId = 200` floor, so a brand-new project's first slice leapt to 201. It now shares the same id
|
|
26
|
+
allocator as the reverse-draft path (`nextIdBase`, floor 100). **Backward-compatible**: the next id
|
|
27
|
+
is always `max(existing)+1`, so a repo already numbering from 201 keeps the identical sequence and
|
|
28
|
+
no existing id is renamed; the change only lowers the *first* number in an empty (or sub-100-max)
|
|
29
|
+
project. Numbering past 999 yields 4-digit ids with no collision.
|
|
30
|
+
- **ADR references recognise four-digit ids** (REQ-546): the citation matcher was `\bADR-\d{3}\b`
|
|
31
|
+
(exactly three digits), so `ADR-1000+` was invisible. Extracted to a pure `adrRefsIn` and widened to
|
|
32
|
+
`\d{3,}` — three-digit matching and ordering are preserved, four-digit ADRs are now cited.
|
|
33
|
+
|
|
8
34
|
## [0.12.0] - 2026-09-05
|
|
9
35
|
|
|
10
36
|
World-top-tier RTM dashboard, in flight. The `holmes-kit serve` dashboard stops being a table of
|
package/README.md
CHANGED
|
@@ -20,7 +20,8 @@
|
|
|
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
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
|
|
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
|
+
- 🔢 **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+`).
|
|
24
25
|
- 🤖 **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.
|
|
25
26
|
- 🪧 **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`.
|
|
26
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.
|
package/dist/.build-id
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
|
|
1
|
+
3b23a9b-mtnx2lfo
|
|
@@ -241,6 +241,7 @@ const ignore_1 = require("../project/ignore");
|
|
|
241
241
|
// @implements A-SPEC-126
|
|
242
242
|
const scan_1 = require("../reverse/scan");
|
|
243
243
|
const draft_1 = require("../reverse/draft");
|
|
244
|
+
const adr_refs_1 = require("../spec/adr-refs");
|
|
244
245
|
const spec_id_guard_1 = require("./spec-id-guard");
|
|
245
246
|
const anchor_1 = require("../reverse/anchor");
|
|
246
247
|
// @implements A-SPEC-128
|
|
@@ -693,10 +694,10 @@ function collectDecisions(root, scanned, specs) {
|
|
|
693
694
|
return { decisions: [], citations: [] };
|
|
694
695
|
}
|
|
695
696
|
const ids = new Set(decisions.map((d) => d.id));
|
|
697
|
+
// @implements A-SPEC-546.1 — recognise ADR-\d{3,} (4-digit ADRs no longer invisible), via a pure fn.
|
|
696
698
|
const cite = (text, from, out) => {
|
|
697
|
-
for (const
|
|
698
|
-
|
|
699
|
-
out.push({ from, adr: m[0] });
|
|
699
|
+
for (const adr of (0, adr_refs_1.adrRefsIn)(text, ids))
|
|
700
|
+
out.push({ from, adr });
|
|
700
701
|
};
|
|
701
702
|
const citations = [];
|
|
702
703
|
for (const f of scanned) {
|
|
@@ -3248,13 +3249,10 @@ function makeRawHandlers(store, opts) {
|
|
|
3248
3249
|
const root = a.root ? (0, root_2.resolveProjectRoot)(a.root).root : process.cwd();
|
|
3249
3250
|
const specsDir = path.join(root, '.ax', 'specs');
|
|
3250
3251
|
const existing = await store.list();
|
|
3251
|
-
|
|
3252
|
-
|
|
3253
|
-
|
|
3254
|
-
|
|
3255
|
-
maxId = num;
|
|
3256
|
-
}
|
|
3257
|
-
const nextId = maxId + 1;
|
|
3252
|
+
// @implements A-SPEC-546.1 — align with the sibling reverse-draft path (nextIdBase, floor 100)
|
|
3253
|
+
// instead of a hard-coded 200 floor, so a fresh project starts at REQ-100, not REQ-201. Existing
|
|
3254
|
+
// projects are unchanged: nextIdBase returns max(existing)+1, which dominates the floor.
|
|
3255
|
+
const nextId = (0, draft_1.nextIdBase)(existing.map((s) => s.id));
|
|
3258
3256
|
const reqId = `REQ-${nextId}`;
|
|
3259
3257
|
const hspecId = `H-SPEC-${nextId}`;
|
|
3260
3258
|
const aspecId = `A-SPEC-${nextId}`;
|
|
@@ -43,3 +43,27 @@ export declare function buildCfgView(cfg: Cfg | {
|
|
|
43
43
|
}, ast: PersistedAst, source: string): CfgView | {
|
|
44
44
|
unsupported: string;
|
|
45
45
|
};
|
|
46
|
+
export declare const CFG_COL_W = 220;
|
|
47
|
+
export declare const CFG_ROW_H = 120;
|
|
48
|
+
export interface CfgLayoutNode {
|
|
49
|
+
id: number;
|
|
50
|
+
layer: number;
|
|
51
|
+
col: number;
|
|
52
|
+
x: number;
|
|
53
|
+
y: number;
|
|
54
|
+
line: number;
|
|
55
|
+
label: string;
|
|
56
|
+
unreachable: boolean;
|
|
57
|
+
}
|
|
58
|
+
export interface CfgLayout {
|
|
59
|
+
nodes: CfgLayoutNode[];
|
|
60
|
+
width: number;
|
|
61
|
+
height: number;
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* @implements A-SPEC-545.5
|
|
65
|
+
* Pure & deterministic layered layout of a CFG: layer = BFS shortest depth from entry (cycle-safe,
|
|
66
|
+
* blocks visited in id order); unreachable blocks trail after the reachable ones. col = order within
|
|
67
|
+
* a layer. Same input ⇒ same layout.
|
|
68
|
+
*/
|
|
69
|
+
export declare function layoutCfg(view: CfgView): CfgLayout;
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.CFG_ROW_H = exports.CFG_COL_W = void 0;
|
|
3
4
|
exports.lineAtByte = lineAtByte;
|
|
4
5
|
exports.buildCfgView = buildCfgView;
|
|
6
|
+
exports.layoutCfg = layoutCfg;
|
|
5
7
|
/**
|
|
6
8
|
* @implements A-SPEC-545.4
|
|
7
9
|
* Pure: 1-based line number of a byte offset (count of newlines in [0, offset) + 1). Clamped.
|
|
@@ -55,3 +57,51 @@ function buildCfgView(cfg, pdg, ast, source) {
|
|
|
55
57
|
}
|
|
56
58
|
return { entry: cfg.entry, exit: cfg.exit, blocks, edges, ctrlDeps, dataDeps };
|
|
57
59
|
}
|
|
60
|
+
exports.CFG_COL_W = 220;
|
|
61
|
+
exports.CFG_ROW_H = 120;
|
|
62
|
+
/**
|
|
63
|
+
* @implements A-SPEC-545.5
|
|
64
|
+
* Pure & deterministic layered layout of a CFG: layer = BFS shortest depth from entry (cycle-safe,
|
|
65
|
+
* blocks visited in id order); unreachable blocks trail after the reachable ones. col = order within
|
|
66
|
+
* a layer. Same input ⇒ same layout.
|
|
67
|
+
*/
|
|
68
|
+
function layoutCfg(view) {
|
|
69
|
+
if (view.blocks.length === 0)
|
|
70
|
+
return { nodes: [], width: 0, height: 0 };
|
|
71
|
+
const succ = new Map();
|
|
72
|
+
for (const e of view.edges)
|
|
73
|
+
succ.set(e.from, [...(succ.get(e.from) ?? []), e.to]);
|
|
74
|
+
const layer = new Map();
|
|
75
|
+
const start = view.blocks.some((b) => b.id === view.entry) ? view.entry : Math.min(...view.blocks.map((b) => b.id));
|
|
76
|
+
let queue = [start];
|
|
77
|
+
layer.set(start, 0);
|
|
78
|
+
while (queue.length) {
|
|
79
|
+
const next = [];
|
|
80
|
+
for (const id of queue) {
|
|
81
|
+
const depth = layer.get(id);
|
|
82
|
+
for (const to of (succ.get(id) ?? []).slice().sort((a, b) => a - b)) {
|
|
83
|
+
if (!layer.has(to)) {
|
|
84
|
+
layer.set(to, depth + 1);
|
|
85
|
+
next.push(to);
|
|
86
|
+
} // BFS: cycle-safe (visit once)
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
queue = next;
|
|
90
|
+
}
|
|
91
|
+
const reachMax = layer.size ? Math.max(...layer.values()) : 0;
|
|
92
|
+
// Unreachable blocks trail after every reachable block, in id order.
|
|
93
|
+
const unreached = view.blocks.filter((b) => !layer.has(b.id)).sort((a, b) => a.id - b.id);
|
|
94
|
+
unreached.forEach((b, i) => layer.set(b.id, reachMax + 1 + i));
|
|
95
|
+
// col = order within a layer, by ascending block id (deterministic).
|
|
96
|
+
const perLayer = new Map();
|
|
97
|
+
const byId = [...view.blocks].sort((a, b) => a.id - b.id);
|
|
98
|
+
const nodes = byId.map((b) => {
|
|
99
|
+
const ly = layer.get(b.id);
|
|
100
|
+
const col = perLayer.get(ly) ?? 0;
|
|
101
|
+
perLayer.set(ly, col + 1);
|
|
102
|
+
return { id: b.id, layer: ly, col, x: col * exports.CFG_COL_W, y: ly * exports.CFG_ROW_H, line: b.line, label: b.label, unreachable: b.unreachable };
|
|
103
|
+
});
|
|
104
|
+
const maxCol = nodes.reduce((m, n) => Math.max(m, n.col), 0);
|
|
105
|
+
const maxLayer = nodes.reduce((m, n) => Math.max(m, n.layer), 0);
|
|
106
|
+
return { nodes, width: (maxCol + 1) * exports.CFG_COL_W, height: (maxLayer + 1) * exports.CFG_ROW_H };
|
|
107
|
+
}
|
|
@@ -271,8 +271,10 @@ async function startDashboardServer(options) {
|
|
|
271
271
|
res.end(JSON.stringify({ ok: true, file, symbol, lang, unsupported: view.unsupported }));
|
|
272
272
|
return;
|
|
273
273
|
}
|
|
274
|
+
// @implements A-SPEC-545.5 — server-computed layered layout so the client only paints (no
|
|
275
|
+
// duplicated layout logic to drift from the tested one).
|
|
274
276
|
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
275
|
-
res.end(JSON.stringify({ ok: true, file, symbol, lang, cfg: view }));
|
|
277
|
+
res.end(JSON.stringify({ ok: true, file, symbol, lang, cfg: view, layout: (0, cfg_view_1.layoutCfg)(view) }));
|
|
276
278
|
}
|
|
277
279
|
catch (err) {
|
|
278
280
|
if (!res.headersSent) {
|
|
@@ -654,6 +656,20 @@ function renderDashboardHtml() {
|
|
|
654
656
|
.tool-btn { background: #1e293b; color: #f8fafc; border: 1px solid #334155; width: 38px; height: 38px; border-radius: 6px; font-weight: bold; font-size: 14px; cursor: pointer; display: flex; align-items: center; justify-content: center; transition: all 0.15s ease; }
|
|
655
657
|
.tool-btn:hover { background: #3b82f6; border-color: #3b82f6; color: #fff; transform: scale(1.05); }
|
|
656
658
|
.ux-hint-banner { background: #1e293b; border-left: 4px solid #ec4899; color: #cbd5e1; padding: 10px 14px; border-radius: 6px; font-size: 12px; margin-bottom: 12px; display: flex; align-items: center; justify-content: space-between; }
|
|
659
|
+
|
|
660
|
+
/* @implements A-SPEC-545.5 — CFG/PDG drilldown: control-flow blocks + program-dependence overlays */
|
|
661
|
+
.cfg-block rect { fill: var(--surface-2); stroke: var(--border); stroke-width: 1.5; rx: 8; }
|
|
662
|
+
.cfg-block.entry rect { stroke: var(--ok); stroke-width: 2.5; }
|
|
663
|
+
.cfg-block.exitb rect { stroke: var(--accent); stroke-width: 2.5; }
|
|
664
|
+
.cfg-block.unreachable rect { opacity: 0.4; stroke-dasharray: 4 3; }
|
|
665
|
+
.cfg-block .cfg-line { font-family: var(--font-mono); font-size: 10px; fill: var(--ink-2); }
|
|
666
|
+
.cfg-block .cfg-label { font-family: var(--font-mono); font-size: 12px; fill: var(--ink-0); }
|
|
667
|
+
.cfg-edge-flow { stroke: var(--ink-1); stroke-width: 2; fill: none; marker-end: url(#cfg-arrow-flow); }
|
|
668
|
+
.cfg-edge-data { stroke: var(--accent); stroke-width: 2; fill: none; opacity: 0.85; marker-end: url(#cfg-arrow-data); }
|
|
669
|
+
.cfg-edge-ctrl { stroke: var(--warn); stroke-width: 2; fill: none; stroke-dasharray: 6 4; opacity: 0.85; marker-end: url(#cfg-arrow-ctrl); }
|
|
670
|
+
.cfg-legend { display: flex; gap: 18px; align-items: center; font-family: var(--font-ui); font-size: 12px; color: var(--ink-1); flex-wrap: wrap; }
|
|
671
|
+
.cfg-legend .swatch { display: inline-block; width: 22px; height: 0; border-top-width: 2px; border-top-style: solid; vertical-align: middle; margin-right: 6px; }
|
|
672
|
+
.cfg-banner { font-family: var(--font-ui); color: var(--ink-1); background: var(--surface-1); border: 1px solid var(--border); border-radius: 10px; padding: 16px 18px; }
|
|
657
673
|
</style>
|
|
658
674
|
</head>
|
|
659
675
|
<body>
|
|
@@ -1168,12 +1184,84 @@ function renderDashboardHtml() {
|
|
|
1168
1184
|
|
|
1169
1185
|
async function focusSpecInGraph(specId) {
|
|
1170
1186
|
switchTab('graph');
|
|
1187
|
+
// @implements A-SPEC-545.5 — a symbol id (file#name) drills into that function's CFG/PDG;
|
|
1188
|
+
// every other id keeps the existing spec-graph traversal untouched.
|
|
1189
|
+
if (typeof specId === 'string' && specId.indexOf('#') !== -1) {
|
|
1190
|
+
const hash = specId.indexOf('#');
|
|
1191
|
+
const file = specId.slice(0, hash);
|
|
1192
|
+
const symbol = specId.slice(hash + 1);
|
|
1193
|
+
try {
|
|
1194
|
+
const res = await fetch('/api/cfg?file=' + encodeURIComponent(file) + '&symbol=' + encodeURIComponent(symbol));
|
|
1195
|
+
renderCfgDrilldown(await res.json(), file, symbol);
|
|
1196
|
+
} catch (err) {
|
|
1197
|
+
document.getElementById('graphScopeNotice').textContent = 'Failed to load CFG: ' + (err && err.message || err);
|
|
1198
|
+
}
|
|
1199
|
+
return;
|
|
1200
|
+
}
|
|
1171
1201
|
if (!graphData) {
|
|
1172
1202
|
await loadGraphCanvas();
|
|
1173
1203
|
}
|
|
1174
1204
|
selectAndTraverseNode(specId);
|
|
1175
1205
|
}
|
|
1176
1206
|
|
|
1207
|
+
// @implements A-SPEC-545.5 — paint the server-computed layered CFG + PDG overlays into the canvas.
|
|
1208
|
+
function renderCfgDrilldown(resp, file, symbol) {
|
|
1209
|
+
const svg = document.getElementById('graphSvg');
|
|
1210
|
+
const notice = document.getElementById('graphScopeNotice');
|
|
1211
|
+
if (!resp || resp.ok === false) {
|
|
1212
|
+
svg.innerHTML = '';
|
|
1213
|
+
notice.innerHTML = '<div class="cfg-banner">No CFG for <strong>' + escapeHtml(symbol) + '</strong> in ' + escapeHtml(file) + (resp && resp.reason ? ' — ' + escapeHtml(resp.reason) : '') + '</div>';
|
|
1214
|
+
return;
|
|
1215
|
+
}
|
|
1216
|
+
if (resp.unsupported) {
|
|
1217
|
+
svg.innerHTML = '';
|
|
1218
|
+
notice.innerHTML = '<div class="cfg-banner">CFG is not built for <strong>' + escapeHtml(resp.unsupported) + '</strong> — this view is honest about the languages it cannot analyse.</div>';
|
|
1219
|
+
return;
|
|
1220
|
+
}
|
|
1221
|
+
const cfg = resp.cfg, layout = resp.layout;
|
|
1222
|
+
const pos = {};
|
|
1223
|
+
layout.nodes.forEach(n => { pos[n.id] = n; });
|
|
1224
|
+
const CW = 180, CH = 60;
|
|
1225
|
+
const cx = (id) => pos[id] ? pos[id].x + CW / 2 : 0;
|
|
1226
|
+
const top = (id) => pos[id] ? pos[id].y : 0;
|
|
1227
|
+
const bot = (id) => pos[id] ? pos[id].y + CH : 0;
|
|
1228
|
+
const line = (x1, y1, x2, y2, cls) => '<path class="' + cls + '" d="M' + x1 + ' ' + y1 + ' L' + x2 + ' ' + y2 + '"></path>';
|
|
1229
|
+
|
|
1230
|
+
let edges = '';
|
|
1231
|
+
(cfg.edges || []).forEach(e => { edges += line(cx(e.from), bot(e.from), cx(e.to), top(e.to), 'cfg-edge-flow'); });
|
|
1232
|
+
(cfg.dataDeps || []).forEach(d => { edges += line(cx(d.from) + 12, top(d.from), cx(d.to) + 12, bot(d.to), 'cfg-edge-data'); });
|
|
1233
|
+
(cfg.ctrlDeps || []).forEach(c => { edges += line(cx(c.ctrl) - 12, top(c.ctrl), cx(c.dep) - 12, bot(c.dep), 'cfg-edge-ctrl'); });
|
|
1234
|
+
|
|
1235
|
+
let blocks = '';
|
|
1236
|
+
layout.nodes.forEach(n => {
|
|
1237
|
+
const cls = 'cfg-block' + (n.id === cfg.entry ? ' entry' : '') + (n.id === cfg.exit ? ' exitb' : '') + (n.unreachable ? ' unreachable' : '');
|
|
1238
|
+
const lbl = n.label ? escapeHtml(n.label) : (n.id === cfg.entry ? 'ENTRY' : n.id === cfg.exit ? 'EXIT' : 'B' + n.id);
|
|
1239
|
+
blocks += '<g class="' + cls + '" transform="translate(' + n.x + ',' + n.y + ')">'
|
|
1240
|
+
+ '<rect width="' + CW + '" height="' + CH + '" rx="8"></rect>'
|
|
1241
|
+
+ '<text class="cfg-line" x="10" y="18">' + (n.line ? ('L' + n.line) : ('#' + n.id)) + '</text>'
|
|
1242
|
+
+ '<text class="cfg-label" x="10" y="38">' + lbl.slice(0, 26) + '</text>'
|
|
1243
|
+
+ '</g>';
|
|
1244
|
+
});
|
|
1245
|
+
|
|
1246
|
+
const defs = '<defs>'
|
|
1247
|
+
+ '<marker id="cfg-arrow-flow" markerWidth="8" markerHeight="8" refX="7" refY="3" orient="auto"><path d="M0 0 L7 3 L0 6 z" fill="var(--ink-1)"></path></marker>'
|
|
1248
|
+
+ '<marker id="cfg-arrow-data" markerWidth="8" markerHeight="8" refX="7" refY="3" orient="auto"><path d="M0 0 L7 3 L0 6 z" fill="var(--accent)"></path></marker>'
|
|
1249
|
+
+ '<marker id="cfg-arrow-ctrl" markerWidth="8" markerHeight="8" refX="7" refY="3" orient="auto"><path d="M0 0 L7 3 L0 6 z" fill="var(--warn)"></path></marker>'
|
|
1250
|
+
+ '</defs>';
|
|
1251
|
+
const W = Math.max(layout.width, CW) + 40, H = Math.max(layout.height, CH) + 40;
|
|
1252
|
+
svg.setAttribute('viewBox', '-20 -20 ' + W + ' ' + H);
|
|
1253
|
+
svg.innerHTML = defs + edges + blocks;
|
|
1254
|
+
|
|
1255
|
+
notice.innerHTML = '<div style="display:flex; justify-content:space-between; align-items:center; gap:16px; flex-wrap:wrap;">'
|
|
1256
|
+
+ '<span style="font-family:var(--font-ui); color:var(--ink-0); font-weight:700;">CFG · ' + escapeHtml(symbol) + '() <span style="color:var(--ink-2); font-weight:400;">' + escapeHtml(file) + '</span></span>'
|
|
1257
|
+
+ '<span class="cfg-legend">'
|
|
1258
|
+
+ '<span><span class="swatch" style="border-top-color:var(--ink-1);"></span>control flow</span>'
|
|
1259
|
+
+ '<span><span class="swatch" style="border-top-color:var(--accent);"></span>data dep</span>'
|
|
1260
|
+
+ '<span><span class="swatch" style="border-top-color:var(--warn); border-top-style:dashed;"></span>control dep</span>'
|
|
1261
|
+
+ '<span style="color:var(--ink-2);">' + layout.nodes.length + ' blocks</span>'
|
|
1262
|
+
+ '</span></div>';
|
|
1263
|
+
}
|
|
1264
|
+
|
|
1177
1265
|
document.getElementById('searchInput').addEventListener('input', (e) => {
|
|
1178
1266
|
const q = e.target.value.toLowerCase();
|
|
1179
1267
|
const filtered = allSpecs.filter(s => s.id.toLowerCase().includes(q) || (s.title && s.title.toLowerCase().includes(q)));
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @implements A-SPEC-546.1
|
|
3
|
+
* Pure: the ADR references cited in `text` that exist in `known`, in appearance order (duplicates
|
|
4
|
+
* preserved, matching the prior cite behaviour). Recognises `ADR-` followed by 3-OR-MORE digits at a
|
|
5
|
+
* word boundary, so four-digit ADRs (ADR-1000+) are no longer invisible.
|
|
6
|
+
*/
|
|
7
|
+
export declare function adrRefsIn(text: string, known: ReadonlySet<string>): string[];
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// @implements A-SPEC-546.1
|
|
3
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
|
+
exports.adrRefsIn = adrRefsIn;
|
|
5
|
+
/**
|
|
6
|
+
* @implements A-SPEC-546.1
|
|
7
|
+
* Pure: the ADR references cited in `text` that exist in `known`, in appearance order (duplicates
|
|
8
|
+
* preserved, matching the prior cite behaviour). Recognises `ADR-` followed by 3-OR-MORE digits at a
|
|
9
|
+
* word boundary, so four-digit ADRs (ADR-1000+) are no longer invisible.
|
|
10
|
+
*/
|
|
11
|
+
function adrRefsIn(text, known) {
|
|
12
|
+
const out = [];
|
|
13
|
+
for (const m of text.matchAll(/\bADR-\d{3,}\b/g))
|
|
14
|
+
if (known.has(m[0]))
|
|
15
|
+
out.push(m[0]);
|
|
16
|
+
return out;
|
|
17
|
+
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"//": "@implements A-SPEC-209",
|
|
3
3
|
"name": "@holmes-lab/holmes-kit",
|
|
4
|
-
"version": "0.12.
|
|
4
|
+
"version": "0.12.1",
|
|
5
5
|
"description": "Holmes-Kit — deterministic Agentic Software Engineering (ASE) harness with causal traceability (spec chain + D-CPG + RTM + phase guardrail)",
|
|
6
6
|
"main": "dist/holmes/mcp/server.js",
|
|
7
7
|
"types": "dist/holmes/mcp/server.d.ts",
|
|
@@ -35,6 +35,9 @@
|
|
|
35
35
|
"spec-driven",
|
|
36
36
|
"rtm",
|
|
37
37
|
"cpg",
|
|
38
|
+
"cfg",
|
|
39
|
+
"pdg",
|
|
40
|
+
"dashboard",
|
|
38
41
|
"traceability",
|
|
39
42
|
"mcp",
|
|
40
43
|
"guardrail"
|