@holmes-lab/holmes-kit 0.19.6 → 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.
- package/CHANGELOG.md +96 -0
- package/README.md +8 -1
- package/dist/.build-id +1 -1
- package/dist/holmes/cli/approve-status.d.ts +24 -0
- package/dist/holmes/cli/approve-status.js +166 -0
- package/dist/holmes/cli/approve-watch.d.ts +6 -0
- package/dist/holmes/cli/approve-watch.js +6 -0
- package/dist/holmes/cli/approve.d.ts +27 -0
- package/dist/holmes/cli/approve.js +68 -7
- package/dist/holmes/cli/cli-execution.d.ts +2 -0
- package/dist/holmes/cli/cli-execution.js +24 -0
- package/dist/holmes/cli/gitignore-merge.d.ts +0 -5
- package/dist/holmes/cli/gitignore-merge.js +6 -2
- package/dist/holmes/cli/index.js +176 -139
- package/dist/holmes/cli/playbook-skills.d.ts +2 -22
- package/dist/holmes/cli/playbook-skills.js +10 -54
- package/dist/holmes/governance/approval-grants.d.ts +22 -0
- package/dist/holmes/governance/approval-grants.js +55 -1
- package/dist/holmes/governance/approval-queue.d.ts +43 -4
- package/dist/holmes/governance/approval-queue.js +86 -12
- package/dist/holmes/governance/display-id-aliases.d.ts +29 -0
- package/dist/holmes/governance/display-id-aliases.js +86 -0
- package/dist/holmes/governance/ledger-store.d.ts +42 -14
- package/dist/holmes/governance/ledger-store.js +49 -3
- package/dist/holmes/governance/ledger-timeline.d.ts +3 -1
- package/dist/holmes/governance/ledger-timeline.js +11 -1
- package/dist/holmes/governance/provenance-chain.d.ts +2 -0
- package/dist/holmes/governance/provenance-chain.js +13 -2
- package/dist/holmes/governance/provenance-ledger.d.ts +28 -0
- package/dist/holmes/governance/provenance-ledger.js +69 -16
- package/dist/holmes/governance/session-context.d.ts +3 -0
- package/dist/holmes/governance/session-context.js +26 -8
- package/dist/holmes/guardrail/risk-classifier.js +2 -2
- package/dist/holmes/guardrail/write-target.js +2 -1
- package/dist/holmes/hooks/pre-tool-use.js +12 -1
- package/dist/holmes/hooks/stop.js +14 -3
- package/dist/holmes/mcp/handlers/adoption.d.ts +118 -0
- package/dist/holmes/mcp/handlers/adoption.js +186 -0
- package/dist/holmes/mcp/handlers/entity-integration.d.ts +100 -0
- package/dist/holmes/mcp/handlers/entity-integration.js +324 -0
- package/dist/holmes/mcp/handlers/entity-renumber.d.ts +137 -0
- package/dist/holmes/mcp/handlers/entity-renumber.js +228 -0
- package/dist/holmes/mcp/handlers/entity-store.d.ts +78 -0
- package/dist/holmes/mcp/handlers/entity-store.js +212 -0
- package/dist/holmes/mcp/handlers/graph-operations.d.ts +90 -0
- package/dist/holmes/mcp/handlers/graph-operations.js +260 -0
- package/dist/holmes/mcp/handlers/maintenance-analysis.d.ts +34 -0
- package/dist/holmes/mcp/handlers/maintenance-analysis.js +288 -0
- package/dist/holmes/mcp/handlers/maintenance-evidence.d.ts +40 -0
- package/dist/holmes/mcp/handlers/maintenance-evidence.js +104 -0
- package/dist/holmes/mcp/handlers/operator-inspection.d.ts +167 -0
- package/dist/holmes/mcp/handlers/operator-inspection.js +284 -0
- package/dist/holmes/mcp/handlers/phase-queries.d.ts +34 -0
- package/dist/holmes/mcp/handlers/phase-queries.js +86 -0
- package/dist/holmes/mcp/handlers/review-evidence.d.ts +47 -0
- package/dist/holmes/mcp/handlers/review-evidence.js +370 -0
- package/dist/holmes/mcp/handlers/review-queries.d.ts +68 -0
- package/dist/holmes/mcp/handlers/review-queries.js +79 -0
- package/dist/holmes/mcp/handlers/risk-assessment.d.ts +25 -0
- package/dist/holmes/mcp/handlers/risk-assessment.js +181 -0
- package/dist/holmes/mcp/handlers/slice-orchestration.d.ts +55 -0
- package/dist/holmes/mcp/handlers/slice-orchestration.js +324 -0
- package/dist/holmes/mcp/handlers/spec-approval.d.ts +105 -0
- package/dist/holmes/mcp/handlers/spec-approval.js +504 -0
- package/dist/holmes/mcp/handlers/spec-authoring.d.ts +57 -0
- package/dist/holmes/mcp/handlers/spec-authoring.js +326 -0
- package/dist/holmes/mcp/handlers/spec-lifecycle.d.ts +122 -0
- package/dist/holmes/mcp/handlers/spec-lifecycle.js +339 -0
- package/dist/holmes/mcp/handlers/spec-queries.d.ts +30 -0
- package/dist/holmes/mcp/handlers/spec-queries.js +74 -0
- package/dist/holmes/mcp/handlers/taint-screening.d.ts +48 -0
- package/dist/holmes/mcp/handlers/taint-screening.js +183 -0
- package/dist/holmes/mcp/handlers/test-execution.d.ts +63 -0
- package/dist/holmes/mcp/handlers/test-execution.js +210 -0
- package/dist/holmes/mcp/handlers/workspace-identity.d.ts +30 -0
- package/dist/holmes/mcp/handlers/workspace-identity.js +68 -0
- package/dist/holmes/mcp/handlers/workspace-queries.d.ts +77 -0
- package/dist/holmes/mcp/handlers/workspace-queries.js +193 -0
- package/dist/holmes/mcp/handlers.d.ts +653 -424
- package/dist/holmes/mcp/handlers.js +267 -3250
- package/dist/holmes/mcp/server.js +2 -1
- package/dist/holmes/mcp/tool-schemas.js +43 -2
- package/dist/holmes/project/execution-context.d.ts +17 -0
- package/dist/holmes/project/execution-context.js +120 -0
- package/dist/holmes/project/installer-markers.d.ts +34 -0
- package/dist/holmes/project/installer-markers.js +65 -0
- package/dist/holmes/project/root.d.ts +3 -1
- package/dist/holmes/project/root.js +4 -3
- package/dist/holmes/project/workspace-identity.d.ts +29 -0
- package/dist/holmes/project/workspace-identity.js +181 -0
- package/dist/holmes/review/test-outcomes.d.ts +6 -1
- package/dist/holmes/review/test-outcomes.js +23 -5
- package/dist/holmes/review/test-runner.d.ts +18 -0
- package/dist/holmes/review/test-runner.js +132 -4
- package/dist/holmes/rtm/graph-store.d.ts +50 -1
- package/dist/holmes/rtm/graph-store.js +244 -3
- package/dist/holmes/rtm/incremental.d.ts +1 -0
- package/dist/holmes/rtm/incremental.js +12 -3
- package/dist/holmes/rtm/localize.js +7 -0
- package/dist/holmes/rtm/rtm-graph.d.ts +9 -0
- package/dist/holmes/rtm/rtm-graph.js +13 -0
- package/dist/holmes/semantic/vector-cache.d.ts +16 -1
- package/dist/holmes/semantic/vector-cache.js +155 -19
- package/dist/holmes/spec/approval-status.d.ts +10 -0
- package/dist/holmes/spec/approval-status.js +7 -3
- package/dist/holmes/spec/entity-git-snapshot.d.ts +50 -0
- package/dist/holmes/spec/entity-git-snapshot.js +276 -0
- package/dist/holmes/spec/entity-integration-discharge.d.ts +73 -0
- package/dist/holmes/spec/entity-integration-discharge.js +233 -0
- package/dist/holmes/spec/entity-integration-transaction.d.ts +179 -0
- package/dist/holmes/spec/entity-integration-transaction.js +1221 -0
- package/dist/holmes/spec/entity-integration.d.ts +207 -0
- package/dist/holmes/spec/entity-integration.js +747 -0
- package/dist/holmes/spec/entity-renumber-transaction.d.ts +82 -0
- package/dist/holmes/spec/entity-renumber-transaction.js +711 -0
- package/dist/holmes/spec/entity-renumber.d.ts +32 -0
- package/dist/holmes/spec/entity-renumber.js +156 -0
- package/dist/holmes/spec/entity-store.d.ts +135 -0
- package/dist/holmes/spec/entity-store.js +1051 -0
- package/dist/holmes/spec/entity-transaction.d.ts +89 -0
- package/dist/holmes/spec/entity-transaction.js +701 -0
- package/dist/holmes/spec/renumber.d.ts +58 -0
- package/dist/holmes/spec/renumber.js +200 -1
- package/dist/holmes/spec/spec-store.d.ts +3 -2
- package/dist/holmes/spec/spec-store.js +23 -2
- package/dist/holmes/spec/transition-policy.d.ts +36 -0
- package/dist/holmes/spec/transition-policy.js +135 -0
- package/package.json +1 -1
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import { Spec } from '../../spec/spec-parser';
|
|
2
|
+
import { SpecStore } from '../../spec/spec-store';
|
|
3
|
+
import { DraftResult } from '../../reverse/draft';
|
|
4
|
+
import { AnchorMapping } from '../../reverse/anchor';
|
|
5
|
+
export interface AdoptionContext {
|
|
6
|
+
listSpecs(): Promise<Spec[]>;
|
|
7
|
+
writeSpec: SpecStore['write'];
|
|
8
|
+
assertReadableRoot(tool: string, root: string): void;
|
|
9
|
+
occupiedTarget(specs: Spec[]): string | undefined;
|
|
10
|
+
}
|
|
11
|
+
export declare function createAdoptionHandlers(context: AdoptionContext): {
|
|
12
|
+
/**
|
|
13
|
+
* Read-only inventory of a brownfield target: candidate clusters, coverage, and what the scan
|
|
14
|
+
* could not resolve. Deliberately does NOT call assertRepoTopLevel — a target that is not a git
|
|
15
|
+
* repository is a supported case here, reported as `isGit: false`.
|
|
16
|
+
*/
|
|
17
|
+
reverse_scan(a: {
|
|
18
|
+
root: string;
|
|
19
|
+
maxFlagged?: number;
|
|
20
|
+
}): Promise<{
|
|
21
|
+
root: string;
|
|
22
|
+
isGit: boolean;
|
|
23
|
+
scanned: number;
|
|
24
|
+
droppedUntracked: number;
|
|
25
|
+
candidates: number;
|
|
26
|
+
anchored: number;
|
|
27
|
+
unanchored: number;
|
|
28
|
+
clusters: import("../../reverse/cluster").Cluster[];
|
|
29
|
+
testClusters: import("../../reverse/cluster").Cluster[];
|
|
30
|
+
coverage: {
|
|
31
|
+
structurallyResolved: number;
|
|
32
|
+
inTestClusters: number;
|
|
33
|
+
cohesionUnmeasured: number;
|
|
34
|
+
testsMatched: number;
|
|
35
|
+
testsUnmatched: number;
|
|
36
|
+
withDynamicIndicators: number;
|
|
37
|
+
indicators: Partial<Record<import("../../reverse/dynamic-wiring").Indicator, number>>;
|
|
38
|
+
uncalibratedIndicatorLanguages: string[];
|
|
39
|
+
ignoreUnsupportedPatterns: string[];
|
|
40
|
+
};
|
|
41
|
+
needsHumanConfirmation: import("../../reverse/scan").FlaggedFile[];
|
|
42
|
+
tests: import("../../reverse/test-map").TestMapping;
|
|
43
|
+
}>;
|
|
44
|
+
/**
|
|
45
|
+
* Draft H-SPEC/A-SPEC/T-SPEC documents for the recovered clusters under an EXISTING parent REQ.
|
|
46
|
+
*
|
|
47
|
+
* The REQ is the human's to write and this tool refuses without one — a requirement states
|
|
48
|
+
* business intent, which is not in the code. Everything emitted is `status: draft`, and writing
|
|
49
|
+
* requires an explicit `dryRun: false`.
|
|
50
|
+
*
|
|
51
|
+
* WHERE THE DOCUMENTS LAND: in the SERVER's configured spec store (`HOLMES_SPECS`, default
|
|
52
|
+
* `.ax/specs` relative to the server's working directory) — not inside `root`. In the intended
|
|
53
|
+
* adoption the two are the same directory, because the server runs inside the target it governs;
|
|
54
|
+
* pointing `root` at a different repository drafts that repository's slices into THIS store,
|
|
55
|
+
* which is a governance decision the caller has to make deliberately.
|
|
56
|
+
*/
|
|
57
|
+
reverse_draft(a: {
|
|
58
|
+
root: string;
|
|
59
|
+
parentReqId?: string;
|
|
60
|
+
cluster?: string;
|
|
61
|
+
dryRun?: boolean;
|
|
62
|
+
}): Promise<{
|
|
63
|
+
ok: boolean;
|
|
64
|
+
reason: string;
|
|
65
|
+
availableClusters?: undefined;
|
|
66
|
+
dryRun?: undefined;
|
|
67
|
+
parentReqId?: undefined;
|
|
68
|
+
clusters?: undefined;
|
|
69
|
+
drafted?: undefined;
|
|
70
|
+
skipped?: undefined;
|
|
71
|
+
refused?: undefined;
|
|
72
|
+
} | {
|
|
73
|
+
ok: boolean;
|
|
74
|
+
reason: string;
|
|
75
|
+
availableClusters: string[];
|
|
76
|
+
dryRun?: undefined;
|
|
77
|
+
parentReqId?: undefined;
|
|
78
|
+
clusters?: undefined;
|
|
79
|
+
drafted?: undefined;
|
|
80
|
+
skipped?: undefined;
|
|
81
|
+
refused?: undefined;
|
|
82
|
+
} | {
|
|
83
|
+
ok: boolean;
|
|
84
|
+
dryRun: boolean;
|
|
85
|
+
parentReqId: string;
|
|
86
|
+
clusters: number;
|
|
87
|
+
drafted: DraftResult[];
|
|
88
|
+
skipped: {
|
|
89
|
+
clusterKey: string;
|
|
90
|
+
reason: string;
|
|
91
|
+
}[];
|
|
92
|
+
refused: {
|
|
93
|
+
clusterKey: string;
|
|
94
|
+
reason: string;
|
|
95
|
+
findings?: unknown[];
|
|
96
|
+
}[];
|
|
97
|
+
reason?: undefined;
|
|
98
|
+
availableClusters?: undefined;
|
|
99
|
+
}>;
|
|
100
|
+
/**
|
|
101
|
+
* Insert `@implements` anchors into source files. Dry-run by DEFAULT, and anchoring to a
|
|
102
|
+
* non-approved A-SPEC is refused — a reverse-engineered draft describes code; description does
|
|
103
|
+
* not confer approval.
|
|
104
|
+
*/
|
|
105
|
+
reverse_anchor(a: {
|
|
106
|
+
root: string;
|
|
107
|
+
mapping: AnchorMapping[];
|
|
108
|
+
dryRun?: boolean;
|
|
109
|
+
}): Promise<{
|
|
110
|
+
blockers: Record<string, string>;
|
|
111
|
+
applied: number;
|
|
112
|
+
dryRun: boolean;
|
|
113
|
+
would: number;
|
|
114
|
+
failed: import("../../reverse/anchor").RefusedAnchor[];
|
|
115
|
+
edits: import("../../reverse/anchor").AnchorEdit[];
|
|
116
|
+
refused: import("../../reverse/anchor").RefusedAnchor[];
|
|
117
|
+
}>;
|
|
118
|
+
};
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.createAdoptionHandlers = createAdoptionHandlers;
|
|
4
|
+
const spec_store_1 = require("../../spec/spec-store");
|
|
5
|
+
const validator_1 = require("../../spec/validator");
|
|
6
|
+
const legacy_format_1 = require("../../spec/legacy-format");
|
|
7
|
+
const approval_blockers_1 = require("../../spec/approval-blockers");
|
|
8
|
+
const scan_1 = require("../../reverse/scan");
|
|
9
|
+
const draft_1 = require("../../reverse/draft");
|
|
10
|
+
const anchor_1 = require("../../reverse/anchor");
|
|
11
|
+
function createAdoptionHandlers(context) {
|
|
12
|
+
return {
|
|
13
|
+
// @implements A-SPEC-126
|
|
14
|
+
// Brownfield reverse engineering. Three explicitly-invoked tools, nothing running as a side
|
|
15
|
+
// effect of an ordinary session, and every write path opt-in: scan writes nothing at all, draft
|
|
16
|
+
// and anchor are dry-run by DEFAULT.
|
|
17
|
+
/**
|
|
18
|
+
* Read-only inventory of a brownfield target: candidate clusters, coverage, and what the scan
|
|
19
|
+
* could not resolve. Deliberately does NOT call assertRepoTopLevel — a target that is not a git
|
|
20
|
+
* repository is a supported case here, reported as `isGit: false`.
|
|
21
|
+
*/
|
|
22
|
+
async reverse_scan(a) {
|
|
23
|
+
// @implements A-SPEC-189 §10 (round 11) — `reverse_scan`/`reverse_draft` never call
|
|
24
|
+
// `projectRootOf`, so §7's refusal marker never reached them and their own POINTED sentence
|
|
25
|
+
// ("<root> is not a directory") arrived at the wire as a raw -32603 fault. A refusal about the
|
|
26
|
+
// caller's own argument is a refusal wherever it is authored.
|
|
27
|
+
context.assertReadableRoot('reverse_scan', a.root);
|
|
28
|
+
// `surfaceByCluster` is drafting evidence, not reading material. On the calibration target it
|
|
29
|
+
// is 11,460 characters across 15 clusters, and this response enters context on every scan —
|
|
30
|
+
// paying that to READ what only DRAFTING consumes. `reverse_draft` reads it in-process instead.
|
|
31
|
+
const { surfaceByCluster: _drafting, ...response } = (0, scan_1.reverseScan)(a.root, { maxFlagged: a.maxFlagged });
|
|
32
|
+
return response;
|
|
33
|
+
},
|
|
34
|
+
/**
|
|
35
|
+
* Draft H-SPEC/A-SPEC/T-SPEC documents for the recovered clusters under an EXISTING parent REQ.
|
|
36
|
+
*
|
|
37
|
+
* The REQ is the human's to write and this tool refuses without one — a requirement states
|
|
38
|
+
* business intent, which is not in the code. Everything emitted is `status: draft`, and writing
|
|
39
|
+
* requires an explicit `dryRun: false`.
|
|
40
|
+
*
|
|
41
|
+
* WHERE THE DOCUMENTS LAND: in the SERVER's configured spec store (`HOLMES_SPECS`, default
|
|
42
|
+
* `.ax/specs` relative to the server's working directory) — not inside `root`. In the intended
|
|
43
|
+
* adoption the two are the same directory, because the server runs inside the target it governs;
|
|
44
|
+
* pointing `root` at a different repository drafts that repository's slices into THIS store,
|
|
45
|
+
* which is a governance decision the caller has to make deliberately.
|
|
46
|
+
*/
|
|
47
|
+
async reverse_draft(a) {
|
|
48
|
+
context.assertReadableRoot('reverse_draft', a.root);
|
|
49
|
+
if (typeof a.parentReqId !== 'string' || a.parentReqId.trim() === '') {
|
|
50
|
+
return {
|
|
51
|
+
ok: false,
|
|
52
|
+
reason: 'parentReqId is required. A REQ states business intent, which does not exist in the code ' +
|
|
53
|
+
'and cannot be recovered from it — write the REQ first, then re-run with its id.',
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
const parentReqId = a.parentReqId.trim();
|
|
57
|
+
const specs = await context.listSpecs();
|
|
58
|
+
const req = specs.find((s) => s.id === parentReqId);
|
|
59
|
+
// An unresolvable parent would emit an orphan H-SPEC: a chain that only looks complete.
|
|
60
|
+
if (!req)
|
|
61
|
+
return { ok: false, reason: `parent REQ ${parentReqId} was not found in the spec store — create it first` };
|
|
62
|
+
// @implements A-SPEC-184
|
|
63
|
+
// A document with no `type:` is not a wrong-kind parent — it is an older-format one, and saying
|
|
64
|
+
// "is a undefined" left the adopter holding a visible document with nowhere to go. When the type
|
|
65
|
+
// IS declared, `legacyMessage` returns null and the original wording stands: that path was
|
|
66
|
+
// already accurate and REQ-184 does not touch it.
|
|
67
|
+
const formatWhy = (0, legacy_format_1.legacyMessage)(req);
|
|
68
|
+
if (formatWhy)
|
|
69
|
+
return { ok: false, reason: formatWhy };
|
|
70
|
+
if (req.type !== 'REQ')
|
|
71
|
+
return { ok: false, reason: `${parentReqId} is a ${req.type}; drafts must hang off a REQ` };
|
|
72
|
+
const report = (0, scan_1.reverseScan)(a.root);
|
|
73
|
+
// A cluster already drafted is recognised by the `reverse_cluster` key its documents carry, so
|
|
74
|
+
// re-running over the same tree never duplicates a slice.
|
|
75
|
+
const alreadyDrafted = new Set(specs.map((s) => s.frontmatter.reverse_cluster).filter((k) => typeof k === 'string'));
|
|
76
|
+
const selected = a.cluster ? report.clusters.filter((c) => (0, draft_1.clusterKeyOf)(c) === a.cluster) : report.clusters;
|
|
77
|
+
// A key that matches nothing must not read as success: "drafted 0" is indistinguishable from
|
|
78
|
+
// "everything was already drafted", so a mistyped key would silently skip the work.
|
|
79
|
+
if (a.cluster && selected.length === 0) {
|
|
80
|
+
const keys = report.clusters.map(draft_1.clusterKeyOf);
|
|
81
|
+
return {
|
|
82
|
+
ok: false,
|
|
83
|
+
reason: `no cluster matches key "${a.cluster}" — reverse_scan reports ${keys.length} cluster(s)`,
|
|
84
|
+
availableClusters: keys.slice(0, 50),
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
let base = (0, draft_1.nextIdBase)(specs.map((s) => s.id));
|
|
88
|
+
const drafted = [];
|
|
89
|
+
const skipped = [];
|
|
90
|
+
const refused = [];
|
|
91
|
+
for (const cluster of selected) {
|
|
92
|
+
const key = (0, draft_1.clusterKeyOf)(cluster);
|
|
93
|
+
if (alreadyDrafted.has(key)) {
|
|
94
|
+
skipped.push({ clusterKey: key, reason: 'already drafted' });
|
|
95
|
+
continue;
|
|
96
|
+
}
|
|
97
|
+
const d = (0, draft_1.draftSpecs)(cluster, req.id, String(base), {
|
|
98
|
+
surface: report.surfaceByCluster[key] ?? [],
|
|
99
|
+
testFiles: cluster.testFiles ?? [],
|
|
100
|
+
testsUnmatched: report.coverage.testsUnmatched,
|
|
101
|
+
});
|
|
102
|
+
if (!d.ok) {
|
|
103
|
+
refused.push({ clusterKey: key, reason: d.reason });
|
|
104
|
+
continue;
|
|
105
|
+
}
|
|
106
|
+
// Validate BEFORE writing: a draft that fails the project's own validator is not evidence of
|
|
107
|
+
// anything, and emitting one would put a broken document into the chain.
|
|
108
|
+
const known = [...specs, ...drafted.flatMap((x) => x.specs), ...d.specs];
|
|
109
|
+
const resolve = (id) => known.find((s) => s.id === id) ?? null;
|
|
110
|
+
const errors = d.specs.flatMap((s) => (0, validator_1.validateSpec)(s, resolve).findings.filter((f) => f.level === 'error').map((f) => ({ spec: s.id, ...f })));
|
|
111
|
+
if (errors.length) {
|
|
112
|
+
refused.push({ clusterKey: key, reason: 'drafted documents failed validation', findings: errors });
|
|
113
|
+
continue;
|
|
114
|
+
}
|
|
115
|
+
drafted.push(d);
|
|
116
|
+
base++;
|
|
117
|
+
}
|
|
118
|
+
const dryRun = a.dryRun !== false; // opt IN to writing
|
|
119
|
+
// @implements A-SPEC-188 — the minted id sits above every PARSEABLE id, but a file list()
|
|
120
|
+
// cannot read is invisible to nextIdBase, so its path can collide with a fresh draft. Same
|
|
121
|
+
// rule as spec_create: a path already occupied is a human's to look at, never overwritten.
|
|
122
|
+
// The whole cluster moves to `refused` — a partially-written chain only looks complete.
|
|
123
|
+
// Judged in the DRY RUN too (round-3): a preview that lists a cluster as drafted which the
|
|
124
|
+
// real run would refuse is a preview that lies.
|
|
125
|
+
const written = [];
|
|
126
|
+
for (const d of drafted) {
|
|
127
|
+
const clash = context.occupiedTarget(d.specs);
|
|
128
|
+
if (clash) {
|
|
129
|
+
refused.push({ clusterKey: d.clusterKey, reason: `초안 목적지(${clash})에 스토어가 읽지 못하는 파일이 이미 있습니다 — 덮어쓰지 않습니다. 사람이 확인해 옮기거나 고친 뒤 다시 실행하십시오.` });
|
|
130
|
+
continue;
|
|
131
|
+
}
|
|
132
|
+
if (!dryRun) {
|
|
133
|
+
try {
|
|
134
|
+
for (const s of d.specs)
|
|
135
|
+
await context.writeSpec(s);
|
|
136
|
+
}
|
|
137
|
+
catch (e) {
|
|
138
|
+
if (e instanceof spec_store_1.TargetPathOccupiedError) {
|
|
139
|
+
refused.push({ clusterKey: d.clusterKey, reason: `초안 목적지(${e.occupiedPath})에 스토어가 읽지 못하는 파일이 이미 있습니다 — 덮어쓰지 않습니다. 사람이 확인해 옮기거나 고친 뒤 다시 실행하십시오.` });
|
|
140
|
+
continue;
|
|
141
|
+
}
|
|
142
|
+
throw e;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
written.push(d);
|
|
146
|
+
}
|
|
147
|
+
// @implements A-SPEC-299
|
|
148
|
+
// Deliberately NOT changed. This looked like the same false pass as spec_slice_approve, but an
|
|
149
|
+
// approved store-integrity test pins `ok: true` for a run that refused every cluster because a
|
|
150
|
+
// destination held an unreadable file: nothing was written, nothing was damaged, and `ok` here
|
|
151
|
+
// means the call completed without harm. That is a defensible contract, and the case against
|
|
152
|
+
// it was analogy rather than an observed failure. Evidence beats symmetry.
|
|
153
|
+
return { ok: true, dryRun, parentReqId: req.id, clusters: report.clusters.length, drafted: written, skipped, refused };
|
|
154
|
+
},
|
|
155
|
+
/**
|
|
156
|
+
* Insert `@implements` anchors into source files. Dry-run by DEFAULT, and anchoring to a
|
|
157
|
+
* non-approved A-SPEC is refused — a reverse-engineered draft describes code; description does
|
|
158
|
+
* not confer approval.
|
|
159
|
+
*/
|
|
160
|
+
async reverse_anchor(a) {
|
|
161
|
+
const specs = await context.listSpecs();
|
|
162
|
+
const approved = specs.filter((s) => s.type === 'A-SPEC' && s.status === 'approved').map((s) => s.id);
|
|
163
|
+
const plan = (0, anchor_1.planAnchors)(a.root, a.mapping ?? [], approved);
|
|
164
|
+
// @implements A-SPEC-182
|
|
165
|
+
// The third place the harness refuses over an unapproved A-SPEC. Enriched HERE rather than
|
|
166
|
+
// inside planAnchors, which is a pure planner holding only the approved-id list — this is the
|
|
167
|
+
// boundary that has the spec objects, so the planner stays testable without them.
|
|
168
|
+
const byId = new Map(specs.map((s) => [s.id, s]));
|
|
169
|
+
// ONE payload per distinct A-SPEC, on a sibling field — not appended to every refused entry.
|
|
170
|
+
// Review measured the first attempt, which memoized only the COMPUTATION: 149 files mapped to
|
|
171
|
+
// one draft A-SPEC still concatenated the same 361-character sentence 149 times, 48 KB of pure
|
|
172
|
+
// duplication in a single tool result. The comment claimed a property the code did not have,
|
|
173
|
+
// which is how it survived a round of review — hence the size assertion in the test.
|
|
174
|
+
const blockers = {};
|
|
175
|
+
for (const r of plan.refused) {
|
|
176
|
+
if (!/is not approved$/.test(r.reason) || blockers[r.aspec] !== undefined)
|
|
177
|
+
continue;
|
|
178
|
+
const why = (0, approval_blockers_1.blockerSummary)(byId.get(r.aspec), (id) => byId.get(id) ?? null, r.aspec);
|
|
179
|
+
if (why)
|
|
180
|
+
blockers[r.aspec] = why.trim();
|
|
181
|
+
}
|
|
182
|
+
const applied = (0, anchor_1.applyAnchors)(a.root, plan.edits, { dryRun: a.dryRun !== false });
|
|
183
|
+
return { ...plan, ...applied, blockers };
|
|
184
|
+
},
|
|
185
|
+
};
|
|
186
|
+
}
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import { type DerivedRebuild } from '../../spec/entity-integration-discharge';
|
|
2
|
+
import { EntityIntegrationPlan } from '../../spec/entity-integration-transaction';
|
|
3
|
+
export type IntegrationSealTransition = {
|
|
4
|
+
kind: 'spec-unsealed';
|
|
5
|
+
id: string;
|
|
6
|
+
} | {
|
|
7
|
+
kind: 'spec-approved';
|
|
8
|
+
id: string;
|
|
9
|
+
digest: string;
|
|
10
|
+
};
|
|
11
|
+
/**
|
|
12
|
+
* @implements A-SPEC-635 — the seal transitions an integration performs, derived from its plan: every
|
|
13
|
+
* `unseal` step withdraws a seal, every `approve` step issues one whose digest is read from the
|
|
14
|
+
* published document bytes and must be that document's own `specDigest` (otherwise the seal is not
|
|
15
|
+
* what the transition policy computed → `audit-conflict`). Through the public tool a forged step is
|
|
16
|
+
* already refused `plan-changed` by step revalidation; this check is the audit's own invariant.
|
|
17
|
+
*/
|
|
18
|
+
/**
|
|
19
|
+
* @implements A-SPEC-645
|
|
20
|
+
* The `entity:`/`old-id:`/`new-id:` inputs for every mapping step whose record changes its display id,
|
|
21
|
+
* so an integration's renumbers are readable from the ledger like entity_renumber's. Without them the
|
|
22
|
+
* evidence consumers had no way to follow a document an integration renamed.
|
|
23
|
+
*/
|
|
24
|
+
export declare function renumberedMappingInputs(steps: ReadonlyArray<{
|
|
25
|
+
phase: string;
|
|
26
|
+
entityId?: string;
|
|
27
|
+
change: {
|
|
28
|
+
locator: string;
|
|
29
|
+
before: string | null;
|
|
30
|
+
after: string | null;
|
|
31
|
+
};
|
|
32
|
+
}>, renames?: ReadonlyArray<{
|
|
33
|
+
entityId: string;
|
|
34
|
+
oldDisplayId: string;
|
|
35
|
+
newDisplayId: string;
|
|
36
|
+
}>): string[];
|
|
37
|
+
export declare function integrationSealTransitions(operation: EntityIntegrationPlan): IntegrationSealTransition[];
|
|
38
|
+
export declare function createEntityIntegrationHandlers(context: {
|
|
39
|
+
storeRoot?: string;
|
|
40
|
+
rebuildFor?: (root: string) => DerivedRebuild;
|
|
41
|
+
}): {
|
|
42
|
+
entity_integrate(a: {
|
|
43
|
+
root?: string;
|
|
44
|
+
operation?: string;
|
|
45
|
+
base?: string;
|
|
46
|
+
left?: string;
|
|
47
|
+
right?: string;
|
|
48
|
+
operationId?: string;
|
|
49
|
+
plan?: unknown;
|
|
50
|
+
}): Promise<{
|
|
51
|
+
ok: true;
|
|
52
|
+
state: "completed";
|
|
53
|
+
operationId: string;
|
|
54
|
+
} | {
|
|
55
|
+
ok: true;
|
|
56
|
+
state: "abandoned";
|
|
57
|
+
operationId: string;
|
|
58
|
+
restored: string[];
|
|
59
|
+
untouched: string[];
|
|
60
|
+
preserved: string[];
|
|
61
|
+
} | {
|
|
62
|
+
ok: true;
|
|
63
|
+
record: import("../../spec/entity-integration-discharge").DerivedDischargeRecord;
|
|
64
|
+
alreadyDischarged: boolean;
|
|
65
|
+
} | {
|
|
66
|
+
ok: boolean;
|
|
67
|
+
obligations: import("../../spec/entity-integration-discharge").DerivedObligationStatus[];
|
|
68
|
+
plan?: undefined;
|
|
69
|
+
target?: undefined;
|
|
70
|
+
executable?: undefined;
|
|
71
|
+
code?: undefined;
|
|
72
|
+
reason?: undefined;
|
|
73
|
+
} | {
|
|
74
|
+
ok: boolean;
|
|
75
|
+
plan: import("../../spec/entity-integration-transaction").EntityIntegrationRecoveryInspection;
|
|
76
|
+
target: string;
|
|
77
|
+
executable: boolean;
|
|
78
|
+
obligations?: undefined;
|
|
79
|
+
code?: undefined;
|
|
80
|
+
reason?: undefined;
|
|
81
|
+
} | {
|
|
82
|
+
blockers: string[];
|
|
83
|
+
target?: string | undefined;
|
|
84
|
+
plan?: EntityIntegrationPlan | undefined;
|
|
85
|
+
preview: import("../../spec/entity-integration-transaction").EntityIntegrationCandidate;
|
|
86
|
+
ok: boolean;
|
|
87
|
+
executable: boolean;
|
|
88
|
+
obligations?: undefined;
|
|
89
|
+
code?: undefined;
|
|
90
|
+
reason?: undefined;
|
|
91
|
+
} | {
|
|
92
|
+
ok: boolean;
|
|
93
|
+
code: string;
|
|
94
|
+
reason: string;
|
|
95
|
+
obligations?: undefined;
|
|
96
|
+
plan?: undefined;
|
|
97
|
+
target?: undefined;
|
|
98
|
+
executable?: undefined;
|
|
99
|
+
}>;
|
|
100
|
+
};
|