@davesheffer/hunch 1.31.0 → 1.32.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/README.md +3 -6
- package/dist/cli/automaticReviewMemory.d.ts +13 -0
- package/dist/cli/dna.d.ts +2 -0
- package/dist/cli/index.d.ts +16 -0
- package/dist/cli/index.js +602 -43
- package/dist/cli/integrations.d.ts +2 -0
- package/dist/cli/integrations.js +5 -2
- package/dist/cli/invocation.d.ts +35 -0
- package/dist/cli/preflight.d.ts +1 -0
- package/dist/cli/reviewMemory.d.ts +4 -0
- package/dist/cli/reviewMemoryProvider.d.ts +14 -0
- package/dist/cli/serve.d.ts +2 -0
- package/dist/cli/taskReport.d.ts +6 -0
- package/dist/cli/taskReport.js +124 -0
- package/dist/cli/update.d.ts +14 -0
- package/dist/client/state.d.ts +248 -0
- package/dist/client/state.js +2 -0
- package/dist/constitution/adapters.d.ts +29 -0
- package/dist/constitution/behaviorAttestationBinding.d.ts +3 -0
- package/dist/constitution/behaviorEvaluator.d.ts +8 -0
- package/dist/constitution/behaviorProof.d.ts +9 -0
- package/dist/constitution/behaviorWorkspace.d.ts +25 -0
- package/dist/constitution/bootstrap.d.ts +30 -0
- package/dist/constitution/canonical.d.ts +15 -0
- package/dist/constitution/card.d.ts +58 -0
- package/dist/constitution/compiler.d.ts +52 -0
- package/dist/constitution/composition.d.ts +10 -0
- package/dist/constitution/corpus.d.ts +7 -0
- package/dist/constitution/correctionPolicyMaterializer.d.ts +57 -0
- package/dist/constitution/delta.d.ts +4 -0
- package/dist/constitution/disposition.d.ts +20 -0
- package/dist/constitution/evaluator.d.ts +47 -0
- package/dist/constitution/experiment.d.ts +466 -0
- package/dist/constitution/experimentRunner.d.ts +7 -0
- package/dist/constitution/g2.d.ts +209 -0
- package/dist/constitution/g2BehaviorAttestation.d.ts +45 -0
- package/dist/constitution/g2BehaviorCandidates.d.ts +123 -0
- package/dist/constitution/g2BehaviorDependencies.d.ts +83 -0
- package/dist/constitution/g2BehaviorMaterialization.d.ts +77 -0
- package/dist/constitution/g2BehaviorPolicyMaterializer.d.ts +48 -0
- package/dist/constitution/g2CandidateAttestation.d.ts +43 -0
- package/dist/constitution/g2Candidates.d.ts +80 -0
- package/dist/constitution/g2Drills.d.ts +33 -0
- package/dist/constitution/g3.d.ts +311 -0
- package/dist/constitution/g3Conformance.d.ts +33 -0
- package/dist/constitution/lifecycle.d.ts +25 -0
- package/dist/constitution/mutation.d.ts +15 -0
- package/dist/constitution/nodeTestEvidence.d.ts +13 -0
- package/dist/constitution/plan.d.ts +18 -0
- package/dist/constitution/policyRuntime.d.ts +5 -0
- package/dist/constitution/proof.d.ts +11 -0
- package/dist/constitution/repairPolicies.d.ts +34 -0
- package/dist/constitution/replacementFreeGit.d.ts +14 -0
- package/dist/constitution/replay.d.ts +40 -0
- package/dist/constitution/replayCache.d.ts +14 -0
- package/dist/constitution/replayWorker.d.ts +1 -0
- package/dist/constitution/repository.d.ts +129 -0
- package/dist/constitution/safeCheckout.d.ts +9 -0
- package/dist/constitution/schema.d.ts +1763 -0
- package/dist/constitution/scorecard.d.ts +219 -0
- package/dist/constitution/service.d.ts +398 -0
- package/dist/constitution/shadow.d.ts +54 -0
- package/dist/constitution/sourceMutation.d.ts +15 -0
- package/dist/constitution/staticGraphBaseline.d.ts +13 -0
- package/dist/constitution/structural.d.ts +64 -0
- package/dist/core/agenthook.d.ts +63 -0
- package/dist/core/agenthook.js +1 -0
- package/dist/core/automaticReviewMemory.d.ts +60 -0
- package/dist/core/autoreview.d.ts +66 -0
- package/dist/core/canonicalOrder.d.ts +3 -0
- package/dist/core/capturetoken.d.ts +19 -0
- package/dist/core/changeIdentity.d.ts +22 -0
- package/dist/core/changeProof.d.ts +14 -0
- package/dist/core/checkreport.d.ts +140 -0
- package/dist/core/commitrepair.d.ts +171 -0
- package/dist/core/commitrepair.js +238 -0
- package/dist/core/compare.d.ts +21 -0
- package/dist/core/config.d.ts +20 -0
- package/dist/core/conformance.d.ts +35 -0
- package/dist/core/constraintmatch.d.ts +54 -0
- package/dist/core/correction.d.ts +42 -0
- package/dist/core/correctionStage.d.ts +224 -0
- package/dist/core/declarationClusters.d.ts +149 -0
- package/dist/core/delivery.d.ts +110 -0
- package/dist/core/docanchors.d.ts +32 -0
- package/dist/core/docscan.d.ts +26 -0
- package/dist/core/drift.d.ts +13 -0
- package/dist/core/drift.js +31 -1
- package/dist/core/dupdetect.d.ts +43 -0
- package/dist/core/escalations.d.ts +129 -0
- package/dist/core/escalations.js +165 -0
- package/dist/core/events.d.ts +29 -0
- package/dist/core/evidenceMap.d.ts +164 -0
- package/dist/core/externalImports.d.ts +8 -0
- package/dist/core/format.d.ts +13 -0
- package/dist/core/glob.d.ts +9 -0
- package/dist/core/groundingLag.d.ts +84 -0
- package/dist/core/groundingLag.js +16 -2
- package/dist/core/groundingMerge.d.ts +15 -0
- package/dist/core/groundingMerge.js +95 -0
- package/dist/core/hookcache.d.ts +10 -0
- package/dist/core/hookpolicy.d.ts +47 -0
- package/dist/core/ids.d.ts +36 -0
- package/dist/core/importReview.d.ts +30 -0
- package/dist/core/io.d.ts +23 -0
- package/dist/core/jsonc.d.ts +4 -0
- package/dist/core/landscapeAdoption.d.ts +66 -0
- package/dist/core/landscapeDelivery.d.ts +76 -0
- package/dist/core/memorylog.d.ts +37 -0
- package/dist/core/migrate.d.ts +28 -0
- package/dist/core/outcomeExperience.d.ts +89 -0
- package/dist/core/overlaySafety.d.ts +24 -0
- package/dist/core/overlaySafety.js +7 -1
- package/dist/core/paths.d.ts +31 -0
- package/dist/core/pipeline.d.ts +289 -0
- package/dist/core/premises.d.ts +43 -0
- package/dist/core/provenance.d.ts +21 -0
- package/dist/core/publication.d.ts +54 -0
- package/dist/core/refrepair.d.ts +24 -0
- package/dist/core/refrepair.js +1 -1
- package/dist/core/relativeImports.d.ts +11 -0
- package/dist/core/repair.d.ts +44 -0
- package/dist/core/repairqueue.d.ts +40 -0
- package/dist/core/repairqueue.js +133 -0
- package/dist/core/reviewMemory.d.ts +40 -0
- package/dist/core/reviewqueue.d.ts +54 -0
- package/dist/core/safeRepoFile.d.ts +20 -0
- package/dist/core/served.d.ts +57 -0
- package/dist/core/served.js +14 -0
- package/dist/core/stateContract.d.ts +979 -0
- package/dist/core/stateContract.js +87 -4
- package/dist/core/stateDelivery.d.ts +78 -0
- package/dist/core/stateRecords.d.ts +383 -0
- package/dist/core/stateRecords.js +16 -1
- package/dist/core/stats.d.ts +88 -0
- package/dist/core/strictgate.d.ts +33 -0
- package/dist/core/taskReport.d.ts +226 -0
- package/dist/core/taskReport.js +514 -0
- package/dist/core/taskReportCapture.d.ts +8 -0
- package/dist/core/taskReportCapture.js +36 -0
- package/dist/core/taskReportEvidence.d.ts +26 -0
- package/dist/core/taskReportEvidence.js +268 -0
- package/dist/core/taskReportHook.d.ts +20 -0
- package/dist/core/taskReportHook.js +76 -0
- package/dist/core/taskReportPaths.d.ts +3 -0
- package/dist/core/taskReportPaths.js +23 -0
- package/dist/core/taskReportPublic.d.ts +31 -0
- package/dist/core/taskReportPublic.js +45 -0
- package/dist/core/taskReportRender.d.ts +6 -0
- package/dist/core/taskReportRender.js +97 -0
- package/dist/core/topics.d.ts +73 -0
- package/dist/core/types.d.ts +1239 -0
- package/dist/core/version.d.ts +10 -0
- package/dist/eval/guards.d.ts +45 -0
- package/dist/eval/harness.d.ts +58 -0
- package/dist/extractors/adrImport.d.ts +59 -0
- package/dist/extractors/comments.d.ts +7 -0
- package/dist/extractors/correctionSources.d.ts +11 -0
- package/dist/extractors/diff.d.ts +40 -0
- package/dist/extractors/git.d.ts +304 -0
- package/dist/extractors/git.js +208 -20
- package/dist/extractors/helm.d.ts +31 -0
- package/dist/extractors/indexer.d.ts +55 -0
- package/dist/extractors/landscapeDiscovery.d.ts +41 -0
- package/dist/extractors/languages.d.ts +77 -0
- package/dist/extractors/nativeTreeSitter.d.ts +16 -0
- package/dist/extractors/parse.d.ts +54 -0
- package/dist/extractors/php.d.ts +26 -0
- package/dist/extractors/repoSource.d.ts +53 -0
- package/dist/extractors/testreport.d.ts +36 -0
- package/dist/integrations/ciAction.d.ts +8 -0
- package/dist/integrations/claudeConfig.d.ts +44 -0
- package/dist/integrations/claudemd.d.ts +13 -0
- package/dist/integrations/claudemd.js +11 -2
- package/dist/integrations/gitignore.d.ts +16 -0
- package/dist/integrations/gitignore.js +7 -0
- package/dist/integrations/health.d.ts +74 -0
- package/dist/integrations/hooks.d.ts +41 -0
- package/dist/integrations/hooks.js +92 -66
- package/dist/integrations/madrExport.d.ts +39 -0
- package/dist/integrations/madrManifest.d.ts +69 -0
- package/dist/integrations/mergeDriver.d.ts +3 -0
- package/dist/integrations/mergeDriver.js +25 -8
- package/dist/integrations/probe.d.ts +4 -0
- package/dist/integrations/providers.d.ts +103 -0
- package/dist/integrations/scaffold.d.ts +29 -0
- package/dist/integrations/sync.d.ts +30 -0
- package/dist/integrations/sync.js +3 -2
- package/dist/integrations/team.d.ts +70 -0
- package/dist/integrations/worktree.d.ts +8 -0
- package/dist/mcp/roots.d.ts +16 -0
- package/dist/mcp/server.d.ts +36 -0
- package/dist/mcp/server.js +114 -25
- package/dist/mcp/taskReportTools.d.ts +3 -0
- package/dist/mcp/taskReportTools.js +106 -0
- package/dist/serve/app.d.ts +36 -0
- package/dist/serve/app.js +20 -0
- package/dist/serve/config.d.ts +100 -0
- package/dist/serve/writelock.d.ts +22 -0
- package/dist/store/changeLedger.d.ts +132 -0
- package/dist/store/changeLedger.js +40 -9
- package/dist/store/compact.d.ts +33 -0
- package/dist/store/db.d.ts +9 -0
- package/dist/store/embedder.d.ts +32 -0
- package/dist/store/hunchStore.d.ts +624 -0
- package/dist/store/hunchStore.js +13 -5
- package/dist/store/jsonStore.d.ts +125 -0
- package/dist/store/jsonStore.js +15 -0
- package/dist/store/merge.d.ts +39 -0
- package/dist/store/privateMigrate.d.ts +24 -0
- package/dist/store/replay.d.ts +56 -0
- package/dist/store/schema.d.ts +17 -0
- package/dist/store/stateBinding.d.ts +145 -0
- package/dist/store/stateBinding.js +110 -15
- package/dist/store/stateCapture.d.ts +15 -0
- package/dist/store/stateCapture.js +145 -0
- package/dist/synthesis/cliAdapter.d.ts +22 -0
- package/dist/synthesis/initiator.d.ts +13 -0
- package/dist/synthesis/provider.d.ts +307 -0
- package/dist/synthesis/synthesize.d.ts +117 -0
- package/dist/synthesis/tripwires.d.ts +9 -0
- package/dist/taskReports.d.ts +76 -0
- package/dist/taskReports.js +67 -0
- package/dist/wiki/adopt.d.ts +22 -0
- package/dist/wiki/graph.d.ts +87 -0
- package/dist/wiki/wiki.d.ts +242 -0
- package/package.json +6 -5
- package/server.json +2 -2
package/dist/extractors/git.js
CHANGED
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
* No LLM here — just parsing what git already knows. */
|
|
3
3
|
import { execFileSync } from "node:child_process";
|
|
4
4
|
import { createHash } from "node:crypto";
|
|
5
|
-
import { devNull } from "node:os";
|
|
5
|
+
import { devNull, tmpdir } from "node:os";
|
|
6
6
|
import { isAbsolute, resolve, join, basename, dirname, relative, sep } from "node:path";
|
|
7
|
-
import { mkdirSync, rmSync, statSync, lstatSync, realpathSync, readFileSync, renameSync, readdirSync } from "node:fs";
|
|
7
|
+
import { mkdtempSync, openSync, closeSync, readSync, mkdirSync, rmSync, statSync, lstatSync, realpathSync, readFileSync, renameSync, readdirSync } from "node:fs";
|
|
8
8
|
import { fileURLToPath } from "node:url";
|
|
9
9
|
import { MEMLOG_FORMAT } from "../core/memorylog.js";
|
|
10
10
|
import { hunchAttributesAreSafe, hunchTreeAttributesAreSafe, safeOverlayTree } from "../core/overlaySafety.js";
|
|
@@ -496,6 +496,12 @@ export function stableRepositoryName(root) {
|
|
|
496
496
|
return `git:${roots.join("+")}`;
|
|
497
497
|
return basename(mainWorktreeRoot(root));
|
|
498
498
|
}
|
|
499
|
+
function observeGit(observer, event) {
|
|
500
|
+
try {
|
|
501
|
+
observer?.(event);
|
|
502
|
+
}
|
|
503
|
+
catch { /* observation cannot change a Git outcome */ }
|
|
504
|
+
}
|
|
499
505
|
const CAPTURE_REMOTE_TIMEOUT_MS = 15_000;
|
|
500
506
|
const READ_REMOTE_TIMEOUT_MS = 5_000;
|
|
501
507
|
// A capture can spend roughly 90s in commit + bounded merge/push/retry seams.
|
|
@@ -698,6 +704,11 @@ export function commitAndPushHunch(hunchDir, message, opts) {
|
|
|
698
704
|
}
|
|
699
705
|
if (!committed)
|
|
700
706
|
return null;
|
|
707
|
+
if (opts.observe) {
|
|
708
|
+
const commitSha = headShaWithEnv(hunchDir, env);
|
|
709
|
+
if (/^(?:[a-f0-9]{40}|[a-f0-9]{64})$/.test(commitSha))
|
|
710
|
+
observeGit(opts.observe, { kind: "committed", commitSha });
|
|
711
|
+
}
|
|
701
712
|
if (opts.push !== false) {
|
|
702
713
|
// The overlay remote is mutable process state. Re-prove the publication
|
|
703
714
|
// boundary after the local commit and BEFORE pull: hooks or another
|
|
@@ -715,7 +726,7 @@ export function commitAndPushHunch(hunchDir, message, opts) {
|
|
|
715
726
|
// proof immediately before its push seam. Repeating the same expensive proof
|
|
716
727
|
// twice here adds no intervening mutation boundary and materially slows large
|
|
717
728
|
// graph refreshes on process-spawn-sensitive platforms such as Windows.
|
|
718
|
-
if (pushWithOneRemoteAdvanceRetry(hunchDir, env, opts.protectedRepoRoot, CAPTURE_REMOTE_TIMEOUT_MS, opts.remote))
|
|
729
|
+
if (pushWithOneRemoteAdvanceRetry(hunchDir, env, opts.protectedRepoRoot, CAPTURE_REMOTE_TIMEOUT_MS, opts.remote, opts.observe))
|
|
719
730
|
return "pushed";
|
|
720
731
|
}
|
|
721
732
|
return "committed";
|
|
@@ -828,7 +839,13 @@ function isDerivedStoreArtifact(relativeName) {
|
|
|
828
839
|
// `hunch serve` flushes INSIDE its cross-process write lock, so the lock file is always
|
|
829
840
|
// staged alongside the record; treating it as a violation made every served write skip
|
|
830
841
|
// the commit quietly and report durability "local" forever (1.26.0/1.26.1).
|
|
831
|
-
|| relativeName === "write.lock"
|
|
842
|
+
|| relativeName === "write.lock"
|
|
843
|
+
// The post-merge hook's detected-but-unconfirmed repair queue and its
|
|
844
|
+
// rejected-match tombstones (repairqueue.ts): clone-local scratch, never a
|
|
845
|
+
// memory record — must never ride a public flush's `git add .` or an
|
|
846
|
+
// overlay's force-add allowlist into shared/pushed memory.
|
|
847
|
+
|| relativeName === "pending-commit-repairs.json"
|
|
848
|
+
|| relativeName === "dropped-commit-repairs.json";
|
|
832
849
|
}
|
|
833
850
|
/** Enumerate ordinary JSON files already contained under an overlay. Push-capable
|
|
834
851
|
* stores force-add this exact allowlist so remote .gitignore, info/exclude, or an
|
|
@@ -1328,7 +1345,91 @@ function upstreamSha(hunchDir, env) {
|
|
|
1328
1345
|
return "";
|
|
1329
1346
|
}
|
|
1330
1347
|
}
|
|
1331
|
-
|
|
1348
|
+
/** Keep observation output limits out of the primary Git process. A bounded
|
|
1349
|
+
* exec pipe can terminate an otherwise successful multi-ref push on overflow.
|
|
1350
|
+
* Spool privately for the duration of the command, read at most 1 MB afterward,
|
|
1351
|
+
* and discard oversized/unavailable output without changing its exit result. */
|
|
1352
|
+
function runObservedPush(args, env, timeoutMs, observe) {
|
|
1353
|
+
if (!observe) {
|
|
1354
|
+
execFileSync("git", args, { stdio: "ignore", env, timeout: timeoutMs });
|
|
1355
|
+
return null;
|
|
1356
|
+
}
|
|
1357
|
+
let directory, descriptor;
|
|
1358
|
+
try {
|
|
1359
|
+
directory = mkdtempSync(join(tmpdir(), "hunch-push-observation-"));
|
|
1360
|
+
descriptor = openSync(join(directory, "stdout"), "wx+", 0o600);
|
|
1361
|
+
}
|
|
1362
|
+
catch {
|
|
1363
|
+
if (directory) {
|
|
1364
|
+
try {
|
|
1365
|
+
rmSync(directory, { recursive: true, force: true });
|
|
1366
|
+
}
|
|
1367
|
+
catch { }
|
|
1368
|
+
}
|
|
1369
|
+
execFileSync("git", args, { stdio: "ignore", env, timeout: timeoutMs });
|
|
1370
|
+
return null;
|
|
1371
|
+
}
|
|
1372
|
+
try {
|
|
1373
|
+
execFileSync("git", args, { stdio: ["ignore", descriptor, "ignore"], env, timeout: timeoutMs });
|
|
1374
|
+
try {
|
|
1375
|
+
const buffer = Buffer.alloc(1_000_001);
|
|
1376
|
+
const bytes = readSync(descriptor, buffer, 0, buffer.length, 0);
|
|
1377
|
+
return bytes <= 1_000_000 ? buffer.subarray(0, bytes).toString("utf8") : null;
|
|
1378
|
+
}
|
|
1379
|
+
catch {
|
|
1380
|
+
return null;
|
|
1381
|
+
}
|
|
1382
|
+
}
|
|
1383
|
+
finally {
|
|
1384
|
+
if (descriptor !== undefined) {
|
|
1385
|
+
try {
|
|
1386
|
+
closeSync(descriptor);
|
|
1387
|
+
}
|
|
1388
|
+
catch { }
|
|
1389
|
+
}
|
|
1390
|
+
try {
|
|
1391
|
+
rmSync(directory, { recursive: true, force: true });
|
|
1392
|
+
}
|
|
1393
|
+
catch { /* no effect on the Git outcome */ }
|
|
1394
|
+
}
|
|
1395
|
+
}
|
|
1396
|
+
/** Porcelain identifies the destination Git actually used, preserving legacy
|
|
1397
|
+
* push refspecs. New/up-to-date refs lack an OID, so confirm that exact remote
|
|
1398
|
+
* ref with a bounded read. Never infer publication from a post-push local HEAD. */
|
|
1399
|
+
function observePushResult(hunchDir, env, output, observer) {
|
|
1400
|
+
let destination = "", count = 0;
|
|
1401
|
+
const deadline = Date.now() + 2_000;
|
|
1402
|
+
for (const line of output.split("\n")) {
|
|
1403
|
+
if (line.startsWith("To ")) {
|
|
1404
|
+
destination = line.slice(3);
|
|
1405
|
+
continue;
|
|
1406
|
+
}
|
|
1407
|
+
const status = /^([ *+=])\t[^\t]+:(refs\/[^\t]+)\t(.+)$/.exec(line);
|
|
1408
|
+
if (!status || ++count > 32)
|
|
1409
|
+
continue;
|
|
1410
|
+
const ref = status[2];
|
|
1411
|
+
if (/[\x00-\x20\x7f]/.test(ref))
|
|
1412
|
+
continue;
|
|
1413
|
+
const range = /^(?:[a-f0-9]{40}|[a-f0-9]{64})\.{2,3}([a-f0-9]{40}|[a-f0-9]{64})(?: |$)/.exec(status[3]);
|
|
1414
|
+
if (range) {
|
|
1415
|
+
observeGit(observer, { kind: "published", commitSha: range[1], ref, basis: "push-status" });
|
|
1416
|
+
continue;
|
|
1417
|
+
}
|
|
1418
|
+
if (!destination || /[\x00-\x1f\x7f]/.test(destination) || Date.now() >= deadline)
|
|
1419
|
+
continue;
|
|
1420
|
+
try {
|
|
1421
|
+
const result = execFileSync("git", ["-C", hunchDir, "ls-remote", "--refs", "--exit-code", "--", destination, ref], {
|
|
1422
|
+
env, encoding: "utf8", stdio: ["ignore", "pipe", "ignore"], timeout: Math.max(1, deadline - Date.now()), maxBuffer: 16_384,
|
|
1423
|
+
}).trim();
|
|
1424
|
+
const rows = result.split("\n").filter(row => row.split("\t")[1] === ref);
|
|
1425
|
+
const commitSha = rows.length === 1 ? rows[0].split("\t")[0] : "";
|
|
1426
|
+
if (/^(?:[a-f0-9]{40}|[a-f0-9]{64})$/.test(commitSha))
|
|
1427
|
+
observeGit(observer, { kind: "published", commitSha, ref, basis: "remote-ref-confirmed" });
|
|
1428
|
+
}
|
|
1429
|
+
catch { /* no exact publication proof; the original push outcome stands */ }
|
|
1430
|
+
}
|
|
1431
|
+
}
|
|
1432
|
+
function tryPush(hunchDir, env, timeoutMs, contract, observe) {
|
|
1332
1433
|
if (!contractReady(contract))
|
|
1333
1434
|
return false;
|
|
1334
1435
|
const hooksDir = disabledHooksDir(hunchDir);
|
|
@@ -1337,11 +1438,17 @@ function tryPush(hunchDir, env, timeoutMs, contract) {
|
|
|
1337
1438
|
try {
|
|
1338
1439
|
if (contract && !setContractUpstream(hunchDir, contract, env))
|
|
1339
1440
|
return false;
|
|
1441
|
+
const observeArgs = observe ? ["-c", "core.abbrev=no"] : [];
|
|
1442
|
+
const porcelain = observe ? ["--porcelain"] : [];
|
|
1340
1443
|
const args = contract
|
|
1341
|
-
? ["-C", hunchDir, "-c", `core.hooksPath=${hooksDir}`, "push", "--receive-pack=git-receive-pack", contract.pushUrl, `HEAD:${contract.ref}`]
|
|
1342
|
-
: ["-C", hunchDir, "-c", `core.hooksPath=${hooksDir}`, "push"];
|
|
1343
|
-
|
|
1344
|
-
|
|
1444
|
+
? ["-C", hunchDir, "-c", `core.hooksPath=${hooksDir}`, ...observeArgs, "push", ...porcelain, "--receive-pack=git-receive-pack", contract.pushUrl, `HEAD:${contract.ref}`]
|
|
1445
|
+
: ["-C", hunchDir, "-c", `core.hooksPath=${hooksDir}`, ...observeArgs, "push", ...porcelain];
|
|
1446
|
+
const output = runObservedPush(args, contract ? boundedTeamEnv(env) : env, timeoutMs, !!observe);
|
|
1447
|
+
if (!contractReady(contract))
|
|
1448
|
+
return false;
|
|
1449
|
+
if (observe && output !== null)
|
|
1450
|
+
observePushResult(hunchDir, contract ? boundedTeamEnv(env) : env, output, observe);
|
|
1451
|
+
return true;
|
|
1345
1452
|
}
|
|
1346
1453
|
catch {
|
|
1347
1454
|
return false;
|
|
@@ -1353,7 +1460,7 @@ function tryPush(hunchDir, env, timeoutMs, contract) {
|
|
|
1353
1460
|
* remote), proves that remote has no refs, snapshots its identity across the
|
|
1354
1461
|
* network check, and uses a non-force push so a concurrent first writer wins
|
|
1355
1462
|
* safely instead of being overwritten. */
|
|
1356
|
-
function establishEmptyRemoteUpstream(hunchDir, env, protectedRepoRoot, timeoutMs, contract) {
|
|
1463
|
+
function establishEmptyRemoteUpstream(hunchDir, env, protectedRepoRoot, timeoutMs, contract, observe) {
|
|
1357
1464
|
if (unsafeOverlayPublication(hunchDir, protectedRepoRoot)
|
|
1358
1465
|
|| !hunchWorktreeClean(hunchDir, env)
|
|
1359
1466
|
|| !contractReady(contract))
|
|
@@ -1365,7 +1472,7 @@ function establishEmptyRemoteUpstream(hunchDir, env, protectedRepoRoot, timeoutM
|
|
|
1365
1472
|
// Exact URL + exact canonical ref + non-force push. If another teammate wins
|
|
1366
1473
|
// the first-writer race after the empty proof, Git rejects this safely and the
|
|
1367
1474
|
// bounded retry path fetches/merges that winner.
|
|
1368
|
-
return tryPush(hunchDir, env, timeoutMs, contract);
|
|
1475
|
+
return tryPush(hunchDir, env, timeoutMs, contract, observe);
|
|
1369
1476
|
}
|
|
1370
1477
|
let branch = "";
|
|
1371
1478
|
let remotes = [];
|
|
@@ -1424,11 +1531,9 @@ function establishEmptyRemoteUpstream(hunchDir, env, protectedRepoRoot, timeoutM
|
|
|
1424
1531
|
const hooksDir = disabledHooksDir(hunchDir);
|
|
1425
1532
|
if (!hooksDir)
|
|
1426
1533
|
return false;
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
env,
|
|
1430
|
-
timeout: timeoutMs,
|
|
1431
|
-
});
|
|
1534
|
+
const output = runObservedPush(["-C", hunchDir, "-c", `core.hooksPath=${hooksDir}`, ...(observe ? ["-c", "core.abbrev=no"] : []), "push", ...(observe ? ["--porcelain"] : []), "--set-upstream", remote, `HEAD:refs/heads/${branch}`], env, timeoutMs, !!observe);
|
|
1535
|
+
if (observe && output !== null)
|
|
1536
|
+
observePushResult(hunchDir, env, output, observe);
|
|
1432
1537
|
return true;
|
|
1433
1538
|
}
|
|
1434
1539
|
catch {
|
|
@@ -1438,15 +1543,15 @@ function establishEmptyRemoteUpstream(hunchDir, env, protectedRepoRoot, timeoutM
|
|
|
1438
1543
|
/** Push once, then retry exactly once only when a bounded pull proves the upstream
|
|
1439
1544
|
* advanced during the first push seam. Offline/auth/hook failures with an unchanged
|
|
1440
1545
|
* upstream never loop, and every remote mutation re-proves the publication boundary. */
|
|
1441
|
-
function pushWithOneRemoteAdvanceRetry(hunchDir, env, protectedRepoRoot, timeoutMs, contract) {
|
|
1546
|
+
function pushWithOneRemoteAdvanceRetry(hunchDir, env, protectedRepoRoot, timeoutMs, contract, observe) {
|
|
1442
1547
|
if (unsafeOverlayPublication(hunchDir, protectedRepoRoot) || !contractReady(contract))
|
|
1443
1548
|
return false;
|
|
1444
1549
|
const before = contract
|
|
1445
1550
|
? gitSafeWithEnv(["rev-parse", "--verify", TEAM_FETCH_REF], hunchDir, env)
|
|
1446
1551
|
: upstreamSha(hunchDir, env);
|
|
1447
1552
|
if (!before)
|
|
1448
|
-
return establishEmptyRemoteUpstream(hunchDir, env, protectedRepoRoot, timeoutMs, contract);
|
|
1449
|
-
if (tryPush(hunchDir, env, timeoutMs, contract))
|
|
1553
|
+
return establishEmptyRemoteUpstream(hunchDir, env, protectedRepoRoot, timeoutMs, contract, observe);
|
|
1554
|
+
if (tryPush(hunchDir, env, timeoutMs, contract, observe))
|
|
1450
1555
|
return true;
|
|
1451
1556
|
if (unsafeOverlayPublication(hunchDir, protectedRepoRoot) || !contractReady(contract))
|
|
1452
1557
|
return false;
|
|
@@ -1458,7 +1563,7 @@ function pushWithOneRemoteAdvanceRetry(hunchDir, env, protectedRepoRoot, timeout
|
|
|
1458
1563
|
return false;
|
|
1459
1564
|
if (unsafeOverlayPublication(hunchDir, protectedRepoRoot) || !contractReady(contract))
|
|
1460
1565
|
return false;
|
|
1461
|
-
return tryPush(hunchDir, env, timeoutMs, contract);
|
|
1566
|
+
return tryPush(hunchDir, env, timeoutMs, contract, observe);
|
|
1462
1567
|
}
|
|
1463
1568
|
export function pullHunchStatus(hunchDir, opts = {}) {
|
|
1464
1569
|
const env = foreignRepoEnv({
|
|
@@ -2068,6 +2173,89 @@ export function commitChanges(sha, cwd) {
|
|
|
2068
2173
|
}
|
|
2069
2174
|
return out;
|
|
2070
2175
|
}
|
|
2176
|
+
/** Default cap on how many commits mergeRangeChanges will inspect. A `git
|
|
2177
|
+
* pull` bringing in an unusually large number of commits (hundreds) would
|
|
2178
|
+
* otherwise spawn one `diff-tree` per commit with no bound — cheap insurance
|
|
2179
|
+
* against an unbounded background hook run, not a hard product limit. */
|
|
2180
|
+
const DEFAULT_MERGE_RANGE_CAP = 500;
|
|
2181
|
+
/** Every commit newly reachable in `oldRef..newRef` (capped to the most
|
|
2182
|
+
* recent `maxCount`), with the files each one changed (rename-aware, via
|
|
2183
|
+
* commitChanges). Used to find the commit a squash-merge produced from a
|
|
2184
|
+
* set of now-orphaned source-branch commits. */
|
|
2185
|
+
export function mergeRangeChanges(oldRef, newRef, cwd, maxCount = DEFAULT_MERGE_RANGE_CAP) {
|
|
2186
|
+
const shas = gitSafe(["rev-list", "--abbrev-commit", `--max-count=${maxCount}`, `${oldRef}..${newRef}`], cwd).split("\n").filter(Boolean).reverse();
|
|
2187
|
+
return shas.map((sha) => ({
|
|
2188
|
+
sha,
|
|
2189
|
+
// A deletion is never a sensible repair target — a decision's provenance
|
|
2190
|
+
// should never resolve to the commit that removed the file it's about.
|
|
2191
|
+
files: commitChanges(sha, cwd).filter((c) => c.status !== "deleted").map((c) => c.after ?? c.before).filter((f) => !!f),
|
|
2192
|
+
}));
|
|
2193
|
+
}
|
|
2194
|
+
/** Batched existence check for many commit shas at once — one `git cat-file
|
|
2195
|
+
* --batch-check` process instead of one `rev-parse --verify` per sha. Used
|
|
2196
|
+
* by drift.ts's default commitResolvable so hunch drift/heal/doctor spawn a
|
|
2197
|
+
* single git process regardless of how many decisions carry a commit, not
|
|
2198
|
+
* one per decision. Returns the subset of `shas` that resolve to a real
|
|
2199
|
+
* commit object, or `null` if the check itself failed to run (not a git
|
|
2200
|
+
* repo, git missing, timeout) — distinct from an empty set, which means
|
|
2201
|
+
* "checked, and none of them resolve." A caller that collapsed those two
|
|
2202
|
+
* would flag every commit as unresolvable on a transient environment error
|
|
2203
|
+
* instead of failing open. */
|
|
2204
|
+
export function commitsExist(shas, cwd) {
|
|
2205
|
+
const resolvable = new Set();
|
|
2206
|
+
if (!shas.length)
|
|
2207
|
+
return resolvable;
|
|
2208
|
+
// Same shape guard as commitRepairStatus. Without it, a malformed value
|
|
2209
|
+
// embedding its own newline would count as more input lines than entries
|
|
2210
|
+
// in `shas`, shifting every later sha's output line and misclassifying it.
|
|
2211
|
+
const valid = shas.filter((sha) => /^[0-9a-f]{7,64}$/i.test(sha));
|
|
2212
|
+
if (!valid.length)
|
|
2213
|
+
return resolvable;
|
|
2214
|
+
let raw;
|
|
2215
|
+
try {
|
|
2216
|
+
raw = execFileSync("git", ["-C", cwd, "cat-file", "--batch-check"], {
|
|
2217
|
+
encoding: "utf8",
|
|
2218
|
+
input: valid.map((sha) => `${sha}^{commit}\n`).join(""),
|
|
2219
|
+
stdio: ["pipe", "pipe", "ignore"],
|
|
2220
|
+
timeout: 10_000,
|
|
2221
|
+
});
|
|
2222
|
+
}
|
|
2223
|
+
catch {
|
|
2224
|
+
return null;
|
|
2225
|
+
}
|
|
2226
|
+
const lines = raw.split("\n").filter(Boolean);
|
|
2227
|
+
for (let i = 0; i < valid.length && i < lines.length; i++) {
|
|
2228
|
+
if (!lines[i].endsWith(" missing"))
|
|
2229
|
+
resolvable.add(valid[i]);
|
|
2230
|
+
}
|
|
2231
|
+
return resolvable;
|
|
2232
|
+
}
|
|
2233
|
+
/** Classifies `commit` relative to `ref` for squash-merge repair eligibility.
|
|
2234
|
+
* "orphaned" (exists in this repo, but is not an ancestor of `ref`) is the
|
|
2235
|
+
* ONLY status planCommitRepair should ever act on. "unresolvable" (the
|
|
2236
|
+
* commit doesn't exist here at all) is explicitly out of repair scope —
|
|
2237
|
+
* drift.ts's commit-unresolvable kind is what flags that case, because
|
|
2238
|
+
* there's no reliable signal left to match a replacement against. "unknown"
|
|
2239
|
+
* (the git command itself failed to run — timeout, git missing) must never
|
|
2240
|
+
* be treated as repair-eligible; collapsing "false" and "error" into one
|
|
2241
|
+
* boolean is exactly the bug this type exists to prevent. */
|
|
2242
|
+
export function commitRepairStatus(commit, ref, cwd) {
|
|
2243
|
+
if (!/^[0-9a-f]{7,64}$/i.test(commit))
|
|
2244
|
+
return "unresolvable";
|
|
2245
|
+
try {
|
|
2246
|
+
execFileSync("git", ["-C", cwd, "rev-parse", "--verify", "--quiet", `${commit}^{commit}`], { stdio: "ignore", timeout: 5_000 });
|
|
2247
|
+
}
|
|
2248
|
+
catch (e) {
|
|
2249
|
+
return typeof e.status === "number" ? "unresolvable" : "unknown";
|
|
2250
|
+
}
|
|
2251
|
+
try {
|
|
2252
|
+
execFileSync("git", ["-C", cwd, "merge-base", "--is-ancestor", commit, ref], { stdio: "ignore", timeout: 5_000 });
|
|
2253
|
+
return "current";
|
|
2254
|
+
}
|
|
2255
|
+
catch (e) {
|
|
2256
|
+
return e.status === 1 ? "orphaned" : "unknown";
|
|
2257
|
+
}
|
|
2258
|
+
}
|
|
2071
2259
|
/** Machine-generated paths that carry no design "why" — lockfiles, build output,
|
|
2072
2260
|
* vendored deps, snapshots, source maps. Excluded from synthesis diffs via git
|
|
2073
2261
|
* pathspec BEFORE git assembles/orders the patch: a huge lockfile sorts ahead of
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Deterministic text scan for Helm's `define`/`include`/`template` Go-template
|
|
3
|
+
* actions — NOT a Go-template parser, and NOT a tree-sitter query. tree-sitter-yaml
|
|
4
|
+
* has no notion of `{{ }}` content: parsing `{{ include "x" . }}` inside a real
|
|
5
|
+
* YAML mapping value produces only stray `{` flow-mapping-open tokens, with the
|
|
6
|
+
* enclosed text lost to ERROR recovery (verified directly against this repo's
|
|
7
|
+
* tree-sitter-yaml bundle). There is nothing for a tree-sitter query to capture,
|
|
8
|
+
* so this runs as a sidecar text scan over the raw source — invoked only for
|
|
9
|
+
* files under a Helm chart (indexer.ts's chart-root detection), never for
|
|
10
|
+
* arbitrary YAML.
|
|
11
|
+
*
|
|
12
|
+
* Known bounded limitation: this is a token scan, not a full parser. A literal
|
|
13
|
+
* `}}` inside a quoted argument, or an include/define-shaped string inside a
|
|
14
|
+
* `{{/* comment *}}`, can misattribute a byte range or produce a phantom call.
|
|
15
|
+
* Both are bounded failure modes (a stray reference to a real symbol name, or a
|
|
16
|
+
* slightly-long symbol range) — the same class of accepted limitation
|
|
17
|
+
* `toleratedErrorScopes` documents for the tree-sitter grammars, not a silent gap.
|
|
18
|
+
* The chart-wide `importedFiles` widening this module's output flows through
|
|
19
|
+
* (indexer.ts) also lets a YAML alias resolve to an anchor in a sibling chart
|
|
20
|
+
* file, even though YAML anchors are properly document-scoped; this only fires
|
|
21
|
+
* when the alias has no matching anchor in its own file (i.e. only on input
|
|
22
|
+
* that's already invalid YAML on its own terms), so it's bounded, but it's a
|
|
23
|
+
* real, disclosed side effect of the chart-scoping mechanism, not something to
|
|
24
|
+
* silently rely on.
|
|
25
|
+
*/
|
|
26
|
+
import { type ParsedSymbol, type ParsedCall } from "./parse.js";
|
|
27
|
+
export interface HelmExtraction {
|
|
28
|
+
symbols: ParsedSymbol[];
|
|
29
|
+
calls: ParsedCall[];
|
|
30
|
+
}
|
|
31
|
+
export declare function extractHelmDirectives(source: string): HelmExtraction;
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import type { HunchStore } from "../store/hunchStore.js";
|
|
2
|
+
import { type Symbol, type Edge, type Component } from "../core/types.js";
|
|
3
|
+
import { type RepoScanSource, type RepoScanSourceIdentity, type RepoSourceIssue } from "./repoSource.js";
|
|
4
|
+
export interface IndexResult {
|
|
5
|
+
files: number;
|
|
6
|
+
symbols: number;
|
|
7
|
+
edges: number;
|
|
8
|
+
components: number;
|
|
9
|
+
/** Files that could not be parsed (read error / oversized / extraction error). */
|
|
10
|
+
skipped: number;
|
|
11
|
+
coverage: Array<{
|
|
12
|
+
language: string;
|
|
13
|
+
eligible: number;
|
|
14
|
+
parsed: number;
|
|
15
|
+
skipped: number;
|
|
16
|
+
reasons: Record<string, number>;
|
|
17
|
+
}>;
|
|
18
|
+
}
|
|
19
|
+
export interface RepoScan {
|
|
20
|
+
result: IndexResult;
|
|
21
|
+
symbols: Symbol[];
|
|
22
|
+
edges: Edge[];
|
|
23
|
+
components: Component[];
|
|
24
|
+
source: RepoScanSourceIdentity & {
|
|
25
|
+
content_hash: string;
|
|
26
|
+
};
|
|
27
|
+
issues: RepoSourceIssue[];
|
|
28
|
+
}
|
|
29
|
+
export interface ScanRepoOptions {
|
|
30
|
+
churn?: boolean;
|
|
31
|
+
source?: RepoScanSource;
|
|
32
|
+
}
|
|
33
|
+
export interface IndexRepoOptions {
|
|
34
|
+
churn?: boolean;
|
|
35
|
+
/** Explicit immutable source for setup paths that must never persist checkout
|
|
36
|
+
* bytes. Normal durable refreshes should use requireClean instead. */
|
|
37
|
+
source?: RepoScanSource;
|
|
38
|
+
/** Production publication paths set this to prove graph bytes came only from
|
|
39
|
+
* committed code. Library fixtures and disposable replay checkouts opt in. */
|
|
40
|
+
requireClean?: boolean;
|
|
41
|
+
/** Authoritative replay/proof paths cannot represent a partial graph. Reject
|
|
42
|
+
* every read, path, mode, or parse issue before writing derived JSON. */
|
|
43
|
+
requireComplete?: boolean;
|
|
44
|
+
}
|
|
45
|
+
/** Read-only policy evaluators must never turn an omitted source file into a
|
|
46
|
+
* false satisfied receipt. Call this after scanRepo when the consumer cannot
|
|
47
|
+
* represent partial-graph uncertainty directly. */
|
|
48
|
+
export declare function assertCompleteRepoScan(scan: RepoScan): void;
|
|
49
|
+
/** Derive the current repository graph without writing JSON or rebuilding SQLite.
|
|
50
|
+
* Read-only gates use this so checking changed code can never rewrite or publish
|
|
51
|
+
* the durable graph merely by inspecting it. Existing public graph records remain
|
|
52
|
+
* inputs for the same churn/component enrichment semantics as a persisted index. */
|
|
53
|
+
export declare function scanRepo(store: HunchStore, root: string, opts?: ScanRepoOptions): RepoScan;
|
|
54
|
+
/** Persist one pure scan into the Git-native source of truth. */
|
|
55
|
+
export declare function indexRepo(store: HunchStore, root: string, opts?: IndexRepoOptions): IndexResult;
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { type Edge, type Resource } from "../core/types.js";
|
|
2
|
+
export declare const LANDSCAPE_DISCOVERY_SCHEMA_VERSION: "hunch.landscape-discovery/1";
|
|
3
|
+
export declare const LANDSCAPE_CANDIDATE_SCHEMA_VERSION: "hunch.landscape-candidate/1";
|
|
4
|
+
export type LandscapeEvidenceKind = "package_manifest" | "git_remote" | "git_history" | "submodule_declaration" | "mcp_declaration" | "ci_declaration" | "deployment_declaration" | "api_declaration" | "migration_declaration" | "ownership_declaration" | "operations_declaration" | "dashboard_declaration" | "slo_declaration";
|
|
5
|
+
export interface LandscapeCandidateEvidence {
|
|
6
|
+
kind: LandscapeEvidenceKind;
|
|
7
|
+
sourcePath: string;
|
|
8
|
+
sourceField: string;
|
|
9
|
+
sourceRevision: string;
|
|
10
|
+
sourceContentHash: string;
|
|
11
|
+
}
|
|
12
|
+
export interface LandscapeCandidate<T extends Resource | Edge> {
|
|
13
|
+
schema: typeof LANDSCAPE_CANDIDATE_SCHEMA_VERSION;
|
|
14
|
+
authority: "candidate";
|
|
15
|
+
record: T;
|
|
16
|
+
evidence: LandscapeCandidateEvidence[];
|
|
17
|
+
candidateHash: string;
|
|
18
|
+
}
|
|
19
|
+
export type LandscapeDiscoveryIssueCode = "manifest_missing" | "manifest_invalid" | "manifest_oversized" | "manifest_mode" | "manifest_path" | "manifest_limit" | "workspace_pattern_invalid" | "package_name_missing" | "package_name_invalid" | "package_identity_conflict" | "package_dependency_invalid" | "package_dependency_limit" | "repository_identity_conflict" | "submodule_declaration_invalid" | "submodule_declaration_oversized" | "submodule_declaration_mode" | "submodule_declaration_limit" | "mcp_config_invalid" | "mcp_config_oversized" | "mcp_config_mode" | "mcp_server_name_invalid" | "mcp_declaration_invalid" | "mcp_declaration_conflict" | "mcp_declaration_limit" | "delivery_declaration_invalid" | "delivery_declaration_oversized" | "delivery_declaration_mode" | "delivery_declaration_path" | "delivery_declaration_limit" | "delivery_declaration_conflict" | "api_declaration_invalid" | "api_declaration_oversized" | "api_declaration_mode" | "api_declaration_path" | "api_declaration_limit" | "migration_declaration_invalid" | "migration_declaration_oversized" | "migration_declaration_mode" | "migration_declaration_path" | "migration_declaration_limit" | "ownership_declaration_invalid" | "ownership_declaration_oversized" | "ownership_declaration_mode" | "ownership_declaration_limit" | "operations_declaration_invalid" | "operations_declaration_oversized" | "operations_declaration_mode" | "operations_declaration_path" | "operations_declaration_limit" | "dashboard_declaration_invalid" | "dashboard_declaration_oversized" | "dashboard_declaration_mode" | "dashboard_declaration_path" | "dashboard_declaration_limit" | "slo_declaration_invalid" | "slo_declaration_oversized" | "slo_declaration_mode" | "slo_declaration_path" | "slo_declaration_limit";
|
|
20
|
+
export interface LandscapeDiscoveryIssue {
|
|
21
|
+
code: LandscapeDiscoveryIssueCode;
|
|
22
|
+
sourcePath: string;
|
|
23
|
+
sourceField: string;
|
|
24
|
+
detail: string;
|
|
25
|
+
}
|
|
26
|
+
export interface LandscapeDiscoveryResult {
|
|
27
|
+
schema: typeof LANDSCAPE_DISCOVERY_SCHEMA_VERSION;
|
|
28
|
+
authority: "candidate";
|
|
29
|
+
sourceRevision: string;
|
|
30
|
+
repositoryRootIdentity: string;
|
|
31
|
+
resources: Array<LandscapeCandidate<Resource>>;
|
|
32
|
+
relationships: Array<LandscapeCandidate<Edge>>;
|
|
33
|
+
issues: LandscapeDiscoveryIssue[];
|
|
34
|
+
discoveryHash: string;
|
|
35
|
+
}
|
|
36
|
+
/** Canonical content identity shared by discovery and its review/adoption seam. */
|
|
37
|
+
export declare function landscapeContentHash(value: unknown): string;
|
|
38
|
+
/** Discover an exact, reviewable repository candidate fragment. This function
|
|
39
|
+
* never writes Hunch graph state: candidate authority remains explicit until a
|
|
40
|
+
* normal review/capture path accepts the records. */
|
|
41
|
+
export declare function discoverRepositoryLandscape(root: string, ref?: string): LandscapeDiscoveryResult;
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import type { Edge } from "../core/types.js";
|
|
2
|
+
export type ParsedSymbolKind = "function" | "method" | "class" | "interface" | "trait" | "enum" | "type" | "variable" | "file";
|
|
3
|
+
export interface LanguageSpec {
|
|
4
|
+
/** Stable id, also used as the grammar-bundle cache key by parse.ts. */
|
|
5
|
+
id: string;
|
|
6
|
+
extensions: string[];
|
|
7
|
+
/** Cache key parse.ts's bundleFor() uses (one grammar+query pair may serve
|
|
8
|
+
* several extensions, e.g. tsx serves both .tsx and .jsx). */
|
|
9
|
+
grammarKey: string;
|
|
10
|
+
/** Lazily returns the tree-sitter Language object for this spec. */
|
|
11
|
+
loadGrammar(): unknown;
|
|
12
|
+
/** Tree-sitter query source capturing every construct this language cares about. */
|
|
13
|
+
query: string;
|
|
14
|
+
/** Node types ascendToDef() walks up to when resolving a name capture's enclosing def. */
|
|
15
|
+
defNodeTypes: Set<string>;
|
|
16
|
+
/** Query capture-name (ending in ".def") -> the ParsedSymbolKind it represents. */
|
|
17
|
+
defKindOf: Record<string, ParsedSymbolKind>;
|
|
18
|
+
/** Query capture-name (ending in ".name") -> the matching ".def" capture-name. */
|
|
19
|
+
nameToDef: Record<string, string>;
|
|
20
|
+
/** Optional semantic relationship captures. The generic parser/indexer emit
|
|
21
|
+
* these through the normal graph path; languages only describe syntax. */
|
|
22
|
+
relationKindOf?: Record<string, {
|
|
23
|
+
edgeType: Edge["type"];
|
|
24
|
+
label: string;
|
|
25
|
+
}>;
|
|
26
|
+
/** Common builtin/stdlib method names. Member calls to these (e.g. `arr.map(...)`)
|
|
27
|
+
* must NOT create call edges to unrelated repo symbols that happen to share the
|
|
28
|
+
* name (DESIGN: keep the graph clean). */
|
|
29
|
+
builtinMethods: Set<string>;
|
|
30
|
+
/** Direct-call builtins for languages such as PHP. These never become repo
|
|
31
|
+
* call edges merely because a project symbol shares the runtime name. */
|
|
32
|
+
builtinFunctions?: Set<string>;
|
|
33
|
+
/** Edge type emitted for this language's calls-list entries (e.g. YAML's
|
|
34
|
+
* alias->anchor references aren't function calls). Defaults to "calls"
|
|
35
|
+
* when omitted — every language before YAML. */
|
|
36
|
+
referenceEdgeType?: Edge["type"];
|
|
37
|
+
/** Fallback name for a ".def" capture with no paired ".name" capture (e.g.
|
|
38
|
+
* a whole-file root symbol with no natural identifier of its own). Omit
|
|
39
|
+
* for a language where every ".def" always has a name capture — parse.ts
|
|
40
|
+
* then keeps dropping unnamed defs, the current behavior for TS/Python. */
|
|
41
|
+
fallbackDefName?: (file: string) => string;
|
|
42
|
+
/** Ancestor shapes in which an ERROR node is a known limitation of THIS grammar
|
|
43
|
+
* rather than a real syntax error. parse.ts forgives an error only when some
|
|
44
|
+
* ancestor has type `node` AND that ancestor's own parent has type `parentIs` —
|
|
45
|
+
* the pair is what keeps the tolerance narrow. Omit for a language with no known
|
|
46
|
+
* grammar false positives; that spec then stays strictly fail-closed. */
|
|
47
|
+
toleratedErrorScopes?: ReadonlyArray<{
|
|
48
|
+
readonly node: string;
|
|
49
|
+
readonly parentIs: string;
|
|
50
|
+
}>;
|
|
51
|
+
/** Patterns whose presence anywhere in the source mean "this isn't actually
|
|
52
|
+
* {id} text yet — it's a template that renders to {id} later" (Go/Jinja/Helm
|
|
53
|
+
* delimiters in a .yaml file, e.g.). parse.ts still runs the real parse — a
|
|
54
|
+
* genuine anchor/symbol elsewhere in the file is still extracted, same as any
|
|
55
|
+
* other {id} file — but no longer lets the resulting ERROR nodes fail-close
|
|
56
|
+
* the whole-repo scan completeness gate the way toleratedErrorScopes protects
|
|
57
|
+
* narrower, per-grammar false positives (issue #33). Keep these narrow: a
|
|
58
|
+
* pattern that also matches ordinary, always-valid {id} syntax (e.g. GitHub
|
|
59
|
+
* Actions' `${{ }}` expressions) would silently disable the fail-closed
|
|
60
|
+
* guarantee for files that were never templated at all. */
|
|
61
|
+
templatingMarkers?: readonly RegExp[];
|
|
62
|
+
/** Extensions within this language where EVERY file is inherently a template
|
|
63
|
+
* regardless of content — unlike templatingMarkers, this never content-sniffs.
|
|
64
|
+
* The extension alone means "this file is a template, parseable must never
|
|
65
|
+
* become false for it," matching Helm's `_helpers.tpl` convention where the
|
|
66
|
+
* file may be pure Go-template text with no YAML structure of its own at all. */
|
|
67
|
+
alwaysTemplatedExtensions?: readonly string[];
|
|
68
|
+
}
|
|
69
|
+
export declare const LANGUAGES: LanguageSpec[];
|
|
70
|
+
export declare const CODE_EXTENSIONS: string[];
|
|
71
|
+
export declare function languageFor(file: string): LanguageSpec | null;
|
|
72
|
+
/** Prose formats worth drafting a decision from even though no LanguageSpec parses
|
|
73
|
+
* them — no grammar, no symbols/edges, just eligible input to synthesis (issue #12). */
|
|
74
|
+
export declare const PROSE_EXTENSIONS: string[];
|
|
75
|
+
/** Broader than languageFor: "is this worth reasoning about" (synthesis input)
|
|
76
|
+
* rather than "can tree-sitter parse this" (symbol/dependency extraction). */
|
|
77
|
+
export declare function isSubstantive(file: string): boolean;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type TreeSitterParser from "tree-sitter";
|
|
2
|
+
export interface NativeTreeSitterRuntime {
|
|
3
|
+
Parser: typeof TreeSitterParser;
|
|
4
|
+
typescript: unknown;
|
|
5
|
+
tsx: unknown;
|
|
6
|
+
python: unknown;
|
|
7
|
+
go: unknown;
|
|
8
|
+
php: unknown;
|
|
9
|
+
yaml: unknown;
|
|
10
|
+
}
|
|
11
|
+
/** Load all native tree-sitter addons (the parser runtime + every grammar) from
|
|
12
|
+
* process-owned temp copies. Windows keeps loaded `.node` files locked for the
|
|
13
|
+
* process lifetime; redirecting the upstream loaders means npm can replace the
|
|
14
|
+
* installed package during an active MCP session without killing that session
|
|
15
|
+
* or falling back to a stale binary. */
|
|
16
|
+
export declare function loadNativeTreeSitter(): NativeTreeSitterRuntime;
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import type { Edge } from "../core/types.js";
|
|
2
|
+
import { type ParsedSymbolKind } from "./languages.js";
|
|
3
|
+
export type { ParsedSymbolKind } from "./languages.js";
|
|
4
|
+
export interface ParsedSymbol {
|
|
5
|
+
name: string;
|
|
6
|
+
kind: ParsedSymbolKind;
|
|
7
|
+
startByte: number;
|
|
8
|
+
endByte: number;
|
|
9
|
+
loc: number;
|
|
10
|
+
bodyText: string;
|
|
11
|
+
}
|
|
12
|
+
export interface ParsedCall {
|
|
13
|
+
callee: string;
|
|
14
|
+
atByte: number;
|
|
15
|
+
endByte: number;
|
|
16
|
+
/** true for `x.foo()` (property access), false for a direct `foo()` call. */
|
|
17
|
+
member: boolean;
|
|
18
|
+
}
|
|
19
|
+
export interface ParsedRelation {
|
|
20
|
+
target: string;
|
|
21
|
+
atByte: number;
|
|
22
|
+
endByte: number;
|
|
23
|
+
edgeType: Edge["type"];
|
|
24
|
+
label: string;
|
|
25
|
+
}
|
|
26
|
+
export interface ParsedFile {
|
|
27
|
+
symbols: ParsedSymbol[];
|
|
28
|
+
imports: string[];
|
|
29
|
+
calls: ParsedCall[];
|
|
30
|
+
relations: ParsedRelation[];
|
|
31
|
+
namespace: string | null;
|
|
32
|
+
parseable: boolean;
|
|
33
|
+
}
|
|
34
|
+
/** Cap on a stored symbol's bodyText — large enough for review context, small
|
|
35
|
+
* enough that a huge function/file doesn't bloat every JSON symbol record. */
|
|
36
|
+
export declare const MAX_BODY_TEXT_CHARS = 4000;
|
|
37
|
+
export declare function parseSource(file: string, source: string): ParsedFile | null;
|
|
38
|
+
/** Map each call site to the innermost symbol whose byte-range contains it.
|
|
39
|
+
* Keyed by the symbol's position (index) in `parsed.symbols` — NOT its
|
|
40
|
+
* startByte, which is not a reliable per-symbol identity: a language whose
|
|
41
|
+
* extractor merges a synthetic whole-file symbol with independently-derived
|
|
42
|
+
* symbols (e.g. YAML's fallback-root synthetic symbol alongside Helm's
|
|
43
|
+
* regex-derived `define` blocks) can produce two distinct symbols that both
|
|
44
|
+
* start at byte 0. Indexing by array position is unique by construction,
|
|
45
|
+
* regardless of byte overlap — the caller must consume the exact same
|
|
46
|
+
* `parsed.symbols` array (or an equivalently-ordered copy) to look up a
|
|
47
|
+
* symbol by the index this function returns. The value maps callee name ->
|
|
48
|
+
* `memberOnly` (true iff every occurrence was a `x.foo()` member call, never
|
|
49
|
+
* a direct `foo()`), so the indexer can resolve member calls conservatively. */
|
|
50
|
+
export declare function attributeCalls(parsed: ParsedFile): Map<number, Map<string, boolean>>;
|
|
51
|
+
/** Map static semantic relationships (extends/implements/trait use) to the
|
|
52
|
+
* innermost enclosing declaration using the same stable symbol-index identity
|
|
53
|
+
* as call attribution. */
|
|
54
|
+
export declare function attributeRelations(parsed: ParsedFile): Map<number, ParsedRelation[]>;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
export interface PhpPsr4Mapping {
|
|
2
|
+
prefix: string;
|
|
3
|
+
directories: string[];
|
|
4
|
+
}
|
|
5
|
+
export interface PhpUseImport {
|
|
6
|
+
fqn: string;
|
|
7
|
+
alias: string;
|
|
8
|
+
}
|
|
9
|
+
/** Extract bounded, repository-relative PSR-4 mappings from composer.json.
|
|
10
|
+
* autoload-dev is included because test symbols and changed-file history are
|
|
11
|
+
* first-class graph inputs. Unsafe/absolute mapping roots are ignored. */
|
|
12
|
+
export declare function composerPsr4Mappings(value: unknown): PhpPsr4Mapping[];
|
|
13
|
+
/** Parse ordinary, aliased, comma-separated, function/const, and grouped PHP
|
|
14
|
+
* namespace-use declarations. Dynamic namespace expressions do not exist in
|
|
15
|
+
* this syntax, so every returned import is exact. */
|
|
16
|
+
export declare function parsePhpUseDeclaration(raw: string): PhpUseImport[];
|
|
17
|
+
export interface PhpResolvedReference {
|
|
18
|
+
symbolName: string;
|
|
19
|
+
files: string[];
|
|
20
|
+
}
|
|
21
|
+
export declare function resolvePhpReference(reference: string, namespace: string | null, useDeclarations: readonly string[], mappings: readonly PhpPsr4Mapping[], files: ReadonlySet<string>): PhpResolvedReference;
|
|
22
|
+
/** Resolve one parser import capture. A namespace-use declaration can map to
|
|
23
|
+
* multiple local files; object/static class references map through aliases and
|
|
24
|
+
* current namespace; static include/require paths stay fail-closed. */
|
|
25
|
+
export declare function resolvePhpImportTargets(fromFile: string, capture: string, namespace: string | null, useDeclarations: readonly string[], mappings: readonly PhpPsr4Mapping[], files: ReadonlySet<string>): string[];
|
|
26
|
+
export declare function phpExternalSpecifier(capture: string): string | null;
|