@holmes-lab/holmes-kit 0.19.5 → 0.20.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.
Files changed (128) hide show
  1. package/CHANGELOG.md +135 -0
  2. package/README.md +17 -1
  3. package/dist/.build-id +1 -1
  4. package/dist/holmes/cli/approve-status.d.ts +24 -0
  5. package/dist/holmes/cli/approve-status.js +166 -0
  6. package/dist/holmes/cli/approve-watch.d.ts +6 -0
  7. package/dist/holmes/cli/approve-watch.js +6 -0
  8. package/dist/holmes/cli/approve.d.ts +27 -0
  9. package/dist/holmes/cli/approve.js +68 -7
  10. package/dist/holmes/cli/cli-execution.d.ts +2 -0
  11. package/dist/holmes/cli/cli-execution.js +24 -0
  12. package/dist/holmes/cli/gitignore-merge.d.ts +0 -5
  13. package/dist/holmes/cli/gitignore-merge.js +6 -2
  14. package/dist/holmes/cli/index.js +176 -139
  15. package/dist/holmes/cli/playbook-skills.d.ts +2 -22
  16. package/dist/holmes/cli/playbook-skills.js +10 -54
  17. package/dist/holmes/governance/approval-grants.d.ts +22 -0
  18. package/dist/holmes/governance/approval-grants.js +55 -1
  19. package/dist/holmes/governance/approval-queue.d.ts +43 -4
  20. package/dist/holmes/governance/approval-queue.js +86 -12
  21. package/dist/holmes/governance/display-id-aliases.d.ts +29 -0
  22. package/dist/holmes/governance/display-id-aliases.js +86 -0
  23. package/dist/holmes/governance/ledger-store.d.ts +42 -14
  24. package/dist/holmes/governance/ledger-store.js +49 -3
  25. package/dist/holmes/governance/ledger-timeline.d.ts +3 -1
  26. package/dist/holmes/governance/ledger-timeline.js +11 -1
  27. package/dist/holmes/governance/provenance-chain.d.ts +2 -0
  28. package/dist/holmes/governance/provenance-chain.js +13 -2
  29. package/dist/holmes/governance/provenance-ledger.d.ts +28 -0
  30. package/dist/holmes/governance/provenance-ledger.js +69 -16
  31. package/dist/holmes/governance/session-context.d.ts +3 -0
  32. package/dist/holmes/governance/session-context.js +26 -8
  33. package/dist/holmes/guardrail/risk-classifier.js +2 -2
  34. package/dist/holmes/guardrail/write-target.js +2 -1
  35. package/dist/holmes/hooks/pre-tool-use.js +12 -1
  36. package/dist/holmes/hooks/stop.js +14 -3
  37. package/dist/holmes/mcp/handlers/adoption.d.ts +118 -0
  38. package/dist/holmes/mcp/handlers/adoption.js +186 -0
  39. package/dist/holmes/mcp/handlers/entity-integration.d.ts +100 -0
  40. package/dist/holmes/mcp/handlers/entity-integration.js +324 -0
  41. package/dist/holmes/mcp/handlers/entity-renumber.d.ts +137 -0
  42. package/dist/holmes/mcp/handlers/entity-renumber.js +228 -0
  43. package/dist/holmes/mcp/handlers/entity-store.d.ts +78 -0
  44. package/dist/holmes/mcp/handlers/entity-store.js +212 -0
  45. package/dist/holmes/mcp/handlers/graph-operations.d.ts +90 -0
  46. package/dist/holmes/mcp/handlers/graph-operations.js +260 -0
  47. package/dist/holmes/mcp/handlers/maintenance-analysis.d.ts +34 -0
  48. package/dist/holmes/mcp/handlers/maintenance-analysis.js +288 -0
  49. package/dist/holmes/mcp/handlers/maintenance-evidence.d.ts +40 -0
  50. package/dist/holmes/mcp/handlers/maintenance-evidence.js +104 -0
  51. package/dist/holmes/mcp/handlers/operator-inspection.d.ts +167 -0
  52. package/dist/holmes/mcp/handlers/operator-inspection.js +284 -0
  53. package/dist/holmes/mcp/handlers/phase-queries.d.ts +34 -0
  54. package/dist/holmes/mcp/handlers/phase-queries.js +86 -0
  55. package/dist/holmes/mcp/handlers/review-evidence.d.ts +47 -0
  56. package/dist/holmes/mcp/handlers/review-evidence.js +370 -0
  57. package/dist/holmes/mcp/handlers/review-queries.d.ts +68 -0
  58. package/dist/holmes/mcp/handlers/review-queries.js +79 -0
  59. package/dist/holmes/mcp/handlers/risk-assessment.d.ts +25 -0
  60. package/dist/holmes/mcp/handlers/risk-assessment.js +181 -0
  61. package/dist/holmes/mcp/handlers/slice-orchestration.d.ts +55 -0
  62. package/dist/holmes/mcp/handlers/slice-orchestration.js +324 -0
  63. package/dist/holmes/mcp/handlers/spec-approval.d.ts +105 -0
  64. package/dist/holmes/mcp/handlers/spec-approval.js +504 -0
  65. package/dist/holmes/mcp/handlers/spec-authoring.d.ts +57 -0
  66. package/dist/holmes/mcp/handlers/spec-authoring.js +326 -0
  67. package/dist/holmes/mcp/handlers/spec-lifecycle.d.ts +122 -0
  68. package/dist/holmes/mcp/handlers/spec-lifecycle.js +339 -0
  69. package/dist/holmes/mcp/handlers/spec-queries.d.ts +30 -0
  70. package/dist/holmes/mcp/handlers/spec-queries.js +74 -0
  71. package/dist/holmes/mcp/handlers/taint-screening.d.ts +48 -0
  72. package/dist/holmes/mcp/handlers/taint-screening.js +183 -0
  73. package/dist/holmes/mcp/handlers/test-execution.d.ts +63 -0
  74. package/dist/holmes/mcp/handlers/test-execution.js +210 -0
  75. package/dist/holmes/mcp/handlers/workspace-identity.d.ts +30 -0
  76. package/dist/holmes/mcp/handlers/workspace-identity.js +68 -0
  77. package/dist/holmes/mcp/handlers/workspace-queries.d.ts +77 -0
  78. package/dist/holmes/mcp/handlers/workspace-queries.js +193 -0
  79. package/dist/holmes/mcp/handlers.d.ts +653 -424
  80. package/dist/holmes/mcp/handlers.js +267 -3250
  81. package/dist/holmes/mcp/server.js +2 -1
  82. package/dist/holmes/mcp/tool-schemas.js +43 -2
  83. package/dist/holmes/project/execution-context.d.ts +17 -0
  84. package/dist/holmes/project/execution-context.js +120 -0
  85. package/dist/holmes/project/installer-markers.d.ts +34 -0
  86. package/dist/holmes/project/installer-markers.js +65 -0
  87. package/dist/holmes/project/root.d.ts +3 -1
  88. package/dist/holmes/project/root.js +4 -3
  89. package/dist/holmes/project/workspace-identity.d.ts +29 -0
  90. package/dist/holmes/project/workspace-identity.js +181 -0
  91. package/dist/holmes/review/test-outcomes.d.ts +6 -1
  92. package/dist/holmes/review/test-outcomes.js +23 -5
  93. package/dist/holmes/review/test-runner.d.ts +18 -0
  94. package/dist/holmes/review/test-runner.js +132 -4
  95. package/dist/holmes/rtm/graph-store.d.ts +50 -1
  96. package/dist/holmes/rtm/graph-store.js +244 -3
  97. package/dist/holmes/rtm/incremental.d.ts +1 -0
  98. package/dist/holmes/rtm/incremental.js +12 -3
  99. package/dist/holmes/rtm/localize.js +7 -0
  100. package/dist/holmes/rtm/rtm-graph.d.ts +9 -0
  101. package/dist/holmes/rtm/rtm-graph.js +13 -0
  102. package/dist/holmes/semantic/vector-cache.d.ts +16 -1
  103. package/dist/holmes/semantic/vector-cache.js +155 -19
  104. package/dist/holmes/spec/approval-status.d.ts +10 -0
  105. package/dist/holmes/spec/approval-status.js +7 -3
  106. package/dist/holmes/spec/entity-git-snapshot.d.ts +50 -0
  107. package/dist/holmes/spec/entity-git-snapshot.js +276 -0
  108. package/dist/holmes/spec/entity-integration-discharge.d.ts +73 -0
  109. package/dist/holmes/spec/entity-integration-discharge.js +233 -0
  110. package/dist/holmes/spec/entity-integration-transaction.d.ts +179 -0
  111. package/dist/holmes/spec/entity-integration-transaction.js +1221 -0
  112. package/dist/holmes/spec/entity-integration.d.ts +207 -0
  113. package/dist/holmes/spec/entity-integration.js +747 -0
  114. package/dist/holmes/spec/entity-renumber-transaction.d.ts +82 -0
  115. package/dist/holmes/spec/entity-renumber-transaction.js +711 -0
  116. package/dist/holmes/spec/entity-renumber.d.ts +32 -0
  117. package/dist/holmes/spec/entity-renumber.js +156 -0
  118. package/dist/holmes/spec/entity-store.d.ts +135 -0
  119. package/dist/holmes/spec/entity-store.js +1051 -0
  120. package/dist/holmes/spec/entity-transaction.d.ts +89 -0
  121. package/dist/holmes/spec/entity-transaction.js +701 -0
  122. package/dist/holmes/spec/renumber.d.ts +58 -0
  123. package/dist/holmes/spec/renumber.js +200 -1
  124. package/dist/holmes/spec/spec-store.d.ts +3 -2
  125. package/dist/holmes/spec/spec-store.js +23 -2
  126. package/dist/holmes/spec/transition-policy.d.ts +36 -0
  127. package/dist/holmes/spec/transition-policy.js +135 -0
  128. package/package.json +1 -1
@@ -0,0 +1,104 @@
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.createMaintenanceEvidenceHandlers = createMaintenanceEvidenceHandlers;
37
+ // @implements A-SPEC-603
38
+ const fs = __importStar(require("node:fs"));
39
+ const path = __importStar(require("node:path"));
40
+ const node_child_process_1 = require("node:child_process");
41
+ const maintenance_evidence_1 = require("../maintenance-evidence");
42
+ const impact_gate_1 = require("../../guardrail/impact-gate");
43
+ const root_1 = require("../../project/root");
44
+ /** Raw handlers: the public facade attaches basis exactly once. */
45
+ function createMaintenanceEvidenceHandlers(context) {
46
+ return {
47
+ // @implements A-SPEC-268
48
+ async maintenance_outcome(a) {
49
+ const foreign = context.foreignRootReason(a.root);
50
+ if (foreign)
51
+ throw context.refusal(foreign);
52
+ const root = context.projectRootOf(a.root);
53
+ return (0, maintenance_evidence_1.recordOutcome)(path.join(root, maintenance_evidence_1.EVIDENCE_DIR), a.digest, {
54
+ actualFiles: a.actualFiles,
55
+ actualSymbols: a.actualSymbols,
56
+ actualTests: a.actualTests,
57
+ actualClassification: a.actualClassification,
58
+ }, new Date().toISOString());
59
+ },
60
+ // @implements A-SPEC-277
61
+ async impact_gate_check(a) {
62
+ const foreign = context.foreignRootReason(a.root);
63
+ if (foreign)
64
+ throw context.refusal(foreign);
65
+ const root = context.projectRootOf(a.root);
66
+ let head = '';
67
+ try {
68
+ head = (0, node_child_process_1.execFileSync)('git', ['-C', root, 'rev-parse', 'HEAD'], { encoding: 'utf8', env: (0, root_1.cleanSubprocessEnv)(), stdio: ['ignore', 'pipe', 'pipe'] }).trim();
69
+ }
70
+ catch { /* non-git */ }
71
+ let artifact = null;
72
+ if (a.digest) {
73
+ const file = path.join(root, maintenance_evidence_1.EVIDENCE_DIR, (0, maintenance_evidence_1.digestFilename)(a.digest));
74
+ // A digest naming an artifact this project never stored is NOT the same as no evidence at
75
+ // all — say which one happened rather than collapsing both into "refused".
76
+ if (!fs.existsSync(file))
77
+ throw context.refusal(`이 프로젝트에 저장된 분석이 아닙니다: ${a.digest}`);
78
+ artifact = JSON.parse(fs.readFileSync(file, 'utf8'));
79
+ }
80
+ return (0, impact_gate_1.evaluateEditEvidence)({
81
+ target: a.target.replace(/\\/g, '/'),
82
+ artifact,
83
+ currentHead: head,
84
+ currentTargetDigest: context.fileDigestOf(root, a.target),
85
+ });
86
+ },
87
+ // @implements A-SPEC-268
88
+ async maintenance_calibration(a) {
89
+ const foreign = context.foreignRootReason(a.root);
90
+ if (foreign)
91
+ throw context.refusal(foreign);
92
+ const root = context.projectRootOf(a.root);
93
+ const read = (0, maintenance_evidence_1.readArtifacts)(path.join(root, maintenance_evidence_1.EVIDENCE_DIR));
94
+ // Corrupt and off-schema records travel WITH the score. A calibration that quietly dropped
95
+ // them would report a clean number over an unstated subset of the evidence.
96
+ return {
97
+ ...(0, maintenance_evidence_1.computeCalibration)(read.artifacts, { minSamples: a.minSamples }),
98
+ artifacts: read.artifacts.length,
99
+ unreadable: read.unreadable,
100
+ skippedSchema: read.skippedSchema,
101
+ };
102
+ },
103
+ };
104
+ }
@@ -0,0 +1,167 @@
1
+ import { Spec } from '../../spec/spec-parser';
2
+ import { SpecStore } from '../../spec/spec-store';
3
+ import type { Approval } from '../../guardrail/risk-gate';
4
+ export interface OperatorInspectionContext {
5
+ listSpecs(): Promise<Spec[]>;
6
+ readSpec: SpecStore['read'];
7
+ specsRoot(): unknown;
8
+ assertStoreReachable(tool: string, root: unknown): void;
9
+ resolver(specs: Spec[]): (id: string) => Spec | null;
10
+ resolveLedgerRoot(root?: string): {
11
+ ok: true;
12
+ root: string;
13
+ } | {
14
+ ok: false;
15
+ reason: string;
16
+ };
17
+ fetchJson(url: string): Promise<any>;
18
+ /** A-SPEC-642 — the shared approval channel and queue hint, for the reconciliation act. */
19
+ resolveHandlerApproval(root: string | undefined, approval: Approval | undefined, action: {
20
+ kind: string;
21
+ target: string;
22
+ }, now: string): {
23
+ approval: Approval;
24
+ source: 'env' | 'grant';
25
+ root?: string;
26
+ } | undefined;
27
+ refusalQueueHint(root: string | undefined, request: {
28
+ kind: string;
29
+ target: string;
30
+ why: string;
31
+ }): string;
32
+ }
33
+ export declare function createOperatorInspectionHandlers(context: OperatorInspectionContext): {
34
+ /**
35
+ * @implements A-SPEC-538.2
36
+ * Read-only: report a spec's approval/seal state — sealed?, approved_digest, each parent's seal
37
+ * state, and the concrete blockers still standing between it and approval — so a caller need not
38
+ * parse files to ask "what is the approval state right now". Reuses `sealOf` and `approvalBlockers`
39
+ * (the same predicates the code gate and spec_approve read) so the report cannot drift from the
40
+ * acts it describes. No writes, no ledger append.
41
+ */
42
+ approval_status(a: {
43
+ root?: string;
44
+ id: string;
45
+ }): Promise<{
46
+ ok: boolean;
47
+ reason: string;
48
+ } | {
49
+ graphPreview?: {
50
+ impact?: import("../../rtm/impact-advisory").ImpactAdvisory;
51
+ density?: Array<import("../../rtm/anchor-density").AnchorDensityFinding>;
52
+ cycles?: {
53
+ findings: Array<import("../../cpg/cycle-detect").CycleFinding>;
54
+ note: string;
55
+ };
56
+ architecture?: Array<import("../../cpg/arch-observe").ArchObservation>;
57
+ graphAsOf?: string;
58
+ graph?: {
59
+ status: "current" | "stale" | "unverified";
60
+ sourceCommit: string | null;
61
+ head: string | null;
62
+ };
63
+ unavailable?: "empty" | "unreadable";
64
+ } | undefined;
65
+ id: string;
66
+ type?: string;
67
+ status: string;
68
+ sealed: boolean;
69
+ approvedDigest?: string;
70
+ parents: import("../../spec/approval-status").ParentApproval[];
71
+ blockers: string[];
72
+ resealBlockers: string[];
73
+ ok: boolean;
74
+ reason?: undefined;
75
+ }>;
76
+ /**
77
+ * @implements A-SPEC-538.3
78
+ * Read-only: return the provenance ledger's events in time order (optionally narrowed to one
79
+ * spec) so the governance history — approved / unsealed / retired / review-needed … — is legible
80
+ * at a glance without reading raw JSONL. Reuses FileLedgerStore.loadAll(); the ordering/filtering/
81
+ * projection is the pure `timelineFrom`. No writes. An absent ledger is an empty history, not an
82
+ * error.
83
+ */
84
+ ledger_timeline(a: {
85
+ root?: string;
86
+ id?: string;
87
+ }): Promise<{
88
+ ok: boolean;
89
+ reason: string;
90
+ events?: undefined;
91
+ } | {
92
+ ok: boolean;
93
+ events: import("../../governance/ledger-timeline").TimelineEntry[];
94
+ reason?: undefined;
95
+ }>;
96
+ /**
97
+ * @implements A-SPEC-642
98
+ * The one exit from a merged cross-replica double-spend. Under out-of-band authority of kind
99
+ * `ledger-reconcile` for that nonce, append `nonce-double-spend-reconciled` on THIS replica's chain
100
+ * naming the nonce and every consuming event's hash; `verifyAll`, the Stop hook, baseline trust and
101
+ * the ledger-conflict refusals then clear. Nothing is erased: both consumptions stay in their
102
+ * chains, and a later consumption of the same nonce is a new, open double-spend. Refuses to extend
103
+ * a broken chain, because a reconciliation that itself cannot be verified lifts nothing.
104
+ */
105
+ ledger_reconcile(a: {
106
+ root?: string;
107
+ nonce: string;
108
+ rationale?: string;
109
+ }): Promise<{
110
+ ok: boolean;
111
+ reason: string;
112
+ reconciled?: undefined;
113
+ nonce?: undefined;
114
+ pending?: undefined;
115
+ code?: undefined;
116
+ events?: undefined;
117
+ remaining?: undefined;
118
+ } | {
119
+ ok: boolean;
120
+ reconciled: boolean;
121
+ nonce: string;
122
+ pending: string[];
123
+ reason: string;
124
+ code?: undefined;
125
+ events?: undefined;
126
+ remaining?: undefined;
127
+ } | {
128
+ ok: boolean;
129
+ code: string;
130
+ reason: string;
131
+ reconciled?: undefined;
132
+ nonce?: undefined;
133
+ pending?: undefined;
134
+ events?: undefined;
135
+ remaining?: undefined;
136
+ } | {
137
+ ok: boolean;
138
+ reconciled: boolean;
139
+ nonce: string;
140
+ events: {
141
+ replicaId: string | null;
142
+ seq: number;
143
+ hash: string;
144
+ ts: string;
145
+ }[];
146
+ remaining: string[];
147
+ reason?: undefined;
148
+ pending?: undefined;
149
+ code?: undefined;
150
+ }>;
151
+ rtm_dashboard(a: {
152
+ root?: string;
153
+ port?: number;
154
+ }): Promise<{
155
+ ok: boolean;
156
+ reason: string;
157
+ url?: undefined;
158
+ running?: undefined;
159
+ census?: undefined;
160
+ } | {
161
+ ok: boolean;
162
+ url: string;
163
+ running: boolean;
164
+ census: import("../../server/dashboard-launcher").DashboardCensus;
165
+ reason?: undefined;
166
+ }>;
167
+ };
@@ -0,0 +1,284 @@
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.createOperatorInspectionHandlers = createOperatorInspectionHandlers;
37
+ // @implements A-SPEC-605, A-SPEC-605.1
38
+ // @implements A-SPEC-642
39
+ // @implements A-SPEC-631
40
+ const fs = __importStar(require("node:fs"));
41
+ const path = __importStar(require("node:path"));
42
+ const spec_store_1 = require("../../spec/spec-store");
43
+ const approval_status_1 = require("../../spec/approval-status");
44
+ const ledger_store_1 = require("../../governance/ledger-store");
45
+ const ledger_timeline_1 = require("../../governance/ledger-timeline");
46
+ const provenance_ledger_1 = require("../../governance/provenance-ledger");
47
+ const provenance_chain_1 = require("../../governance/provenance-chain");
48
+ const approval_grants_1 = require("../../governance/approval-grants");
49
+ function createOperatorInspectionHandlers(context) {
50
+ return {
51
+ /**
52
+ * @implements A-SPEC-538.2
53
+ * Read-only: report a spec's approval/seal state — sealed?, approved_digest, each parent's seal
54
+ * state, and the concrete blockers still standing between it and approval — so a caller need not
55
+ * parse files to ask "what is the approval state right now". Reuses `sealOf` and `approvalBlockers`
56
+ * (the same predicates the code gate and spec_approve read) so the report cannot drift from the
57
+ * acts it describes. No writes, no ledger append.
58
+ */
59
+ async approval_status(a) {
60
+ context.assertStoreReachable('approval_status', a.root); // @implements A-SPEC-419
61
+ const all = await context.listSpecs();
62
+ if (all.filter((s) => s.id === a.id).length > 1) {
63
+ return {
64
+ ok: false,
65
+ reason: `${a.id}이(가) 스토어에 두 번 이상 존재합니다 — 어느 사본의 상태를 물었는지 도구가 고를 수 없어 거부합니다.`,
66
+ };
67
+ }
68
+ const cur = await context.readSpec(a.id);
69
+ if (!cur) {
70
+ const dir = context.specsRoot();
71
+ const unreadable = typeof dir === 'string' ? (0, spec_store_1.unreadableSpecFiles)(dir) : [];
72
+ return { ok: false, reason: (0, spec_store_1.notFoundReason)(a.id, unreadable) };
73
+ }
74
+ // @implements A-SPEC-572.1
75
+ // The graph advisory, delivered at DESIGN time. Measured on our own work: A-SPEC-571.1's
76
+ // sealing advisory named the exact cluster three regressions then landed in — and by then
77
+ // the design was done. The calculation was never the gap; the delivery time was. So the
78
+ // read-only "what is blocking this right now" tool also answers "what will this scope leak,
79
+ // and where is it dense" — using the SAME functions spec_approve calls, so the preview can
80
+ // never disagree with the seal. Read-only stays read-only: no scan, no build, no ledger
81
+ // append (a query must not pollute the observation denominator), and every failure degrades
82
+ // to an absent field on an otherwise identical response.
83
+ let graphPreview;
84
+ try {
85
+ if (cur.spec.type === 'A-SPEC' && a.root) {
86
+ const dbPath = path.join(a.root, '.ax', 'rtm.sqlite');
87
+ // @implements A-SPEC-631 — non-mutating inspection of the live graph; an empty or
88
+ // unreadable file is reported as such instead of previewing "no impact" from nothing.
89
+ const { openPublishedGraph, headCommitOf } = require('../../rtm/graph-store');
90
+ const { cleanSubprocessEnv } = require('../../project/root');
91
+ const view = openPublishedGraph(dbPath, headCommitOf(a.root, cleanSubprocessEnv()));
92
+ if (view.status === 'empty' || view.status === 'unreadable') {
93
+ graphPreview = { unavailable: view.status, ...(view.graphAsOf ? { graphAsOf: view.graphAsOf } : {}) };
94
+ }
95
+ else if (view.graph !== null) {
96
+ const { declaredImpactGap } = require('../../rtm/impact-advisory');
97
+ const { anchorDensityFindings } = require('../../rtm/anchor-density');
98
+ const { filesToTouch } = require('../../spec/compat-impact');
99
+ const graph = view.graph;
100
+ const condition = { status: view.status, sourceCommit: view.sourceCommit, head: view.head };
101
+ try {
102
+ const ftt = filesToTouch(cur.spec);
103
+ const impact = declaredImpactGap(ftt, graph, (rel) => { try {
104
+ return fs.readFileSync(path.join(a.root, rel), 'utf8');
105
+ }
106
+ catch {
107
+ return null;
108
+ } });
109
+ const density = anchorDensityFindings(ftt, graph.implementsAnchorCounts());
110
+ // @implements A-SPEC-574.2 — the computation lives in cycle-detect; this file holds
111
+ // the wiring only (measured: 85 anchors here against a p90 of 7).
112
+ const { cycleAdvisory, classifyEdgeByTarget, CYCLE_ADVISORY_NOTE } = require('../../cpg/cycle-detect');
113
+ const readCache = new Map();
114
+ const readSource = (rel) => {
115
+ const hit = readCache.get(rel);
116
+ if (hit !== undefined)
117
+ return hit;
118
+ // A read failure degrades to the conservative kind rather than losing the finding.
119
+ let text = '';
120
+ try {
121
+ text = fs.readFileSync(path.join(a.root, rel), 'utf8');
122
+ }
123
+ catch {
124
+ text = '';
125
+ }
126
+ readCache.set(rel, text);
127
+ return text;
128
+ };
129
+ const cycleFindings = cycleAdvisory(ftt, graph.importEdges().map((edge) => ({
130
+ ...edge,
131
+ kind: classifyEdgeByTarget(readSource(edge.from), edge.from, edge.to),
132
+ })));
133
+ // @implements A-SPEC-574.5 — the same import edges the cycle pass already read, plus
134
+ // the parent-time symbol spans the graph already holds. No new scan, no new parse.
135
+ const { architectureObservation } = require('../../cpg/arch-observe');
136
+ const { TreeSitterTsParser } = require('../../cpg/language-parser');
137
+ const { langForPath } = require('../../cpg/cpg-scanner');
138
+ // Bounded to the DECLARED files, whose text is read once and used for both numbers.
139
+ const archParser = new TreeSitterTsParser();
140
+ const archText = new Map();
141
+ const readArch = (f) => {
142
+ if (!archText.has(f)) {
143
+ try {
144
+ archText.set(f, fs.readFileSync(path.join(a.root, f), 'utf8'));
145
+ }
146
+ catch {
147
+ archText.set(f, null);
148
+ }
149
+ }
150
+ return archText.get(f) ?? null;
151
+ };
152
+ const spans = new Map(ftt.map((f) => {
153
+ const text = readArch(f);
154
+ if (text === null)
155
+ return [f, []];
156
+ try {
157
+ return [f, archParser.extractSymbols(text, langForPath(f))
158
+ .filter((sy) => sy.kind !== 'class')
159
+ .map((sy) => ({ startLine: sy.startLine, endLine: sy.endLine }))];
160
+ }
161
+ catch {
162
+ return [f, []];
163
+ }
164
+ }));
165
+ const arch = architectureObservation(ftt, spans, graph.importEdges(), (f) => { const t = readArch(f); return t === null ? null : t.split('\n').length; });
166
+ if (impact || density.length > 0 || cycleFindings.length > 0 || arch.length > 0) {
167
+ const graphAsOf = (() => { try {
168
+ return fs.statSync(dbPath).mtime.toISOString();
169
+ }
170
+ catch {
171
+ return undefined;
172
+ } })();
173
+ graphPreview = {
174
+ graph: condition,
175
+ ...(impact ? { impact } : {}),
176
+ ...(density.length > 0 ? { density } : {}),
177
+ ...(cycleFindings.length > 0
178
+ ? { cycles: { findings: cycleFindings, note: CYCLE_ADVISORY_NOTE } } : {}),
179
+ ...(arch.length > 0 ? { architecture: arch } : {}),
180
+ ...(graphAsOf ? { graphAsOf } : {}),
181
+ };
182
+ }
183
+ }
184
+ finally {
185
+ graph.close();
186
+ }
187
+ }
188
+ }
189
+ }
190
+ catch {
191
+ graphPreview = undefined;
192
+ }
193
+ return { ok: true, ...(0, approval_status_1.describeApproval)(cur.spec, context.resolver(all)), ...(graphPreview ? { graphPreview } : {}) };
194
+ },
195
+ /**
196
+ * @implements A-SPEC-538.3
197
+ * Read-only: return the provenance ledger's events in time order (optionally narrowed to one
198
+ * spec) so the governance history — approved / unsealed / retired / review-needed … — is legible
199
+ * at a glance without reading raw JSONL. Reuses FileLedgerStore.loadAll(); the ordering/filtering/
200
+ * projection is the pure `timelineFrom`. No writes. An absent ledger is an empty history, not an
201
+ * error.
202
+ */
203
+ async ledger_timeline(a) {
204
+ const dest = context.resolveLedgerRoot(a.root);
205
+ if (!dest.ok)
206
+ return { ok: false, reason: dest.reason };
207
+ const dir = path.join(dest.root, '.ax', 'ledger');
208
+ const events = fs.existsSync(dir) ? new ledger_store_1.FileLedgerStore(dir).loadAll() : [];
209
+ return { ok: true, events: (0, ledger_timeline_1.timelineFrom)(events, a.id) };
210
+ },
211
+ /**
212
+ * @implements A-SPEC-642
213
+ * The one exit from a merged cross-replica double-spend. Under out-of-band authority of kind
214
+ * `ledger-reconcile` for that nonce, append `nonce-double-spend-reconciled` on THIS replica's chain
215
+ * naming the nonce and every consuming event's hash; `verifyAll`, the Stop hook, baseline trust and
216
+ * the ledger-conflict refusals then clear. Nothing is erased: both consumptions stay in their
217
+ * chains, and a later consumption of the same nonce is a new, open double-spend. Refuses to extend
218
+ * a broken chain, because a reconciliation that itself cannot be verified lifts nothing.
219
+ */
220
+ async ledger_reconcile(a) {
221
+ const dest = context.resolveLedgerRoot(a.root);
222
+ if (!dest.ok)
223
+ return { ok: false, reason: dest.reason };
224
+ const nonce = typeof a.nonce === 'string' ? a.nonce.trim() : '';
225
+ if (!nonce)
226
+ return { ok: false, reason: 'ledger_reconcile: `nonce` is required — the nonce input exactly as verifyAll / doubleSpends names it (usually "nonce:<12 hex>").' };
227
+ const dir = path.join(dest.root, '.ax', 'ledger');
228
+ const store = new ledger_store_1.FileLedgerStore(dir);
229
+ const pending = store.doubleSpends();
230
+ const match = pending.find((d) => d.nonce === nonce);
231
+ if (!match) {
232
+ return { ok: true, reconciled: false, nonce, pending: pending.map((d) => d.nonce),
233
+ reason: pending.length === 0 ? 'the merged ledger holds no unreconciled double-spend' : `${nonce} is not an unreconciled double-spend; pending: ${pending.map((d) => d.nonce).join(', ')}` };
234
+ }
235
+ const replicas = [...new Set(match.events.map((e) => e.replicaId ?? 'legacy'))];
236
+ let approval;
237
+ try {
238
+ approval = process.env.HOLMES_APPROVAL ? JSON.parse(process.env.HOLMES_APPROVAL) : undefined;
239
+ }
240
+ catch {
241
+ approval = undefined;
242
+ }
243
+ const now = new Date().toISOString();
244
+ const resolved = context.resolveHandlerApproval(dest.root, approval, { kind: 'ledger-reconcile', target: nonce }, now);
245
+ if (!resolved) {
246
+ return { ok: false, code: 'approval-required',
247
+ reason: `ledger_reconcile requires an out-of-band HOLMES_APPROVAL (or grant) that COVERS kind "ledger-reconcile" for ${nonce} — accepting that ${match.events.length} consumptions on ${replicas.join(', ')} were intended is an operator decision, not the agent's.`
248
+ + context.refusalQueueHint(dest.root, { kind: 'ledger-reconcile', target: nonce, why: `Reconcile the cross-replica double-spend of ${nonce} (${match.events.length} consumptions on ${replicas.join(', ')})` }) };
249
+ }
250
+ const chainBreaks = store.verifyAll().broken.filter((b) => b.kind === 'chain');
251
+ if (chainBreaks.length > 0) {
252
+ return { ok: false, code: 'audit-conflict', reason: 'ledger_reconcile cannot extend a broken chain: ' + chainBreaks.map((b) => `${b.replicaId ?? 'legacy'}: ${b.detail ?? 'broken'}`).join('; ') };
253
+ }
254
+ store.append({
255
+ ts: now, actor: resolved.approval.actor, kind: provenance_ledger_1.DOUBLE_SPEND_RECONCILED,
256
+ summary: `reconciled cross-replica double-spend of ${nonce}: ${match.events.length} consumptions on ${replicas.join(', ')}`.slice(0, 200),
257
+ inputs: [nonce, ...match.events.map((e) => 'event:' + e.hash), ...replicas.map((r) => 'replica:' + r)],
258
+ rationale: typeof a.rationale === 'string' && a.rationale.trim() !== '' ? a.rationale.trim() : resolved.approval.rationale,
259
+ authorization: (0, provenance_chain_1.authorizationRef)(resolved.approval.actor, resolved.approval.token),
260
+ });
261
+ if (resolved.source === 'grant' && resolved.approval.nonce)
262
+ (0, approval_grants_1.consumeGrantFile)(resolved.root ?? dest.root, String(resolved.approval.nonce));
263
+ return { ok: true, reconciled: true, nonce, events: match.events, remaining: store.doubleSpends().map((d) => d.nonce) };
264
+ },
265
+ // @implements A-SPEC-545.3 — "show me the RTM dashboard" launches (idempotently) the server the
266
+ // agent would otherwise start by hand, and returns its URL plus an honesty census of what it shows.
267
+ async rtm_dashboard(a) {
268
+ const dest = context.resolveLedgerRoot(a.root);
269
+ if (!dest.ok)
270
+ return { ok: false, reason: dest.reason };
271
+ try {
272
+ const { startDashboardServer } = await Promise.resolve().then(() => __importStar(require('../../server/dashboard')));
273
+ const { ensureDashboard, dashboardCensus } = await Promise.resolve().then(() => __importStar(require('../../server/dashboard-launcher')));
274
+ const launch = await ensureDashboard(dest.root, a.port, (opts) => startDashboardServer(opts));
275
+ const rtm = await context.fetchJson(`${launch.url}/api/rtm`);
276
+ const heatmap = await context.fetchJson(`${launch.url}/api/rtm/heatmap`);
277
+ return { ok: true, url: launch.url, running: launch.running, census: dashboardCensus(rtm, heatmap) };
278
+ }
279
+ catch (err) {
280
+ return { ok: false, reason: `대시보드 기동 실패: ${err?.message ?? String(err)}` };
281
+ }
282
+ },
283
+ };
284
+ }
@@ -0,0 +1,34 @@
1
+ import { Spec } from '../../spec/spec-parser';
2
+ import { Action } from '../../guardrail/phase';
3
+ export interface PhaseQueryContext {
4
+ listSpecs(): Promise<Spec[]>;
5
+ boundSpecsRoot(): string | undefined;
6
+ projectRootOf(root: string): string;
7
+ }
8
+ export declare function createPhaseQueryHandlers(context: PhaseQueryContext): {
9
+ phase_status(a: {
10
+ root?: string;
11
+ }): Promise<{
12
+ ok: boolean;
13
+ reason: string;
14
+ specs?: undefined;
15
+ note?: undefined;
16
+ } | {
17
+ specs: {
18
+ id: string;
19
+ type: import("../../spec/spec-types").SpecType;
20
+ status: "draft" | "review" | "approved" | "outdated";
21
+ }[];
22
+ note: string;
23
+ ok?: undefined;
24
+ reason?: undefined;
25
+ }>;
26
+ phase_check(a: {
27
+ action?: Action;
28
+ target: string;
29
+ targetAspecId?: string;
30
+ }): Promise<import("../../guardrail/phase").CheckResult | {
31
+ decision: string;
32
+ note: string;
33
+ }>;
34
+ };