onto-mcp 0.4.11 → 0.4.13
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/.onto/authority/core-lexicon.yaml +1 -1
- package/.onto/authority/supported-models.yaml +153 -0
- package/.onto/domains/accounting-kr/competency_qs.md +153 -153
- package/.onto/processes/reconstruct/obligation-coverage-ledger.yaml +842 -0
- package/.onto/processes/reconstruct/obligation-coverage-recorded.yaml +225 -0
- package/.onto/processes/reconstruct/ontology-seeding-and-maturation-design.md +93 -54
- package/.onto/processes/reconstruct/reconstruct-contract-registry.yaml +202 -49
- package/.onto/processes/reconstruct/reconstruct-execution-ux-contract.md +72 -1
- package/.onto/processes/reconstruct/source-profiles/spreadsheet.md +35 -0
- package/.onto/processes/review/nesting-batch-worker-contract.md +1 -1
- package/.onto/processes/review/productized-live-path.md +9 -5
- package/.onto/processes/review/prompt-execution-runner-contract.md +32 -8
- package/.onto/processes/review/review-target-profile-contract.md +47 -4
- package/.onto/processes/shared/pipeline-execution-ledger-contract.md +11 -0
- package/.onto/processes/shared/target-material-kind-contract.md +1 -1
- package/README.md +64 -1
- package/dist/cli.js +11 -1
- package/dist/core-api/reconstruct-api.js +679 -18
- package/dist/core-api/review-api.js +70 -5
- package/dist/core-api/review-progress.js +7 -0
- package/dist/core-api/runtime-observation.js +7 -0
- package/dist/core-runtime/artifact-io.js +131 -0
- package/dist/core-runtime/cli/claude-code-review-unit-executor.js +21 -9
- package/dist/core-runtime/cli/claude-nesting-batch-worker.js +4 -2
- package/dist/core-runtime/cli/materialize-review-prompt-packets.js +68 -14
- package/dist/core-runtime/cli/prepare-review-session.js +34 -1
- package/dist/core-runtime/cli/render-review-final-output.js +2 -1
- package/dist/core-runtime/cli/run-review-prompt-execution.js +758 -48
- package/dist/core-runtime/cli/run-seats.js +18 -0
- package/dist/core-runtime/cli/runtime-submit-context.js +10 -2
- package/dist/core-runtime/cli/unit-resubmit.js +269 -0
- package/dist/core-runtime/cli/worker-structured-output.js +80 -2
- package/dist/core-runtime/discovery/host-detection.js +71 -22
- package/dist/core-runtime/discovery/review-cert-assemble.js +208 -0
- package/dist/core-runtime/discovery/review-cert-record.js +442 -0
- package/dist/core-runtime/discovery/review-execution-units.js +22 -0
- package/dist/core-runtime/discovery/seat-inventory.js +167 -0
- package/dist/core-runtime/discovery/settings-chain.js +311 -52
- package/dist/core-runtime/discovery/supported-models.js +289 -25
- package/dist/core-runtime/discovery/synthesize-cert-assemble.js +197 -0
- package/dist/core-runtime/discovery/synthesize-cert-capsule.js +399 -0
- package/dist/core-runtime/discovery/synthesize-cert-judge.js +340 -0
- package/dist/core-runtime/discovery/synthesize-cert-loop.js +234 -0
- package/dist/core-runtime/discovery/synthesize-cert-mutation.js +117 -0
- package/dist/core-runtime/discovery/synthesize-cert-packet.js +182 -0
- package/dist/core-runtime/discovery/synthesize-cert-record.js +831 -0
- package/dist/core-runtime/discovery/synthesize-cert-sampler.js +336 -0
- package/dist/core-runtime/effort-calibration-ingest.js +326 -0
- package/dist/core-runtime/effort-calibration-reconstruct.js +47 -0
- package/dist/core-runtime/effort-calibration-review.js +22 -0
- package/dist/core-runtime/effort-calibration-sweep.js +110 -0
- package/dist/core-runtime/effort-frontier.js +134 -0
- package/dist/core-runtime/llm/claude-bin.js +77 -0
- package/dist/core-runtime/llm/dispatch-breaker.js +412 -0
- package/dist/core-runtime/llm/dispatch-fallback-adapter-capabilities.js +33 -0
- package/dist/core-runtime/llm/llm-caller.js +316 -35
- package/dist/core-runtime/llm/mock-llm-realization.js +35 -4
- package/dist/core-runtime/llm/model-switcher.js +1 -1
- package/dist/core-runtime/llm/openai-responses-incomplete-error.js +46 -0
- package/dist/core-runtime/llm/sealed-dispatch-capability.js +359 -0
- package/dist/core-runtime/llm/structured-dispatch-error.js +15 -0
- package/dist/core-runtime/onboard/claude-profile-scan.js +65 -0
- package/dist/core-runtime/onboard/cli-host.js +5 -2
- package/dist/core-runtime/onboard/configure-provider.js +33 -1
- package/dist/core-runtime/onboard/host-target.js +28 -3
- package/dist/core-runtime/onboard/register.js +54 -12
- package/dist/core-runtime/pipeline-execution-ledger.js +10 -0
- package/dist/core-runtime/reconstruct/artifact-types.js +37 -0
- package/dist/core-runtime/reconstruct/benchmark-evidence.js +16 -0
- package/dist/core-runtime/reconstruct/claim-projection-validation.js +19 -5
- package/dist/core-runtime/reconstruct/competency-projection-contract.js +63 -0
- package/dist/core-runtime/reconstruct/comprehension-artifact.js +321 -0
- package/dist/core-runtime/reconstruct/comprehension-reduce.js +367 -0
- package/dist/core-runtime/reconstruct/comprehension-semantic-map.js +771 -0
- package/dist/core-runtime/reconstruct/contract-registry.js +20 -0
- package/dist/core-runtime/reconstruct/directive-validation.js +4 -3
- package/dist/core-runtime/reconstruct/dispatch-fallback-artifacts.js +640 -0
- package/dist/core-runtime/reconstruct/execution-telemetry.js +84 -9
- package/dist/core-runtime/reconstruct/final-output-sections.js +89 -0
- package/dist/core-runtime/reconstruct/governing-snapshot.js +36 -1
- package/dist/core-runtime/reconstruct/leaf-reader.js +305 -0
- package/dist/core-runtime/reconstruct/llm-dispatch-failure.js +270 -0
- package/dist/core-runtime/reconstruct/llm-touch-fingerprint.js +94 -0
- package/dist/core-runtime/reconstruct/material-admission-validation.js +20 -5
- package/dist/core-runtime/reconstruct/material-profile-validation.js +24 -6
- package/dist/core-runtime/reconstruct/materialize-preparation.js +380 -23
- package/dist/core-runtime/reconstruct/maturation-validation.js +1522 -67
- package/dist/core-runtime/reconstruct/mock-llm-realization.js +120 -1
- package/dist/core-runtime/reconstruct/obligation-assertion.js +18 -0
- package/dist/core-runtime/reconstruct/ontology-seed-validation.js +39 -7
- package/dist/core-runtime/reconstruct/output-budget.js +6 -0
- package/dist/core-runtime/reconstruct/pipeline-execution-ledger.js +112 -5
- package/dist/core-runtime/reconstruct/post-seed-validation.js +122 -11
- package/dist/core-runtime/reconstruct/proof-authority-validation.js +2 -6
- package/dist/core-runtime/reconstruct/purpose-authority-validation.js +119 -7
- package/dist/core-runtime/reconstruct/record.js +49 -3
- package/dist/core-runtime/reconstruct/registry-verification-validation.js +41 -5
- package/dist/core-runtime/reconstruct/run-control-validation.js +1019 -37
- package/dist/core-runtime/reconstruct/run.js +5409 -517
- package/dist/core-runtime/reconstruct/seed-authoring-readiness-validation.js +56 -8
- package/dist/core-runtime/reconstruct/semantic-quality-gate.js +112 -7
- package/dist/core-runtime/reconstruct/source-observation-delta-validation.js +53 -5
- package/dist/core-runtime/reconstruct/source-observations.js +166 -0
- package/dist/core-runtime/reconstruct/source-safety-validation.js +77 -188
- package/dist/core-runtime/reconstruct/source-scout-pack-validation.js +50 -6
- package/dist/core-runtime/reconstruct/terminal-validation.js +145 -13
- package/dist/core-runtime/review/continuation-plan.js +12 -5
- package/dist/core-runtime/review/issue-artifact-runtime.js +4 -1
- package/dist/core-runtime/review/materializers.js +100 -11
- package/dist/core-runtime/review/nesting-batch.js +6 -2
- package/dist/core-runtime/review/obligation-shardability.js +138 -0
- package/dist/core-runtime/review/pipeline-execution-ledger.js +42 -2
- package/dist/core-runtime/review/review-artifact-utils.js +219 -7
- package/dist/core-runtime/review/review-execution-steps.js +32 -8
- package/dist/core-runtime/review/review-progress-contract.js +8 -0
- package/dist/core-runtime/review/review-prompt-budget.js +97 -0
- package/dist/core-runtime/review/semantic-quality-gate.js +33 -7
- package/dist/core-runtime/review/spreadsheet-review-disposition.js +94 -0
- package/dist/core-runtime/route-identity.js +182 -0
- package/dist/core-runtime/spreadsheet-header-escalation.js +204 -0
- package/dist/core-runtime/spreadsheet-structure-observer.js +2689 -0
- package/dist/core-runtime/target-material-kind.js +58 -0
- package/dist/mcp/server.js +27 -5
- package/dist/mcp/tool-schemas.js +8 -0
- package/dist/tui/app.js +167 -0
- package/dist/tui/data/event-follower.js +116 -0
- package/dist/tui/data/node-detail.js +42 -0
- package/dist/tui/data/projection-poll.js +26 -0
- package/dist/tui/data/session-discovery.js +65 -0
- package/dist/tui/index.js +108 -0
- package/dist/tui/screens/log.js +25 -0
- package/dist/tui/screens/node-detail.js +17 -0
- package/dist/tui/screens/session-selector.js +26 -0
- package/dist/tui/screens/workflow-tree.js +90 -0
- package/dist/tui/view-model/reconstruct-adapter.js +162 -0
- package/dist/tui/view-model/review-adapter.js +270 -0
- package/dist/tui/view-model/tree-view-model.js +19 -0
- package/package.json +20 -3
- package/settings.example.json +77 -16
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `onto watch [session]` entry. Resolves a session (by id substring, by path, or
|
|
3
|
+
* most-recent when omitted) and mounts the Ink app. v1 is observe-only; the
|
|
4
|
+
* SessionSelector screen (no-arg browsing) and the Log screen arrive in Stage C.
|
|
5
|
+
*/
|
|
6
|
+
import fs from "node:fs";
|
|
7
|
+
import path from "node:path";
|
|
8
|
+
import { createElement } from "react";
|
|
9
|
+
import { render } from "ink";
|
|
10
|
+
import { WatchApp } from "./app.js";
|
|
11
|
+
import { discoverSessions } from "./data/session-discovery.js";
|
|
12
|
+
function parseArgs(argv) {
|
|
13
|
+
let session;
|
|
14
|
+
let projectRoot = process.cwd();
|
|
15
|
+
let ontoHome;
|
|
16
|
+
for (let index = 0; index < argv.length; index += 1) {
|
|
17
|
+
const arg = argv[index];
|
|
18
|
+
if (arg === "--project-root") {
|
|
19
|
+
const value = argv[++index];
|
|
20
|
+
if (value)
|
|
21
|
+
projectRoot = path.resolve(value);
|
|
22
|
+
}
|
|
23
|
+
else if (arg === "--onto-home") {
|
|
24
|
+
const value = argv[++index];
|
|
25
|
+
if (value)
|
|
26
|
+
ontoHome = path.resolve(value);
|
|
27
|
+
}
|
|
28
|
+
else if (!arg.startsWith("-") && session === undefined) {
|
|
29
|
+
session = arg;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
return {
|
|
33
|
+
...(session !== undefined ? { session } : {}),
|
|
34
|
+
projectRoot,
|
|
35
|
+
...(ontoHome !== undefined ? { ontoHome } : {}),
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
/** Derives a SessionRef from an explicit session-root path, when it resolves to
|
|
39
|
+
* a `.onto/{review,reconstruct}/<id>` directory. */
|
|
40
|
+
function refFromPath(sessionPath) {
|
|
41
|
+
const resolved = path.resolve(sessionPath);
|
|
42
|
+
if (!fs.existsSync(resolved))
|
|
43
|
+
return null;
|
|
44
|
+
const parts = resolved.split(path.sep);
|
|
45
|
+
const idx = parts.lastIndexOf(".onto");
|
|
46
|
+
const pipeline = parts[idx + 1];
|
|
47
|
+
if (pipeline !== "review" && pipeline !== "reconstruct")
|
|
48
|
+
return null;
|
|
49
|
+
const stat = (() => {
|
|
50
|
+
try {
|
|
51
|
+
return fs.statSync(resolved).mtimeMs;
|
|
52
|
+
}
|
|
53
|
+
catch {
|
|
54
|
+
return 0;
|
|
55
|
+
}
|
|
56
|
+
})();
|
|
57
|
+
return {
|
|
58
|
+
pipeline,
|
|
59
|
+
sessionId: parts[parts.length - 1] ?? resolved,
|
|
60
|
+
sessionRoot: resolved,
|
|
61
|
+
modifiedMs: stat,
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
async function resolveWatch(args) {
|
|
65
|
+
const sessions = await discoverSessions(args.projectRoot);
|
|
66
|
+
if (!args.session) {
|
|
67
|
+
// No arg → open the SessionSelector (error only when there is nothing to show).
|
|
68
|
+
if (sessions.length === 0) {
|
|
69
|
+
return { sessions, error: `no review/reconstruct sessions under ${args.projectRoot}/.onto` };
|
|
70
|
+
}
|
|
71
|
+
return { sessions };
|
|
72
|
+
}
|
|
73
|
+
const byPath = refFromPath(args.session);
|
|
74
|
+
if (byPath) {
|
|
75
|
+
return { sessions: sessions.length ? sessions : [byPath], initialSession: byPath };
|
|
76
|
+
}
|
|
77
|
+
const matches = sessions.filter((s) => s.sessionId.includes(args.session));
|
|
78
|
+
if (matches.length === 0) {
|
|
79
|
+
return { sessions, error: `no session matching "${args.session}"` };
|
|
80
|
+
}
|
|
81
|
+
if (matches.length > 1) {
|
|
82
|
+
// Ambiguous token — don't silently attach to the newest; ask for a specific id.
|
|
83
|
+
return {
|
|
84
|
+
sessions,
|
|
85
|
+
error: `"${args.session}" matches ${matches.length} sessions: ` +
|
|
86
|
+
`${matches.map((s) => s.sessionId).join(", ")}. Use a more specific id.`,
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
return { sessions, initialSession: matches[0] };
|
|
90
|
+
}
|
|
91
|
+
export async function runWatch(argv) {
|
|
92
|
+
const args = parseArgs(argv);
|
|
93
|
+
const { sessions, initialSession, error } = await resolveWatch(args);
|
|
94
|
+
if (error) {
|
|
95
|
+
process.stderr.write(`[onto watch] ${error}\n`);
|
|
96
|
+
return 1;
|
|
97
|
+
}
|
|
98
|
+
const instance = render(createElement(WatchApp, {
|
|
99
|
+
sessions,
|
|
100
|
+
...(initialSession ? { initialSession } : {}),
|
|
101
|
+
// projectRoot is for session discovery only; the read APIs resolve the
|
|
102
|
+
// onto-mcp install root themselves (or from an explicit --onto-home), so
|
|
103
|
+
// running `onto watch` from a non-install project does not break.
|
|
104
|
+
...(args.ontoHome ? { ontoHome: args.ontoHome } : {}),
|
|
105
|
+
}));
|
|
106
|
+
await instance.waitUntilExit();
|
|
107
|
+
return 0;
|
|
108
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
/**
|
|
3
|
+
* Log screen — a tail of a session's `runtime-events.ndjson` (fed by the
|
|
4
|
+
* event-follower in the app). Pure presentation: shows the most recent events
|
|
5
|
+
* with timestamp · source · stream · message. Read-only.
|
|
6
|
+
*/
|
|
7
|
+
import { Box, Text } from "ink";
|
|
8
|
+
function clockTime(timestamp) {
|
|
9
|
+
// Render only HH:MM:SS from an ISO timestamp without constructing a Date
|
|
10
|
+
// (avoids locale/clock surprises and keeps it test-stable).
|
|
11
|
+
const match = /T(\d{2}:\d{2}:\d{2})/.exec(timestamp);
|
|
12
|
+
return match ? match[1] : timestamp.slice(0, 8);
|
|
13
|
+
}
|
|
14
|
+
const STREAM_COLOR = {
|
|
15
|
+
model_call: "magenta",
|
|
16
|
+
status: undefined,
|
|
17
|
+
chunk: "gray",
|
|
18
|
+
};
|
|
19
|
+
export function LogScreen({ sessionId, events, maxLines = 18, }) {
|
|
20
|
+
const tail = events.slice(-maxLines);
|
|
21
|
+
return (_jsxs(Box, { flexDirection: "column", children: [_jsx(Text, { bold: true, children: `onto watch · ${sessionId} · log` }), _jsx(Box, { flexDirection: "column", marginTop: 1, children: tail.length === 0 ? (_jsx(Text, { dimColor: true, children: "no events yet" })) : (tail.map((event, index) => {
|
|
22
|
+
const sourceColor = STREAM_COLOR[event.stream];
|
|
23
|
+
return (_jsxs(Box, { children: [_jsx(Text, { dimColor: true, children: `${clockTime(event.timestamp)} ` }), _jsx(Text, { ...(sourceColor ? { color: sourceColor } : {}), children: `${(event.source.label ?? event.source.kind).padEnd(16)} ` }), _jsx(Text, { children: event.message })] }, index));
|
|
24
|
+
})) }), _jsx(Box, { marginTop: 1, children: _jsx(Text, { dimColor: true, children: "[tab] tree [q]uit" }) })] }));
|
|
25
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
/**
|
|
3
|
+
* Drill-down detail pane for the node selected in the WorkflowTree HUD — shows
|
|
4
|
+
* the node's status/owner/attempts/failure/output and a tail of its output (the
|
|
5
|
+
* running log / authored artifact). Pure presentation.
|
|
6
|
+
*/
|
|
7
|
+
import { Box, Text } from "ink";
|
|
8
|
+
export function NodeDetail({ node, tail }) {
|
|
9
|
+
const meta = [`status ${node.status}`];
|
|
10
|
+
if (node.owner)
|
|
11
|
+
meta.push(`owner ${node.owner}`);
|
|
12
|
+
if (node.attempts && node.attempts > 1)
|
|
13
|
+
meta.push(`try${node.attempts}`);
|
|
14
|
+
if (node.signalAgeSec != null)
|
|
15
|
+
meta.push(`${node.signalAgeSec}s`);
|
|
16
|
+
return (_jsxs(Box, { flexDirection: "column", marginTop: 1, borderStyle: "round", borderColor: "cyan", paddingX: 1, children: [_jsx(Text, { bold: true, children: node.label }), _jsx(Text, { dimColor: true, children: meta.join(" · ") }), node.failureMessage ? _jsx(Text, { color: "red", children: node.failureMessage }) : null, node.outputPath ? _jsx(Text, { dimColor: true, children: node.outputPath }) : null, tail.length > 0 ? (_jsxs(Box, { flexDirection: "column", marginTop: 1, children: [_jsx(Text, { dimColor: true, children: "\u2500\u2500 output (tail) \u2500\u2500" }), tail.map((line, index) => (_jsx(Text, { children: line }, index)))] })) : null] }));
|
|
17
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
/**
|
|
3
|
+
* SessionSelector — the `onto watch` entry screen when no session is given (or
|
|
4
|
+
* `s` from a session view). Lists review/reconstruct sessions (most recent
|
|
5
|
+
* first) with a cursor. Pure presentation: selection state is owned by the app.
|
|
6
|
+
*/
|
|
7
|
+
import { Box, Text } from "ink";
|
|
8
|
+
function relativeAge(modifiedMs, nowMs) {
|
|
9
|
+
if (!modifiedMs)
|
|
10
|
+
return "";
|
|
11
|
+
const sec = Math.max(0, Math.round((nowMs - modifiedMs) / 1000));
|
|
12
|
+
if (sec < 60)
|
|
13
|
+
return `${sec}s ago`;
|
|
14
|
+
if (sec < 3600)
|
|
15
|
+
return `${Math.round(sec / 60)}m ago`;
|
|
16
|
+
if (sec < 86400)
|
|
17
|
+
return `${Math.round(sec / 3600)}h ago`;
|
|
18
|
+
return `${Math.round(sec / 86400)}d ago`;
|
|
19
|
+
}
|
|
20
|
+
export function SessionSelector({ sessions, selectedIndex, nowMs, }) {
|
|
21
|
+
const now = nowMs ?? Date.now();
|
|
22
|
+
return (_jsxs(Box, { flexDirection: "column", children: [_jsx(Text, { bold: true, children: "onto watch \u00B7 sessions" }), sessions.length === 0 ? (_jsx(Text, { dimColor: true, children: "no review/reconstruct sessions found" })) : (_jsx(Box, { flexDirection: "column", marginTop: 1, children: sessions.map((session, index) => {
|
|
23
|
+
const active = index === selectedIndex;
|
|
24
|
+
return (_jsxs(Box, { children: [_jsx(Text, { ...(active ? { color: "cyan" } : {}), children: `${active ? "›" : " "} ${session.pipeline.padEnd(11)} ` }), _jsx(Text, { bold: active, children: session.sessionId }), _jsx(Text, { dimColor: true, children: ` ${relativeAge(session.modifiedMs, now)}` })] }, `${session.pipeline}:${session.sessionRoot}`));
|
|
25
|
+
}) })), _jsx(Box, { marginTop: 1, children: _jsx(Text, { dimColor: true, children: "[\u2191\u2193] select [enter] watch [q]uit" }) })] }));
|
|
26
|
+
}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
/**
|
|
3
|
+
* WorkflowTree HUD — the primary `onto watch` screen. Pure presentation: renders
|
|
4
|
+
* a {@link TreeViewModel} (header · narrator · phase-grouped unit tree · footer)
|
|
5
|
+
* for either pipeline. The HUD never branches on pipeline; the adapters already
|
|
6
|
+
* normalized the difference into the view model.
|
|
7
|
+
*/
|
|
8
|
+
import { Box, Text } from "ink";
|
|
9
|
+
const NODE_ICON = {
|
|
10
|
+
completed: "✓",
|
|
11
|
+
running: "◐",
|
|
12
|
+
pending: "○",
|
|
13
|
+
failed: "✗",
|
|
14
|
+
halted: "◑",
|
|
15
|
+
skipped: "⊘",
|
|
16
|
+
};
|
|
17
|
+
const NODE_COLOR = {
|
|
18
|
+
completed: "green",
|
|
19
|
+
running: "cyan",
|
|
20
|
+
pending: "gray",
|
|
21
|
+
failed: "red",
|
|
22
|
+
halted: "yellow",
|
|
23
|
+
skipped: "gray",
|
|
24
|
+
};
|
|
25
|
+
const STATUS_BADGE = {
|
|
26
|
+
running: { label: "◐ running", color: "cyan" },
|
|
27
|
+
completed: { label: "✓ completed", color: "green" },
|
|
28
|
+
halted: { label: "◑ halted", color: "yellow" },
|
|
29
|
+
failed: { label: "✗ failed", color: "red" },
|
|
30
|
+
};
|
|
31
|
+
const SEVERITY_ORDER = ["blocker", "high", "medium", "low", "info"];
|
|
32
|
+
const SEVERITY_ABBR = {
|
|
33
|
+
blocker: "blk",
|
|
34
|
+
high: "high",
|
|
35
|
+
medium: "med",
|
|
36
|
+
low: "low",
|
|
37
|
+
info: "info",
|
|
38
|
+
};
|
|
39
|
+
function NodeBadge({ node }) {
|
|
40
|
+
const parts = [];
|
|
41
|
+
if (node.owner)
|
|
42
|
+
parts.push(node.owner);
|
|
43
|
+
if (node.signalAgeSec != null)
|
|
44
|
+
parts.push(`${node.signalAgeSec}s`);
|
|
45
|
+
if (node.attempts && node.attempts > 1)
|
|
46
|
+
parts.push(`try${node.attempts}`);
|
|
47
|
+
if (node.failureMessage)
|
|
48
|
+
parts.push(node.failureMessage);
|
|
49
|
+
if (parts.length === 0)
|
|
50
|
+
return null;
|
|
51
|
+
return _jsx(Text, { dimColor: true, children: ` ${parts.join(" · ")}` });
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Surfaces the run-control availability the status projection carries (observe-
|
|
55
|
+
* only: it reports what the operator can do via the MCP/CLI, the HUD never acts).
|
|
56
|
+
* Most relevant for halted/stale sessions where continue is the next step.
|
|
57
|
+
*/
|
|
58
|
+
function RunControlRow({ control }) {
|
|
59
|
+
const available = [];
|
|
60
|
+
if (control.continuable)
|
|
61
|
+
available.push("continue");
|
|
62
|
+
if (control.advanceable)
|
|
63
|
+
available.push("advance");
|
|
64
|
+
if (control.cancellable)
|
|
65
|
+
available.push("cancel");
|
|
66
|
+
if (available.length === 0)
|
|
67
|
+
return null;
|
|
68
|
+
return _jsx(Text, { dimColor: true, children: `Controls ${available.join(" · ")} available` });
|
|
69
|
+
}
|
|
70
|
+
function Footer({ vm }) {
|
|
71
|
+
const { findings, counts } = vm.summary;
|
|
72
|
+
if (findings) {
|
|
73
|
+
return (_jsxs(Box, { flexDirection: "column", marginTop: 1, children: [_jsxs(Text, { children: ["Findings ", SEVERITY_ORDER.map((s) => `⬤${findings[s]} ${SEVERITY_ABBR[s]}`).join(" ")] }), findings.material.map((title, index) => (_jsx(Text, { dimColor: true, children: ` › ${title}` }, index)))] }));
|
|
74
|
+
}
|
|
75
|
+
if (counts) {
|
|
76
|
+
const pairs = Object.entries(counts)
|
|
77
|
+
.filter(([, value]) => value != null)
|
|
78
|
+
.map(([key, value]) => `${key} ${value}`);
|
|
79
|
+
return (_jsx(Box, { marginTop: 1, children: _jsxs(Text, { children: ["Coverage ", pairs.join(" · ")] }) }));
|
|
80
|
+
}
|
|
81
|
+
return _jsx(Box, { marginTop: 1 });
|
|
82
|
+
}
|
|
83
|
+
export function WorkflowTree({ vm, selectedNodeId }) {
|
|
84
|
+
const badge = STATUS_BADGE[vm.status];
|
|
85
|
+
const phasesDone = vm.phases.filter((p) => p.state === "completed").length;
|
|
86
|
+
return (_jsxs(Box, { flexDirection: "column", children: [_jsxs(Box, { children: [_jsx(Text, { bold: true, children: "onto watch" }), _jsx(Text, { children: ` · ${vm.pipeline} · ${vm.sessionId} ` }), _jsx(Text, { color: badge.color, children: badge.label })] }), vm.headline ? _jsx(Text, { dimColor: true, children: vm.headline }) : null, _jsx(Text, { children: `▸ ${vm.narrator}` }), _jsxs(Box, { marginTop: 1, children: [_jsx(Text, { children: `Pipeline ${phasesDone}/${vm.phases.length} phases` }), vm.liveness.secondsSinceSignal != null ? (_jsx(Text, { dimColor: true, children: ` · ${vm.liveness.secondsSinceSignal}s since signal` })) : null] }), _jsx(RunControlRow, { control: vm.runControl }), _jsx(Box, { flexDirection: "column", marginTop: 1, children: vm.phases.map((phase) => (_jsxs(Box, { flexDirection: "column", children: [_jsx(Text, { color: NODE_COLOR[phase.state], children: `${NODE_ICON[phase.state]} ${phase.label}` }), phase.nodes.map((node) => {
|
|
87
|
+
const selected = node.id === selectedNodeId;
|
|
88
|
+
return (_jsxs(Box, { children: [_jsx(Text, { color: NODE_COLOR[node.status], children: `${selected ? "›" : " "} ${NODE_ICON[node.status]} ` }), _jsx(Text, { bold: selected, children: node.label }), _jsx(NodeBadge, { node: node })] }, node.id));
|
|
89
|
+
})] }, phase.id))) }), _jsx(Footer, { vm: vm }), _jsx(Box, { marginTop: 1, children: _jsx(Text, { dimColor: true, children: "[q]uit [r]efresh" }) })] }));
|
|
90
|
+
}
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* v1 grouping: a single "pipeline" phase holds every reconstruct stage as a
|
|
3
|
+
* node, in canonical stage order (the projection already lists stages in
|
|
4
|
+
* `RECONSTRUCT_STAGE_IDS` order). This keeps the adapter simple and mirrors the
|
|
5
|
+
* design's "stages are the nodes" framing; finer maturation-stage grouping can
|
|
6
|
+
* be layered on later without changing the node mapping.
|
|
7
|
+
*/
|
|
8
|
+
const PIPELINE_PHASE_ID = "pipeline";
|
|
9
|
+
const PIPELINE_PHASE_LABEL = "reconstruct pipeline";
|
|
10
|
+
/**
|
|
11
|
+
* Map a reconstruct stage `state` to the shared {@link NodeState}. Reconstruct
|
|
12
|
+
* stage states are a strict subset (`pending | completed | skipped | halted`) —
|
|
13
|
+
* there is no per-stage `running` signal in the projection, so the in-flight
|
|
14
|
+
* stage is surfaced at the phase/headline level (see {@link derivePhaseState}),
|
|
15
|
+
* not on individual nodes.
|
|
16
|
+
*/
|
|
17
|
+
function stageStateToNodeState(state) {
|
|
18
|
+
switch (state) {
|
|
19
|
+
case "completed":
|
|
20
|
+
return "completed";
|
|
21
|
+
case "skipped":
|
|
22
|
+
return "skipped";
|
|
23
|
+
case "halted":
|
|
24
|
+
return "halted";
|
|
25
|
+
case "pending":
|
|
26
|
+
return "pending";
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
/** Human-ize a snake_case stageId for display, e.g. `ontology_seed` → `ontology seed`. */
|
|
30
|
+
function humanizeStageId(stageId) {
|
|
31
|
+
return stageId.replace(/_/g, " ");
|
|
32
|
+
}
|
|
33
|
+
function stageToNode(stage, hiddenArtifactRef) {
|
|
34
|
+
const outputPath = stage.artifactRefs.find((ref) => ref !== hiddenArtifactRef) ?? null;
|
|
35
|
+
return {
|
|
36
|
+
id: stage.stageId,
|
|
37
|
+
label: humanizeStageId(stage.stageId),
|
|
38
|
+
status: stageStateToNodeState(stage.state),
|
|
39
|
+
// reconstruct nodes badge by stageId (kind) + carry the owner column.
|
|
40
|
+
kind: stage.stageId,
|
|
41
|
+
// owner is optional under exactOptionalPropertyTypes: omit when absent.
|
|
42
|
+
...(stage.owner ? { owner: stage.owner } : {}),
|
|
43
|
+
// No per-stage live signal in the projection.
|
|
44
|
+
signalAgeSec: null,
|
|
45
|
+
// `reason` is the stage's terminal explanation (skip/halt rationale).
|
|
46
|
+
failureMessage: stage.reason,
|
|
47
|
+
// First authored artifact ref is the drill-down target.
|
|
48
|
+
outputPath,
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Phase state for the single pipeline phase. A halted stage halts the phase;
|
|
53
|
+
* otherwise a completed run completes the phase; otherwise it is running (the
|
|
54
|
+
* pipeline is mid-flight regardless of which individual stages are still
|
|
55
|
+
* pending).
|
|
56
|
+
*/
|
|
57
|
+
function derivePhaseState(stages, workflowStatus) {
|
|
58
|
+
if (stages.some((stage) => stage.state === "halted"))
|
|
59
|
+
return "halted";
|
|
60
|
+
if (workflowStatus === "completed")
|
|
61
|
+
return "completed";
|
|
62
|
+
return "running";
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Map record_stage + stage states to the shared {@link WorkflowStatus}.
|
|
66
|
+
* reconstruct has no `failed` record_stage and no per-stage `running`; we treat
|
|
67
|
+
* a halted stage as a halt (terminal-ish, surfaced for the operator) and a
|
|
68
|
+
* completed record as completed. Everything else is an in-flight run.
|
|
69
|
+
*/
|
|
70
|
+
function deriveWorkflowStatus(status) {
|
|
71
|
+
if (status.status === "failed")
|
|
72
|
+
return "failed";
|
|
73
|
+
if (status.status === "completed")
|
|
74
|
+
return "completed";
|
|
75
|
+
// A graceful terminal (design §16.7) is terminal but not completed: surface it as `halted` so
|
|
76
|
+
// the watch loop stops polling and the operator sees an honest stop, not a "session completed".
|
|
77
|
+
if (status.status === "blocked" || status.status === "limited")
|
|
78
|
+
return "halted";
|
|
79
|
+
if (status.progress.stages.some((stage) => stage.state === "halted")) {
|
|
80
|
+
return "halted";
|
|
81
|
+
}
|
|
82
|
+
return "running";
|
|
83
|
+
}
|
|
84
|
+
/** Flatten countSummary + answerabilitySummary into the footer label→value map. */
|
|
85
|
+
function deriveCounts(progress) {
|
|
86
|
+
const c = progress.countSummary;
|
|
87
|
+
const a = progress.answerabilitySummary;
|
|
88
|
+
return {
|
|
89
|
+
observations: c.selectedObservationCount ?? c.sourceObservationCount,
|
|
90
|
+
claims: c.semanticClaimCount,
|
|
91
|
+
confirmed: c.confirmedClaimCount,
|
|
92
|
+
deferredClaims: c.deferredClaimCount,
|
|
93
|
+
CQ: c.competencyQuestionCount,
|
|
94
|
+
passRate: c.passRate,
|
|
95
|
+
// Answerability (maturation) summary — null until metrics are computed.
|
|
96
|
+
declared: a ? a.declaredQuestionCount : null,
|
|
97
|
+
supported: a ? a.supportedQuestionCount : null,
|
|
98
|
+
deferred: a ? a.deferredQuestionCount : null,
|
|
99
|
+
unsupported: a ? a.unsupportedQuestionCount : null,
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
/** One-line narrator from the current stage + the most informative counts. */
|
|
103
|
+
function deriveNarrator(progress, workflowStatus) {
|
|
104
|
+
const stage = humanizeStageId(progress.currentStageId);
|
|
105
|
+
const a = progress.answerabilitySummary;
|
|
106
|
+
const lead = workflowStatus === "completed"
|
|
107
|
+
? "reconstruct complete"
|
|
108
|
+
: workflowStatus === "failed"
|
|
109
|
+
? `reconstruct failed at ${stage}`
|
|
110
|
+
: workflowStatus === "halted"
|
|
111
|
+
? `halted at ${stage}`
|
|
112
|
+
: `reconstructing — ${stage}`;
|
|
113
|
+
if (a) {
|
|
114
|
+
return `${lead} · CQ ${a.declaredQuestionCount} · supported ${a.supportedQuestionCount} · deferred ${a.deferredQuestionCount} · unsupported ${a.unsupportedQuestionCount}`;
|
|
115
|
+
}
|
|
116
|
+
const claims = progress.countSummary.semanticClaimCount;
|
|
117
|
+
return claims === null ? lead : `${lead} · ${claims} claims`;
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* Pure projection: {@link ReconstructSessionStatus} → {@link TreeViewModel}.
|
|
121
|
+
*
|
|
122
|
+
* `sessionRoot` is threaded explicitly (the caller already knows it) so the view
|
|
123
|
+
* model header is independent of how the status was fetched.
|
|
124
|
+
*/
|
|
125
|
+
export function reconstructStatusToTreeViewModel(status, sessionRoot) {
|
|
126
|
+
const { progress } = status;
|
|
127
|
+
const workflowStatus = deriveWorkflowStatus(status);
|
|
128
|
+
const hiddenArtifactRef = status.status === "failed"
|
|
129
|
+
? status.failure.failure_artifact_ref
|
|
130
|
+
: null;
|
|
131
|
+
const nodes = progress.stages.map((stage) => stageToNode(stage, hiddenArtifactRef));
|
|
132
|
+
const phase = {
|
|
133
|
+
id: PIPELINE_PHASE_ID,
|
|
134
|
+
label: PIPELINE_PHASE_LABEL,
|
|
135
|
+
state: derivePhaseState(progress.stages, workflowStatus),
|
|
136
|
+
nodes,
|
|
137
|
+
};
|
|
138
|
+
return {
|
|
139
|
+
pipeline: "reconstruct",
|
|
140
|
+
sessionId: status.sessionId,
|
|
141
|
+
sessionRoot,
|
|
142
|
+
status: workflowStatus,
|
|
143
|
+
narrator: deriveNarrator(progress, workflowStatus),
|
|
144
|
+
liveness: {
|
|
145
|
+
state: progress.liveness.state,
|
|
146
|
+
// The projection carries no per-signal timestamp for reconstruct.
|
|
147
|
+
secondsSinceSignal: null,
|
|
148
|
+
pollMs: progress.liveness.recommendedPollIntervalMs,
|
|
149
|
+
},
|
|
150
|
+
phases: [phase],
|
|
151
|
+
summary: { counts: deriveCounts(progress) },
|
|
152
|
+
// reconstruct status carries no rich run_control (unlike review): be
|
|
153
|
+
// conservative — only a still-running session is cancellable; continue /
|
|
154
|
+
// advance are display-only false until a control path exists (design §4,
|
|
155
|
+
// later stage E).
|
|
156
|
+
runControl: {
|
|
157
|
+
cancellable: workflowStatus === "running",
|
|
158
|
+
continuable: false,
|
|
159
|
+
advanceable: false,
|
|
160
|
+
},
|
|
161
|
+
};
|
|
162
|
+
}
|