@holmes-lab/holmes-kit 0.1.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 +22 -0
- package/LICENSE +21 -0
- package/README.md +102 -0
- package/bin/holmes-hook-antigravity.js +31 -0
- package/bin/holmes-kit.js +23 -0
- package/bin/holmes-mcp.js +34 -0
- package/bin/holmes-stop-antigravity.js +29 -0
- package/dist/.build-id +1 -0
- package/dist/holmes/cli/agents.js +168 -0
- package/dist/holmes/cli/doctor.js +625 -0
- package/dist/holmes/cli/gitignore-merge.js +84 -0
- package/dist/holmes/cli/governed-precondition.js +157 -0
- package/dist/holmes/cli/index.js +384 -0
- package/dist/holmes/cli/init.js +462 -0
- package/dist/holmes/cli/playbook-skills.js +711 -0
- package/dist/holmes/cli/roles-readme.js +134 -0
- package/dist/holmes/cli/settings-merge.js +122 -0
- package/dist/holmes/config/config.js +70 -0
- package/dist/holmes/context/bundler.js +114 -0
- package/dist/holmes/context/render.js +29 -0
- package/dist/holmes/context/tiers.js +110 -0
- package/dist/holmes/context/tokens.js +8 -0
- package/dist/holmes/cpg/cpg-scanner.js +213 -0
- package/dist/holmes/cpg/hash-cache.js +86 -0
- package/dist/holmes/cpg/language-parser-walk.js +917 -0
- package/dist/holmes/cpg/language-parser-worker.js +81 -0
- package/dist/holmes/cpg/language-parser.js +234 -0
- package/dist/holmes/cpg/scan-cache.js +108 -0
- package/dist/holmes/cpg/source-path.js +44 -0
- package/dist/holmes/cpg/test-files.js +84 -0
- package/dist/holmes/governance/constitution-debt.js +73 -0
- package/dist/holmes/governance/constitution-report.js +25 -0
- package/dist/holmes/governance/constitution.js +129 -0
- package/dist/holmes/governance/identity.js +30 -0
- package/dist/holmes/governance/ledger-lock.js +165 -0
- package/dist/holmes/governance/ledger-store.conformance.js +90 -0
- package/dist/holmes/governance/ledger-store.js +106 -0
- package/dist/holmes/governance/progress-ledger.js +83 -0
- package/dist/holmes/governance/provenance-chain.js +365 -0
- package/dist/holmes/governance/provenance-ledger.js +0 -0
- package/dist/holmes/governance/provenance-schema.js +47 -0
- package/dist/holmes/governance/replica-id.js +106 -0
- package/dist/holmes/governance/role-policy.js +137 -0
- package/dist/holmes/governance/trust-score.js +43 -0
- package/dist/holmes/guardrail/anchors.js +31 -0
- package/dist/holmes/guardrail/blind-spots.js +38 -0
- package/dist/holmes/guardrail/decision-ledger.js +107 -0
- package/dist/holmes/guardrail/executable-artifact.js +129 -0
- package/dist/holmes/guardrail/governance-history.js +101 -0
- package/dist/holmes/guardrail/phase.js +169 -0
- package/dist/holmes/guardrail/risk-classifier.js +450 -0
- package/dist/holmes/guardrail/risk-gate.js +160 -0
- package/dist/holmes/guardrail/risk-types.js +6 -0
- package/dist/holmes/guardrail/tspec-state.js +392 -0
- package/dist/holmes/guardrail/write-target.js +224 -0
- package/dist/holmes/hooks/adapters/antigravity.js +194 -0
- package/dist/holmes/hooks/pre-tool-use.js +1262 -0
- package/dist/holmes/hooks/stop.js +416 -0
- package/dist/holmes/mcp/basis.js +162 -0
- package/dist/holmes/mcp/handlers.js +1831 -0
- package/dist/holmes/mcp/server.js +71 -0
- package/dist/holmes/mcp/stdio-client.js +165 -0
- package/dist/holmes/mcp/supervisor.js +178 -0
- package/dist/holmes/mcp/tool-schemas.js +394 -0
- package/dist/holmes/mcp/validate-args.js +281 -0
- package/dist/holmes/messages/registry.js +50 -0
- package/dist/holmes/project/baseline.js +210 -0
- package/dist/holmes/project/change-source.js +233 -0
- package/dist/holmes/project/ignore.js +145 -0
- package/dist/holmes/project/root.js +113 -0
- package/dist/holmes/reverse/anchor.js +162 -0
- package/dist/holmes/reverse/cluster.js +187 -0
- package/dist/holmes/reverse/draft.js +151 -0
- package/dist/holmes/reverse/dynamic-wiring.js +47 -0
- package/dist/holmes/reverse/scan.js +194 -0
- package/dist/holmes/reverse/surface.js +154 -0
- package/dist/holmes/reverse/test-map.js +263 -0
- package/dist/holmes/review/coverage.js +33 -0
- package/dist/holmes/review/findings.js +123 -0
- package/dist/holmes/review/package.js +40 -0
- package/dist/holmes/review/review-targets.js +92 -0
- package/dist/holmes/review/scope.js +57 -0
- package/dist/holmes/review/test-evidence.js +77 -0
- package/dist/holmes/review/test-runner.js +572 -0
- package/dist/holmes/rtm/dataflow-taint.js +262 -0
- package/dist/holmes/rtm/gap-analyzer.js +27 -0
- package/dist/holmes/rtm/git-changes.js +72 -0
- package/dist/holmes/rtm/incremental.js +45 -0
- package/dist/holmes/rtm/localize.js +100 -0
- package/dist/holmes/rtm/rtm-builder.js +191 -0
- package/dist/holmes/rtm/rtm-check.js +89 -0
- package/dist/holmes/rtm/rtm-graph.js +232 -0
- package/dist/holmes/rtm/taint.js +92 -0
- package/dist/holmes/rtm/test-scope.js +336 -0
- package/dist/holmes/spec/approval-blockers.js +204 -0
- package/dist/holmes/spec/breaking-change.js +89 -0
- package/dist/holmes/spec/legacy-format.js +87 -0
- package/dist/holmes/spec/spec-digest.js +71 -0
- package/dist/holmes/spec/spec-parser.js +106 -0
- package/dist/holmes/spec/spec-store.conformance.js +118 -0
- package/dist/holmes/spec/spec-store.js +331 -0
- package/dist/holmes/spec/spec-types.js +177 -0
- package/dist/holmes/spec/validator.js +280 -0
- package/package.json +76 -0
- package/playbooks/adopt/PLAYBOOK.md +125 -0
- package/playbooks/author-slice/PLAYBOOK.md +119 -0
- package/playbooks/promote-slice/PLAYBOOK.md +134 -0
|
@@ -0,0 +1,213 @@
|
|
|
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.CpgScanner = exports.isTestFile = exports.TEST_FILE_PATTERNS = exports.SCANNABLE_EXTENSIONS = exports.SKIP = void 0;
|
|
37
|
+
const fs = __importStar(require("node:fs"));
|
|
38
|
+
const path = __importStar(require("node:path"));
|
|
39
|
+
const language_parser_1 = require("./language-parser");
|
|
40
|
+
const source_path_1 = require("./source-path");
|
|
41
|
+
/**
|
|
42
|
+
* Directories that never contain hand-written source for the project under analysis.
|
|
43
|
+
*
|
|
44
|
+
* The original set (`node_modules`, `dist`, `.git`) was JS-shaped, and that silently made the
|
|
45
|
+
* scanner unusable on other ecosystems: measured on a real brownfield Python target, a scan
|
|
46
|
+
* ingested **20,631 files instead of 315** — 98.5% of it third-party code inside `.venv`. That is
|
|
47
|
+
* not merely slow; the RTM graph fills with library symbols, clustering becomes meaningless, and
|
|
48
|
+
* reverse-engineered specs would describe someone else's code.
|
|
49
|
+
*
|
|
50
|
+
* Entries here must be *unambiguous* vendor or cache directories. Names that are conventional but
|
|
51
|
+
* genuinely ambiguous as source roots — `build`, `out`, `bin`, `obj`, `target`, `lib` — are
|
|
52
|
+
* deliberately EXCLUDED: silently dropping real source is the worse failure, and `isVendorDir`
|
|
53
|
+
* below catches the case that actually matters through evidence rather than by name.
|
|
54
|
+
*/
|
|
55
|
+
exports.SKIP = new Set([
|
|
56
|
+
// JS/TS
|
|
57
|
+
'node_modules', 'dist', '.next', '.nuxt', '.turbo',
|
|
58
|
+
// Python
|
|
59
|
+
'.venv', 'venv', 'site-packages', '__pycache__', '.tox', '.nox',
|
|
60
|
+
'.mypy_cache', '.pytest_cache', '.ruff_cache', '.eggs',
|
|
61
|
+
// Go / Rust / JVM / .NET tool caches
|
|
62
|
+
'vendor', '.gradle', '.cargo',
|
|
63
|
+
// VCS + generic tool output
|
|
64
|
+
'.git', '.hg', '.svn', 'coverage', '.idea', '.vscode',
|
|
65
|
+
]);
|
|
66
|
+
/**
|
|
67
|
+
* Evidence-based virtualenv detection. A Python virtualenv is defined by containing `pyvenv.cfg`,
|
|
68
|
+
* whatever the user named the directory (`env`, `.env311`, `myproject-venv`, …), so name matching
|
|
69
|
+
* alone misses them. Checking for the marker file is precise: no real source tree has one at its
|
|
70
|
+
* root. Same idea for a vendored `site-packages` nested under an unconventionally named env.
|
|
71
|
+
*/
|
|
72
|
+
function isVendorDir(dir) {
|
|
73
|
+
try {
|
|
74
|
+
return fs.existsSync(path.join(dir, 'pyvenv.cfg'));
|
|
75
|
+
}
|
|
76
|
+
catch {
|
|
77
|
+
return false;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
const IMPL = /@implements\s+(A-SPEC-\d{3,}(?:\.\d+)?)/g;
|
|
81
|
+
// Well-formed governed-but-non-A-SPEC ids used (wrongly) as an @implements
|
|
82
|
+
// anchor. Deliberately requires the full "KIND-NNN" shape (kind + '-' +
|
|
83
|
+
// 3+ digits) so prose/comments like `@implements A-SPEC refs` (no id number)
|
|
84
|
+
// or a bare word never match — only a genuine wrong-kind spec-id does.
|
|
85
|
+
const WRONG_KIND_IMPL = /@implements\s+((?:REQ|H-SPEC|C-SPEC|T-SPEC)-\d{3,}(?:\.\d+)?)/g;
|
|
86
|
+
/** Single source of truth for which file extensions CpgScanner ingests (REQ-124 gate 2). */
|
|
87
|
+
exports.SCANNABLE_EXTENSIONS = ['.ts', '.mts', '.cts', '.tsx', '.jsx', '.js', '.mjs', '.cjs', '.py', '.cs', '.java', '.go', '.rs', '.cpp', '.cc', '.cxx', '.hpp', '.hh', '.h'];
|
|
88
|
+
const SCANNABLE_RE = /\.(ts|mts|cts|tsx|jsx|js|mjs|cjs|py|cs|java|go|rs|cpp|cc|cxx|hpp|hh|h)$/;
|
|
89
|
+
// The test-file table moved to ./test-files, so the PreToolUse phase gate can read the SAME rules
|
|
90
|
+
// without pulling the scanner (and tree-sitter) into a latency-critical path. Re-exported here
|
|
91
|
+
// because this module was its published home and remains the natural place to look for it.
|
|
92
|
+
var test_files_1 = require("./test-files");
|
|
93
|
+
Object.defineProperty(exports, "TEST_FILE_PATTERNS", { enumerable: true, get: function () { return test_files_1.TEST_FILE_PATTERNS; } });
|
|
94
|
+
Object.defineProperty(exports, "isTestFile", { enumerable: true, get: function () { return test_files_1.isTestFile; } });
|
|
95
|
+
const test_files_2 = require("./test-files");
|
|
96
|
+
// .ts/.mts/.cts can contain TS type-assertion syntax (`<T>x`, arrow-generics)
|
|
97
|
+
// that the 'tsx' grammar misreads as JSX, so they must stay on 'typescript'.
|
|
98
|
+
// Everything else in SCANNABLE_EXTENSIONS (.tsx/.jsx/.js/.mjs/.cjs) never uses
|
|
99
|
+
// that syntax, and JSX-in-.js is common (React "escape hatch", older
|
|
100
|
+
// Next.js pages) — the 'tsx' grammar parses plain JS/TS byte-identically to
|
|
101
|
+
// 'typescript' for our walk while also correctly handling JSX, so route the
|
|
102
|
+
// whole JS/JSX family there.
|
|
103
|
+
const TSX_GRAMMAR_RE = /\.(tsx|jsx|js|mjs|cjs)$/;
|
|
104
|
+
// @implements A-SPEC-121.1
|
|
105
|
+
class CpgScanner {
|
|
106
|
+
parser;
|
|
107
|
+
cache;
|
|
108
|
+
opts;
|
|
109
|
+
// Optional N7 scan cache: when supplied, unchanged files (matching content hash) skip the
|
|
110
|
+
// tree-sitter parse entirely and reuse their cached symbols — file-key incremental scanning.
|
|
111
|
+
constructor(parser = new language_parser_1.TreeSitterTsParser(), cache,
|
|
112
|
+
// @implements A-SPEC-140.1 — see ScannedFile.dataflow for why this is opt-in.
|
|
113
|
+
opts = {}) {
|
|
114
|
+
this.parser = parser;
|
|
115
|
+
this.cache = cache;
|
|
116
|
+
this.opts = opts;
|
|
117
|
+
}
|
|
118
|
+
// @implements A-SPEC-131
|
|
119
|
+
// The recorded-skip channel: per-file failures used to vanish in the catch below, which is how
|
|
120
|
+
// handlers.ts (37KB, over the parse cliff) stayed outside the graph for the project's entire
|
|
121
|
+
// history. The report is per-scan (reset on entry), path-sorted, and a second channel — scan()'s
|
|
122
|
+
// signature and every existing consumer are untouched.
|
|
123
|
+
skipped = [];
|
|
124
|
+
/** `{file, reason}` for every file the LAST scan claimed but could not ingest. Empty on a clean tree. */
|
|
125
|
+
scanSkipped() {
|
|
126
|
+
return [...this.skipped].sort((a, b) => (a.file < b.file ? -1 : a.file > b.file ? 1 : 0));
|
|
127
|
+
}
|
|
128
|
+
scan(rootDir, repoRoot = rootDir) {
|
|
129
|
+
this.skipped = [];
|
|
130
|
+
const out = [];
|
|
131
|
+
const walk = (d) => {
|
|
132
|
+
let entries;
|
|
133
|
+
try {
|
|
134
|
+
entries = fs.readdirSync(d, { withFileTypes: true });
|
|
135
|
+
}
|
|
136
|
+
catch {
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
139
|
+
for (const e of entries) {
|
|
140
|
+
if (exports.SKIP.has(e.name))
|
|
141
|
+
continue;
|
|
142
|
+
const p = path.join(d, e.name);
|
|
143
|
+
if (e.isDirectory()) {
|
|
144
|
+
if (isVendorDir(p))
|
|
145
|
+
continue;
|
|
146
|
+
walk(p);
|
|
147
|
+
}
|
|
148
|
+
else if (SCANNABLE_RE.test(e.name) && !(0, test_files_2.isTestFile)(path.relative(rootDir, p).split(path.sep).join('/'))) {
|
|
149
|
+
// Test detection needs the path, not the basename: a Rust integration test is `tests/x.rs`
|
|
150
|
+
// and a JVM one is `src/test/java/X.java` — nothing in the filename says "test". Relative
|
|
151
|
+
// to the SCAN ROOT, so a repository that merely lives under some `/…/test/` directory on
|
|
152
|
+
// disk is not mistaken for one big test tree.
|
|
153
|
+
try {
|
|
154
|
+
const code = fs.readFileSync(p, 'utf8');
|
|
155
|
+
// @implements A-SPEC-121.5
|
|
156
|
+
// @implements A-SPEC-121.6
|
|
157
|
+
// .h is routed to the C++ grammar (not a separate C parser): the
|
|
158
|
+
// C++ grammar parses plain C headers acceptably for our lexical
|
|
159
|
+
// symbol walk, and disambiguating a bare `.h` between C and C++
|
|
160
|
+
// is out of scope for this slice.
|
|
161
|
+
const lang = /\.py$/.test(e.name) ? 'python' : (/\.cs$/.test(e.name) ? 'csharp' : (/\.java$/.test(e.name) ? 'java' : (/\.go$/.test(e.name) ? 'go' : (/\.rs$/.test(e.name) ? 'rust' : (/\.(cpp|cc|cxx|hpp|hh|h)$/.test(e.name) ? 'cpp' : (TSX_GRAMMAR_RE.test(e.name) ? 'tsx' : 'typescript'))))));
|
|
162
|
+
const implementsSpecs = [...code.matchAll(IMPL)].map(m => m[1]);
|
|
163
|
+
const unanchoredImplements = [...code.matchAll(WRONG_KIND_IMPL)].map(m => m[1]);
|
|
164
|
+
const sourcePath = (0, source_path_1.toSourcePath)(repoRoot, path.resolve(p));
|
|
165
|
+
// @implements A-SPEC-140.1
|
|
166
|
+
// The third parse happens only when asked for. `undefined` from the parser (a language
|
|
167
|
+
// with no walk) is carried through as `undefined`, not coerced to an empty fact set.
|
|
168
|
+
const dataflow = () => (this.opts.dataflow ? this.parser.extractDataFlow(code, lang) : undefined);
|
|
169
|
+
// N7 incremental: on a content-hash hit, reuse cached symbols and skip the parse.
|
|
170
|
+
if (this.cache) {
|
|
171
|
+
const { ScanFileCache } = require('./scan-cache');
|
|
172
|
+
const h = ScanFileCache.hashOf(code);
|
|
173
|
+
const hit = this.cache.get(sourcePath, h);
|
|
174
|
+
// @implements A-SPEC-140.1
|
|
175
|
+
// A hit written by a scan that did NOT extract data-flow cannot satisfy one that does:
|
|
176
|
+
// serving it would hand the taint pass zero facts, which is indistinguishable from
|
|
177
|
+
// "no tainted flows". Correctness over hit rate — re-extract and re-store instead.
|
|
178
|
+
// The language check keeps this from re-parsing every Go file on every scan forever.
|
|
179
|
+
const usable = hit && !(this.opts.dataflow && hit.dataflow === undefined && (0, language_parser_1.hasDataFlowWalk)(lang));
|
|
180
|
+
if (usable) {
|
|
181
|
+
out.push({ path: p, ...hit });
|
|
182
|
+
continue;
|
|
183
|
+
}
|
|
184
|
+
const fresh = { sourcePath, symbols: this.parser.extractSymbols(code, lang), edges: this.parser.extractEdges(code, lang), dataflow: dataflow(), implementsSpecs, unanchoredImplements };
|
|
185
|
+
this.cache.put(sourcePath, h, fresh);
|
|
186
|
+
out.push({ path: p, ...fresh });
|
|
187
|
+
continue;
|
|
188
|
+
}
|
|
189
|
+
out.push({ path: p, sourcePath, symbols: this.parser.extractSymbols(code, lang), edges: this.parser.extractEdges(code, lang), dataflow: dataflow(), implementsSpecs, unanchoredImplements });
|
|
190
|
+
}
|
|
191
|
+
catch (err) {
|
|
192
|
+
// A single unreadable/unparseable file must not abort a whole-tree scan — but it must
|
|
193
|
+
// not VANISH either (REQ-131: the old bare skip is how a >32K file stayed outside the
|
|
194
|
+
// graph, anchors and all, for the project's entire history). Infrastructure failures
|
|
195
|
+
// still propagate: under Jest the worker path can throw TreeSitterInfraError
|
|
196
|
+
// (timeout/crash/desync), and a dead worker is not a property of the file.
|
|
197
|
+
if (err instanceof language_parser_1.TreeSitterInfraError)
|
|
198
|
+
throw err;
|
|
199
|
+
// repoRoot-relative, matching sourcePath's path-key convention — a skip entry must be
|
|
200
|
+
// comparable with scan results and change sets even when rootDir is a subtree.
|
|
201
|
+
const rel = path.relative(repoRoot, p).split(path.sep).join('/');
|
|
202
|
+
const reason = (err instanceof Error ? err.message : String(err)).slice(0, 200);
|
|
203
|
+
this.skipped.push({ file: rel, reason });
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
};
|
|
208
|
+
walk(rootDir);
|
|
209
|
+
this.cache?.save(); // batched persist + mark-and-sweep prune, once per scan
|
|
210
|
+
return out;
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
exports.CpgScanner = CpgScanner;
|
|
@@ -0,0 +1,86 @@
|
|
|
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.HashCache = void 0;
|
|
37
|
+
const fs = __importStar(require("node:fs"));
|
|
38
|
+
const path = __importStar(require("node:path"));
|
|
39
|
+
const node_crypto_1 = require("node:crypto");
|
|
40
|
+
// @implements A-SPEC-113.1
|
|
41
|
+
/**
|
|
42
|
+
* Content-hash skip cache used by the reindex path to avoid re-scanning
|
|
43
|
+
* files whose content hasn't changed since the last recorded hash. Keyed by
|
|
44
|
+
* repo-relative path, persisted as a single JSON file under `dir` (callers
|
|
45
|
+
* pass `.ax/cpg_cache`, which is gitignored — cache files are derived, never
|
|
46
|
+
* committed).
|
|
47
|
+
*
|
|
48
|
+
* Deterministic: SHA-256 of the exact content string, no timestamps or other
|
|
49
|
+
* nondeterministic inputs recorded.
|
|
50
|
+
*/
|
|
51
|
+
class HashCache {
|
|
52
|
+
dir;
|
|
53
|
+
file;
|
|
54
|
+
hashes;
|
|
55
|
+
constructor(dir) {
|
|
56
|
+
this.dir = dir;
|
|
57
|
+
this.file = path.join(dir, 'hashes.json');
|
|
58
|
+
this.hashes = this.load();
|
|
59
|
+
}
|
|
60
|
+
load() {
|
|
61
|
+
try {
|
|
62
|
+
const raw = fs.readFileSync(this.file, 'utf8');
|
|
63
|
+
return JSON.parse(raw);
|
|
64
|
+
}
|
|
65
|
+
catch {
|
|
66
|
+
// Missing dir/file, or unreadable/corrupt JSON: treat as no prior hash
|
|
67
|
+
// rather than throwing — a cold or damaged cache must never block a
|
|
68
|
+
// reindex, only make it fall back to a full re-scan.
|
|
69
|
+
return {};
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
static hashOf(content) {
|
|
73
|
+
return (0, node_crypto_1.createHash)('sha256').update(content, 'utf8').digest('hex');
|
|
74
|
+
}
|
|
75
|
+
/** True iff a hash was previously stored for relPath and it matches content's hash. */
|
|
76
|
+
unchanged(relPath, content) {
|
|
77
|
+
const stored = this.hashes[relPath];
|
|
78
|
+
return stored !== undefined && stored === HashCache.hashOf(content);
|
|
79
|
+
}
|
|
80
|
+
put(relPath, content) {
|
|
81
|
+
this.hashes[relPath] = HashCache.hashOf(content);
|
|
82
|
+
fs.mkdirSync(this.dir, { recursive: true });
|
|
83
|
+
fs.writeFileSync(this.file, JSON.stringify(this.hashes, null, 2), 'utf8');
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
exports.HashCache = HashCache;
|