@davesheffer/hunch 1.31.1 → 1.32.1
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 +2 -1
- 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 +606 -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 +128 -0
- package/dist/cli/update.d.ts +14 -0
- package/dist/client/state.d.ts +248 -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/hookObservations.d.ts +9 -0
- package/dist/core/hookObservations.js +33 -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/stateDelivery.d.ts +78 -0
- package/dist/core/stateRecords.d.ts +383 -0
- 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 +28 -0
- package/dist/core/taskReportEvidence.js +272 -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/health.js +32 -2
- 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 +74 -22
- package/dist/mcp/taskReportTools.d.ts +188 -0
- package/dist/mcp/taskReportTools.js +147 -0
- package/dist/serve/app.d.ts +36 -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/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 +6 -3
- package/dist/store/jsonStore.d.ts +125 -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/stateCapture.d.ts +15 -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 +79 -0
- package/dist/taskReports.js +69 -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
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/** Runtime evidence that a host actually delivered a lifecycle event to Hunch's
|
|
2
|
+
* hook. Configuration proves wiring; only an observed event proves delivery.
|
|
3
|
+
* One row per (provider, normalized event), machine-local, outside the
|
|
4
|
+
* rebuildable index. Recording is best-effort: a hook must never fail on it. */
|
|
5
|
+
import { existsSync } from "node:fs";
|
|
6
|
+
import { join } from "node:path";
|
|
7
|
+
import { withServedDatabase } from "./served.js";
|
|
8
|
+
import { HUNCH_VERSION } from "./version.js";
|
|
9
|
+
function ensureTable(db) {
|
|
10
|
+
db.exec(`CREATE TABLE IF NOT EXISTS hook_observations (
|
|
11
|
+
provider TEXT NOT NULL, event TEXT NOT NULL, at TEXT NOT NULL, version TEXT NOT NULL,
|
|
12
|
+
PRIMARY KEY (provider, event)
|
|
13
|
+
)`);
|
|
14
|
+
}
|
|
15
|
+
/** Never throws (con_03a0b94b2e): a missing ledger costs evidence, not the edit. */
|
|
16
|
+
export function recordHookObservation(root, provider, event) {
|
|
17
|
+
try {
|
|
18
|
+
withServedDatabase(root, db => {
|
|
19
|
+
ensureTable(db);
|
|
20
|
+
db.prepare("INSERT OR REPLACE INTO hook_observations VALUES (?, ?, ?, ?)").run(provider, event, new Date().toISOString(), HUNCH_VERSION);
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
catch { /* evidence is optional; the hook response is not */ }
|
|
24
|
+
}
|
|
25
|
+
export function readHookObservations(root) {
|
|
26
|
+
if (!existsSync(join(root, ".hunch-cache", "served.db")))
|
|
27
|
+
return [];
|
|
28
|
+
return withServedDatabase(root, db => {
|
|
29
|
+
ensureTable(db);
|
|
30
|
+
return db.prepare("SELECT provider, event, at, version FROM hook_observations ORDER BY at DESC").all();
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
//# sourceMappingURL=hookObservations.js.map
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/** Decide whether this injection should be the FULL grounding block or a delta
|
|
2
|
+
* one-liner. Records the content hash as a side effect (so the next identical
|
|
3
|
+
* call dedups). Never throws. */
|
|
4
|
+
export declare function injectionMode(sessionId: string | undefined, key: string, content: string): "full" | "delta";
|
|
5
|
+
/** Forget everything injected into a session. Compaction summarizes injected
|
|
6
|
+
* grounding out of the agent's context while the dedup map still says
|
|
7
|
+
* "delivered" — so on PreCompact / SessionStart[source=compact] the map must
|
|
8
|
+
* reset, or post-compact edits get delta one-liners against grounding the
|
|
9
|
+
* agent no longer has. Never throws (same posture as injectionMode). */
|
|
10
|
+
export declare function resetSessionInjections(sessionId: string | undefined): void;
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/** Decision logic for the strict agent hook (`hunch hook`): does editing a file
|
|
2
|
+
* hit a BLOCKING invariant — directly (its scope matches) or via blast radius
|
|
3
|
+
* (a guarded dependency)? Extracted from the CLI so it is unit-testable against a
|
|
4
|
+
* real HunchStore, and so the model-facing refusal text lives in one audited
|
|
5
|
+
* place. Mirrors the `hunch check` direct/near logic, blocking-severity only. */
|
|
6
|
+
import type { HunchStore } from "../store/hunchStore.js";
|
|
7
|
+
export interface BlockingHit {
|
|
8
|
+
/** Refusal text fed back to the model as the deny reason. Deliberately states
|
|
9
|
+
* ONLY the invariant — never how to disable the guard, so an autonomous agent
|
|
10
|
+
* cannot be coached into lowering enforcement to get its edit through. */
|
|
11
|
+
reason: string;
|
|
12
|
+
/** Structured provenance for the catch-log (core/events.ts). `reason` alone can't
|
|
13
|
+
* be aggregated; these carry which rule earned its keep. A constraint block sets
|
|
14
|
+
* `constraint`; a veto block sets `decision`. IDS ONLY — never rule TEXT: the hook
|
|
15
|
+
* writes to the PUBLIC repo's events.log but can enforce a PRIVATE overlay rule, so
|
|
16
|
+
* the log must stay leak-safe (an opaque id resolves to text only where that record
|
|
17
|
+
* is visible). No subject/object/assert here — a plain block has no conformance
|
|
18
|
+
* predicate to report (dec_6253f7e6d6). */
|
|
19
|
+
event: {
|
|
20
|
+
kind: "constraint" | "veto";
|
|
21
|
+
constraint?: string;
|
|
22
|
+
decision?: string;
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
/** Return a BlockingHit if editing `file` (repo-relative) hits a blocking
|
|
26
|
+
* invariant directly or through its blast radius, else null. `proposedAddedLines`
|
|
27
|
+
* are the lines the edit would ADD: a CONTENT-MATCHED invariant (one carrying a
|
|
28
|
+
* `match` regex) denies ONLY when those lines actually trip it — so it stays quiet
|
|
29
|
+
* on edits that don't break the rule, instead of blocking every edit in scope
|
|
30
|
+
* (dec_e0a36efbf5). Scope-only invariants keep the blunt scope-touch behavior. */
|
|
31
|
+
export declare function blockingInScope(store: HunchStore, file: string, proposedAddedLines?: string[]): BlockingHit | null;
|
|
32
|
+
/** Return a BlockingHit if the proposed added lines for `file` re-introduce an
|
|
33
|
+
* approach an in-force decision deliberately REJECTED (a blocking veto), else null.
|
|
34
|
+
* The deny text states ONLY the decision + receipt (what was rejected, what was
|
|
35
|
+
* chosen) — never how to supersede or disable the guard, so an autonomous agent
|
|
36
|
+
* cannot be coached into reversing a decision to land its edit (dec_a466655539). */
|
|
37
|
+
/** Flatten the proposed-edit text from a PreToolUse `tool_input` across all three
|
|
38
|
+
* edit tools — Edit (`new_string`), Write (`content`), MultiEdit (`edits[].new_string`)
|
|
39
|
+
* — into candidate added lines for the Veto Guard. Empty input → []. */
|
|
40
|
+
export declare function proposedEditLines(toolInput: {
|
|
41
|
+
new_string?: string;
|
|
42
|
+
content?: string;
|
|
43
|
+
edits?: Array<{
|
|
44
|
+
new_string?: string;
|
|
45
|
+
}>;
|
|
46
|
+
} | undefined): string[];
|
|
47
|
+
export declare function vetoInScope(store: HunchStore, file: string, proposedAddedLines: string[]): BlockingHit | null;
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
export declare function shortHash(input: string, len?: number): string;
|
|
2
|
+
/** Full sha1 (used for signature_hash etc.). */
|
|
3
|
+
export declare function sha1(input: string): string;
|
|
4
|
+
/** Symbol id from file + name + kind — deterministic across re-indexes. */
|
|
5
|
+
export declare function symbolId(file: string, name: string, kind: string): string;
|
|
6
|
+
/** Component id from a stable name. */
|
|
7
|
+
export declare function componentId(name: string): string;
|
|
8
|
+
/** Edge id from its endpoints + type — deterministic, dedupes naturally. */
|
|
9
|
+
export declare function edgeId(from: string, to: string, type: string): string;
|
|
10
|
+
/** Stable Engineering Landscape resource identity. The kind remains visible so
|
|
11
|
+
* fragments stay useful without a lookup table; the natural key is normalized
|
|
12
|
+
* only where spelling cannot carry meaning (outer whitespace, path separators,
|
|
13
|
+
* and a trailing slash). Kind-specific discovery may apply stricter canonical
|
|
14
|
+
* rules before calling this helper. */
|
|
15
|
+
export declare function resourceId(kind: string, naturalKey: string): string;
|
|
16
|
+
/** Resource relationships ride the existing edge graph and therefore share its
|
|
17
|
+
* endpoint/type identity rule. Keeping one helper prevents a parallel graph from
|
|
18
|
+
* minting incompatible relationship ids. */
|
|
19
|
+
export declare function resourceRelationshipId(from: string, to: string, type: string): string;
|
|
20
|
+
/** Decision id. Seed with the CANONICAL full commit sha (the auto-sync and MCP
|
|
21
|
+
* commit paths both do this, so a recorded decision upgrades the auto-draft for
|
|
22
|
+
* the same commit), or with "manual:<title>" for an ad-hoc MCP decision. */
|
|
23
|
+
export declare function decisionId(seed: string): string;
|
|
24
|
+
/** Bug id seeded by symptom/test so the same failure doesn't spawn duplicates. */
|
|
25
|
+
export declare function bugId(seed: string): string;
|
|
26
|
+
/** Runbook id seeded by its task; trim + lowercase so re-capturing the same task
|
|
27
|
+
* is idempotent rather than minting a duplicate. */
|
|
28
|
+
export declare function runbookId(seed: string): string;
|
|
29
|
+
/** Constraint id seeded by its statement. Trim + lowercase so trivial
|
|
30
|
+
* whitespace/case variants of the same rule collapse to one id (idempotent
|
|
31
|
+
* re-capture), instead of minting a duplicate constraint. */
|
|
32
|
+
export declare function constraintId(statement: string): string;
|
|
33
|
+
/** Finding id seeded by its title (trim + lowercase, same idiom as constraints):
|
|
34
|
+
* re-recording the same observation UPDATES it (e.g. a triage change) instead of
|
|
35
|
+
* minting a duplicate. A genuinely new observation deserves a new title. */
|
|
36
|
+
export declare function findingId(title: string): string;
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import type { Decision } from "./types.js";
|
|
2
|
+
export type ImportedAdrDisposition = "approve" | "decline";
|
|
3
|
+
export interface ImportedAdrReview {
|
|
4
|
+
disposition: ImportedAdrDisposition;
|
|
5
|
+
sourceHash: string;
|
|
6
|
+
reviewHash: string;
|
|
7
|
+
reviewer: string | null;
|
|
8
|
+
reviewedAt: string | null;
|
|
9
|
+
}
|
|
10
|
+
export declare function isImportedAdrDecision(decision: Decision): boolean;
|
|
11
|
+
export declare function importedAdrSourceHash(decision: Decision): string | null;
|
|
12
|
+
/** Hash the complete mapped meaning, not just the ADR source bytes. This prevents
|
|
13
|
+
* a later importer/parser change from carrying old authority onto new semantics
|
|
14
|
+
* even when the Markdown file itself did not change. */
|
|
15
|
+
export declare function importedAdrReviewHash(decision: Decision): string;
|
|
16
|
+
export declare function importedAdrReview(decision: Decision): ImportedAdrReview | null;
|
|
17
|
+
export declare function isPendingImportedAdrReview(decision: Decision): boolean;
|
|
18
|
+
export declare function pendingImportedAdrReviews(decisions: readonly Decision[]): Decision[];
|
|
19
|
+
/** Apply one explicit answer to one exact live imported ADR. Never changes the
|
|
20
|
+
* ADR lifecycle or content: decline means "reviewed, keep advisory", not delete. */
|
|
21
|
+
export declare function applyImportedAdrReview(decision: Decision, input: {
|
|
22
|
+
disposition: ImportedAdrDisposition;
|
|
23
|
+
expectedSourceHash: string;
|
|
24
|
+
expectedReviewHash: string;
|
|
25
|
+
reviewer: string;
|
|
26
|
+
reviewedAt?: string;
|
|
27
|
+
}): Decision;
|
|
28
|
+
/** Carry a review across idempotent re-import only when the source bytes match.
|
|
29
|
+
* A changed hash deliberately returns the clean new import, reopening review. */
|
|
30
|
+
export declare function carryImportedAdrReview(previous: Decision | null | undefined, next: Decision): Decision;
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Write `data` to `file` via a temp file + rename, so an interrupted write can't
|
|
3
|
+
* leave the target truncated (the symbols/edges index is the worst to half-write).
|
|
4
|
+
*
|
|
5
|
+
* Durability (issue #34): the temp file is fsync'd BEFORE the rename, and the
|
|
6
|
+
* parent directory best-effort after it. A process kill was always safe (page
|
|
7
|
+
* cache preserves ordering), but on power loss / OS crash the rename's metadata
|
|
8
|
+
* could reach disk before the temp file's data blocks — leaving the target
|
|
9
|
+
* present but truncated or garbage, the exact state the atomic-write invariant
|
|
10
|
+
* (con_902759b3dc) exists to prevent.
|
|
11
|
+
*
|
|
12
|
+
* Windows caveat: renameSync can't REPLACE a file another process holds open (even
|
|
13
|
+
* for read) — it throws EPERM/EBUSY/EACCES, exactly when the MCP server is reading
|
|
14
|
+
* while a CLI writes. Retry that atomic replacement with bounded backoff. If the
|
|
15
|
+
* contention persists, fail with the old target untouched; never trade availability
|
|
16
|
+
* for a direct write that an interruption could truncate. Failed writes clean up the
|
|
17
|
+
* temporary file.
|
|
18
|
+
*/
|
|
19
|
+
export declare function writeFileAtomic(file: string, data: string): void;
|
|
20
|
+
/** Atomically create a complete file only when no target exists. A same-dir
|
|
21
|
+
* hard link publishes the fully written temp inode with create-if-absent
|
|
22
|
+
* semantics, so concurrent lifecycle writers can never be overwritten. */
|
|
23
|
+
export declare function writeFileAtomicIfAbsent(file: string, data: string): boolean;
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
/** Parse JSON with the comments and trailing commas accepted by VS Code JSONC.
|
|
2
|
+
* String-aware scanning keeps comment-looking text and commas inside strings
|
|
3
|
+
* untouched. Callers still validate the returned shape for their own contract. */
|
|
4
|
+
export declare function parseJsonc(raw: string): unknown;
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { type Edge, type Resource } from "./types.js";
|
|
2
|
+
import { type LandscapeDiscoveryResult } from "../extractors/landscapeDiscovery.js";
|
|
3
|
+
export declare const LANDSCAPE_REVIEW_SCHEMA_VERSION: "hunch.landscape-review/1";
|
|
4
|
+
export declare const LANDSCAPE_ADOPTION_RECEIPT_SCHEMA_VERSION: "hunch.landscape-adoption-receipt/1";
|
|
5
|
+
export declare const MAX_LANDSCAPE_REFRESH_REVISIONS = 16;
|
|
6
|
+
export interface LandscapeReview {
|
|
7
|
+
schema: typeof LANDSCAPE_REVIEW_SCHEMA_VERSION;
|
|
8
|
+
authority: "human_confirmed";
|
|
9
|
+
reviewId: string;
|
|
10
|
+
reviewer: string;
|
|
11
|
+
reviewedAt: string;
|
|
12
|
+
discoveryHash: string;
|
|
13
|
+
sourceRevision: string;
|
|
14
|
+
repositoryRootIdentity: string;
|
|
15
|
+
selectedCandidateHashes: string[];
|
|
16
|
+
acknowledgedIssueCodes: string[];
|
|
17
|
+
}
|
|
18
|
+
export interface LandscapeAdoptionReceipt {
|
|
19
|
+
schema: typeof LANDSCAPE_ADOPTION_RECEIPT_SCHEMA_VERSION;
|
|
20
|
+
authority: "human_confirmed";
|
|
21
|
+
receiptId: string;
|
|
22
|
+
review: LandscapeReview;
|
|
23
|
+
acceptedResourceIds: string[];
|
|
24
|
+
acceptedRelationshipIds: string[];
|
|
25
|
+
writtenResourceIds: string[];
|
|
26
|
+
writtenRelationshipIds: string[];
|
|
27
|
+
reusedResourceIds: string[];
|
|
28
|
+
reusedRelationshipIds: string[];
|
|
29
|
+
}
|
|
30
|
+
export interface LandscapeAdoptionPlan {
|
|
31
|
+
receipt: LandscapeAdoptionReceipt;
|
|
32
|
+
resourcesToWrite: Resource[];
|
|
33
|
+
relationshipsToWrite: Edge[];
|
|
34
|
+
/** Existing, byte-proven adoption records replaced by this reviewed revision. */
|
|
35
|
+
refreshedResourceIds: string[];
|
|
36
|
+
refreshedRelationshipIds: string[];
|
|
37
|
+
}
|
|
38
|
+
export interface PlanLandscapeAdoptionInput {
|
|
39
|
+
discovery: LandscapeDiscoveryResult;
|
|
40
|
+
expectedDiscoveryHash: string;
|
|
41
|
+
reviewer: string;
|
|
42
|
+
reviewedAt?: string;
|
|
43
|
+
/** `all` remains an explicit operator choice; otherwise every hash is named. */
|
|
44
|
+
candidateHashes: "all" | string[];
|
|
45
|
+
acknowledgeIssues?: boolean;
|
|
46
|
+
existingResources?: Resource[];
|
|
47
|
+
existingRelationships?: Edge[];
|
|
48
|
+
/**
|
|
49
|
+
* Explicitly allow a newer exact revision to replace records from a prior
|
|
50
|
+
* reviewed adoption. Every replacement still requires a matching entry in
|
|
51
|
+
* `previousDiscoveries` so the old accepted bytes can be reconstructed and
|
|
52
|
+
* verified; metadata alone never grants replacement authority.
|
|
53
|
+
*/
|
|
54
|
+
refreshReviewed?: boolean;
|
|
55
|
+
previousDiscoveries?: LandscapeDiscoveryResult[];
|
|
56
|
+
}
|
|
57
|
+
/** Refuse a result that was changed after exact-revision discovery. */
|
|
58
|
+
export declare function assertLandscapeDiscoveryIntegrity(discovery: LandscapeDiscoveryResult): void;
|
|
59
|
+
/**
|
|
60
|
+
* Convert an exact candidate discovery into a prevalidated write plan.
|
|
61
|
+
*
|
|
62
|
+
* Planning has no side effects. The caller persists only `resourcesToWrite` and
|
|
63
|
+
* `relationshipsToWrite` through the ordinary Hunch capture boundary after this
|
|
64
|
+
* function has proved every selection, endpoint and existing-record conflict.
|
|
65
|
+
*/
|
|
66
|
+
export declare function planLandscapeAdoption(input: PlanLandscapeAdoptionInput): LandscapeAdoptionPlan;
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { type Edge, type Resource } from "./types.js";
|
|
2
|
+
export declare const LANDSCAPE_FRAGMENT_SCHEMA_VERSION: "hunch.landscape-fragment/1";
|
|
3
|
+
export type LandscapeSelectionReason = "exact-target" | "task-match" | "orientation-root" | "graph-neighbor";
|
|
4
|
+
export interface SelectedLandscapeResource {
|
|
5
|
+
record: Resource;
|
|
6
|
+
selectionReason: LandscapeSelectionReason;
|
|
7
|
+
selectionRank: number;
|
|
8
|
+
}
|
|
9
|
+
export interface SelectedLandscapeRelationship {
|
|
10
|
+
record: Edge;
|
|
11
|
+
selectionReason: "graph-connection";
|
|
12
|
+
selectionRank: number;
|
|
13
|
+
}
|
|
14
|
+
export interface LandscapeSelectionOmission {
|
|
15
|
+
kind: "resources" | "relationships";
|
|
16
|
+
recordId: string;
|
|
17
|
+
reason: "landscape-cap";
|
|
18
|
+
detail: string;
|
|
19
|
+
}
|
|
20
|
+
export interface ReviewedLandscapeSelection {
|
|
21
|
+
schema: typeof LANDSCAPE_FRAGMENT_SCHEMA_VERSION;
|
|
22
|
+
authority: "human_confirmed";
|
|
23
|
+
target: string;
|
|
24
|
+
resources: SelectedLandscapeResource[];
|
|
25
|
+
relationships: SelectedLandscapeRelationship[];
|
|
26
|
+
omitted: LandscapeSelectionOmission[];
|
|
27
|
+
}
|
|
28
|
+
export interface DeliveredLandscapeResource extends SelectedLandscapeResource {
|
|
29
|
+
rank: number;
|
|
30
|
+
deliveryReason: "ranked";
|
|
31
|
+
required: false;
|
|
32
|
+
blocking: false;
|
|
33
|
+
provenanceStatus: "current";
|
|
34
|
+
tokenCost: number;
|
|
35
|
+
}
|
|
36
|
+
export interface DeliveredLandscapeRelationship extends SelectedLandscapeRelationship {
|
|
37
|
+
rank: number;
|
|
38
|
+
deliveryReason: "ranked";
|
|
39
|
+
required: false;
|
|
40
|
+
blocking: false;
|
|
41
|
+
provenanceStatus: "current";
|
|
42
|
+
tokenCost: number;
|
|
43
|
+
}
|
|
44
|
+
export interface LandscapeFragmentOmission {
|
|
45
|
+
kind: "resources" | "relationships";
|
|
46
|
+
recordId: string;
|
|
47
|
+
reason: "budget" | "stale-provenance" | "endpoint-not-delivered" | "landscape-cap";
|
|
48
|
+
detail: string;
|
|
49
|
+
}
|
|
50
|
+
export interface LandscapeDeliveryFragment {
|
|
51
|
+
schema: typeof LANDSCAPE_FRAGMENT_SCHEMA_VERSION;
|
|
52
|
+
authority: "human_confirmed";
|
|
53
|
+
target: string;
|
|
54
|
+
resources: DeliveredLandscapeResource[];
|
|
55
|
+
relationships: DeliveredLandscapeRelationship[];
|
|
56
|
+
omitted: LandscapeFragmentOmission[];
|
|
57
|
+
reviewIds: string[];
|
|
58
|
+
discoveryHashes: string[];
|
|
59
|
+
sourceRevisions: string[];
|
|
60
|
+
fragmentHash: string;
|
|
61
|
+
}
|
|
62
|
+
export declare function landscapeFragmentHash(value: unknown): string;
|
|
63
|
+
/**
|
|
64
|
+
* Select a small orientation fragment from durable graph records.
|
|
65
|
+
*
|
|
66
|
+
* Candidate/unreviewed/stale records are excluded before ranking, so neither a
|
|
67
|
+
* good lexical match nor a graph edge can accidentally upgrade their authority.
|
|
68
|
+
*/
|
|
69
|
+
export declare function selectReviewedLandscape(resources: readonly Resource[], relationships: readonly Edge[], target: string, maxItems?: number): ReviewedLandscapeSelection;
|
|
70
|
+
export declare function createLandscapeDeliveryFragment(input: {
|
|
71
|
+
selection: ReviewedLandscapeSelection;
|
|
72
|
+
resources: DeliveredLandscapeResource[];
|
|
73
|
+
relationships: DeliveredLandscapeRelationship[];
|
|
74
|
+
omitted: LandscapeFragmentOmission[];
|
|
75
|
+
}): LandscapeDeliveryFragment;
|
|
76
|
+
export declare function assertLandscapeDeliveryFragment(value: LandscapeDeliveryFragment): void;
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The memory-move timeline — the data behind `hunch log` and the VS Code "Hunch
|
|
3
|
+
* Source Control" view. Every commit that touched `.hunch/` is one MOVE (a capture,
|
|
4
|
+
* adoption, supersession, or prune), classified deterministically from git's own
|
|
5
|
+
* name-status. Pure parsing — no LLM, no judgment — so the panel shows exactly what
|
|
6
|
+
* git recorded and each move maps back to a real, revertable commit.
|
|
7
|
+
*
|
|
8
|
+
* The parser is split from the git shell-out (extractors/git.gitMemoryLog) so it is
|
|
9
|
+
* unit-testable with canned `git log` output.
|
|
10
|
+
*/
|
|
11
|
+
export type MemoryMoveKind = "capture" | "adopt" | "supersede" | "prune" | "repair" | "edit";
|
|
12
|
+
/** One commit that changed the memory graph, ready to render as a timeline entry. */
|
|
13
|
+
export interface MemoryMove {
|
|
14
|
+
sha: string;
|
|
15
|
+
shortSha: string;
|
|
16
|
+
/** committer date, ISO 8601. */
|
|
17
|
+
date: string;
|
|
18
|
+
subject: string;
|
|
19
|
+
kind: MemoryMoveKind;
|
|
20
|
+
/** decision ids (dec_*) this move touched — for the click-through popup. */
|
|
21
|
+
decisionIds: string[];
|
|
22
|
+
/** other record ids touched (bugs/constraints/components/policies). */
|
|
23
|
+
otherIds: string[];
|
|
24
|
+
added: number;
|
|
25
|
+
modified: number;
|
|
26
|
+
deleted: number;
|
|
27
|
+
/** the `.hunch/` files this commit changed. */
|
|
28
|
+
files: string[];
|
|
29
|
+
}
|
|
30
|
+
/** The record-header separator we ask `git log --format` to emit, so header lines
|
|
31
|
+
* are unambiguous against the name-status lines that follow each commit. */
|
|
32
|
+
export declare const MEMLOG_HEADER = "@@@";
|
|
33
|
+
/** The `--format` string that pairs with {@link parseMemoryLog}. */
|
|
34
|
+
export declare const MEMLOG_FORMAT = "@@@%H\t%h\t%cI\t%s";
|
|
35
|
+
/** Parse `git log <MEMLOG_FORMAT> --name-status -- .hunch/` output into classified
|
|
36
|
+
* moves, newest first (git's order). */
|
|
37
|
+
export declare function parseMemoryLog(raw: string): MemoryMove[];
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import type { EntityKind } from "./types.js";
|
|
2
|
+
import type { HunchPaths } from "./paths.js";
|
|
3
|
+
/** The schema generation this build writes and reads. Bump on any breaking change. */
|
|
4
|
+
export declare const SCHEMA_VERSION = 3;
|
|
5
|
+
/** A repo whose `.hunch/` predates manifests is treated as v1. Migrations are
|
|
6
|
+
* numbered from 2 (each `version` is the number it PRODUCES), so a baseline repo
|
|
7
|
+
* runs every migration with version >= 2 — never author a no-op version:1 one. */
|
|
8
|
+
export declare const BASELINE_VERSION = 1;
|
|
9
|
+
export interface Migration {
|
|
10
|
+
/** The version this migration PRODUCES (i.e. it upgrades version-1 → `version`). */
|
|
11
|
+
version: number;
|
|
12
|
+
description: string;
|
|
13
|
+
/** Transform one raw record (untrusted JSON) of `kind` to the next shape. */
|
|
14
|
+
up(kind: EntityKind, raw: Record<string, unknown>): Record<string, unknown>;
|
|
15
|
+
}
|
|
16
|
+
/** Ordered, ascending by `version`. Empty at v1 (baseline); future versions append. */
|
|
17
|
+
export declare const MIGRATIONS: Migration[];
|
|
18
|
+
export interface Manifest {
|
|
19
|
+
schema_version: number;
|
|
20
|
+
}
|
|
21
|
+
/** Read `.hunch/manifest.json`. A missing/corrupt manifest is treated as the
|
|
22
|
+
* BASELINE version (a pre-manifest `.hunch/`), so future builds still migrate it. */
|
|
23
|
+
export declare function readManifest(paths: HunchPaths): Manifest;
|
|
24
|
+
/** Write `.hunch/manifest.json` at `version` (default: the current SCHEMA_VERSION). */
|
|
25
|
+
export declare function writeManifest(paths: HunchPaths, version?: number): void;
|
|
26
|
+
/** Apply every migration in (fromVersion, toVersion] to a single raw record. Skips
|
|
27
|
+
* non-object input untouched (the loader's Zod pass will reject it). */
|
|
28
|
+
export declare function migrateRaw(kind: EntityKind, raw: unknown, fromVersion: number, migrations?: Migration[], toVersion?: number): unknown;
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { type Finding } from "./types.js";
|
|
3
|
+
export declare const USEFULNESS_OBSERVATION_SCHEMA_VERSION: "hunch.usefulness-observation/1";
|
|
4
|
+
export declare const USEFULNESS_SIGNALS: readonly ["used", "prevented", "near_miss", "contradicted", "stale", "unused", "unknown"];
|
|
5
|
+
export declare const UsefulnessObservationSchema: z.ZodObject<{
|
|
6
|
+
schema: z.ZodLiteral<"hunch.usefulness-observation/1">;
|
|
7
|
+
observationId: z.ZodString;
|
|
8
|
+
episode: z.ZodObject<{
|
|
9
|
+
provider: z.ZodString;
|
|
10
|
+
schemaVersion: z.ZodString;
|
|
11
|
+
episodeId: z.ZodString;
|
|
12
|
+
episodeHash: z.ZodString;
|
|
13
|
+
terminalAt: z.ZodString;
|
|
14
|
+
result: z.ZodEnum<{
|
|
15
|
+
fail: "fail";
|
|
16
|
+
pass: "pass";
|
|
17
|
+
uncertain: "uncertain";
|
|
18
|
+
abandoned: "abandoned";
|
|
19
|
+
rolled_back: "rolled_back";
|
|
20
|
+
}>;
|
|
21
|
+
}, z.core.$strict>;
|
|
22
|
+
delivery: z.ZodObject<{
|
|
23
|
+
receiptRef: z.ZodString;
|
|
24
|
+
receiptHash: z.ZodString;
|
|
25
|
+
graphRevision: z.ZodString;
|
|
26
|
+
sourceRevision: z.ZodString;
|
|
27
|
+
sourceContentHash: z.ZodString;
|
|
28
|
+
}, z.core.$strict>;
|
|
29
|
+
record: z.ZodObject<{
|
|
30
|
+
recordId: z.ZodString;
|
|
31
|
+
recordKind: z.ZodString;
|
|
32
|
+
recordRevision: z.ZodString;
|
|
33
|
+
contentHash: z.ZodString;
|
|
34
|
+
}, z.core.$strict>;
|
|
35
|
+
change: z.ZodOptional<z.ZodObject<{
|
|
36
|
+
schema: z.ZodLiteral<"hunch.change-identity/1">;
|
|
37
|
+
algorithm: z.ZodLiteral<"git-raw-tree-delta-sha256/1">;
|
|
38
|
+
change_id: z.ZodString;
|
|
39
|
+
base_revision: z.ZodString;
|
|
40
|
+
head_revision: z.ZodString;
|
|
41
|
+
base_tree: z.ZodString;
|
|
42
|
+
head_tree: z.ZodString;
|
|
43
|
+
delta_hash: z.ZodString;
|
|
44
|
+
patch_id: z.ZodNullable<z.ZodString>;
|
|
45
|
+
file_count: z.ZodNumber;
|
|
46
|
+
paths_hash: z.ZodString;
|
|
47
|
+
content_hash: z.ZodString;
|
|
48
|
+
}, z.core.$strict>>;
|
|
49
|
+
signal: z.ZodEnum<{
|
|
50
|
+
unknown: "unknown";
|
|
51
|
+
stale: "stale";
|
|
52
|
+
used: "used";
|
|
53
|
+
prevented: "prevented";
|
|
54
|
+
near_miss: "near_miss";
|
|
55
|
+
contradicted: "contradicted";
|
|
56
|
+
unused: "unused";
|
|
57
|
+
}>;
|
|
58
|
+
evidence: z.ZodArray<z.ZodObject<{
|
|
59
|
+
kind: z.ZodEnum<{
|
|
60
|
+
observation: "observation";
|
|
61
|
+
artifact: "artifact";
|
|
62
|
+
receipt: "receipt";
|
|
63
|
+
event: "event";
|
|
64
|
+
verification: "verification";
|
|
65
|
+
}>;
|
|
66
|
+
ref: z.ZodString;
|
|
67
|
+
hash: z.ZodString;
|
|
68
|
+
}, z.core.$strict>>;
|
|
69
|
+
observedAt: z.ZodString;
|
|
70
|
+
retainUntil: z.ZodString;
|
|
71
|
+
privacy: z.ZodObject<{
|
|
72
|
+
payloadMode: z.ZodLiteral<"references_hashes_only">;
|
|
73
|
+
rawTranscriptIncluded: z.ZodLiteral<false>;
|
|
74
|
+
rawProviderOutputIncluded: z.ZodLiteral<false>;
|
|
75
|
+
}, z.core.$strict>;
|
|
76
|
+
authority: z.ZodObject<{
|
|
77
|
+
behavioralEffect: z.ZodLiteral<"none">;
|
|
78
|
+
mayChangeRanking: z.ZodLiteral<false>;
|
|
79
|
+
mayPromoteKnowledge: z.ZodLiteral<false>;
|
|
80
|
+
mayGrantAuthority: z.ZodLiteral<false>;
|
|
81
|
+
}, z.core.$strict>;
|
|
82
|
+
contentHash: z.ZodString;
|
|
83
|
+
}, z.core.$strict>;
|
|
84
|
+
export type UsefulnessObservation = z.infer<typeof UsefulnessObservationSchema>;
|
|
85
|
+
export type CreateUsefulnessObservationInput = Omit<UsefulnessObservation, "schema" | "observationId" | "authority" | "contentHash">;
|
|
86
|
+
export declare function createUsefulnessObservation(input: CreateUsefulnessObservationInput): UsefulnessObservation;
|
|
87
|
+
export declare function assertUsefulnessObservation(value: unknown): asserts value is UsefulnessObservation;
|
|
88
|
+
/** Contradiction and staleness become open advisory review work, never truth. */
|
|
89
|
+
export declare function usefulnessObservationFinding(value: unknown): Finding | null;
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/** Validate the materialized overlay without following links. Git's metadata is
|
|
2
|
+
* deliberately skipped, but its own directory must still be contained under the
|
|
3
|
+
* canonical overlay root. Every remotely controlled entry must be an ordinary
|
|
4
|
+
* file or real directory whose canonical path stays inside that root. */
|
|
5
|
+
export declare function safeOverlayTree(root: string): boolean;
|
|
6
|
+
/** Validate `git ls-tree -r -t -z <exact-oid>` output before checkout. A Git
|
|
7
|
+
* remote can encode symlinks (120000) and gitlinks (160000); accepting only
|
|
8
|
+
* ordinary blobs and trees makes the fetched object graph safe to materialize.
|
|
9
|
+
* The explicit Hunch topology rules keep canonical record directories and
|
|
10
|
+
* capability/config paths from changing shape on a later pull. */
|
|
11
|
+
export declare function safeOverlayGitTreeListing(listing: string): boolean;
|
|
12
|
+
/** A dedicated Hunch overlay needs exactly one attribute capability: selecting
|
|
13
|
+
* the locally installed `merge=hunch` JSON merge driver, plus Hunch's exact
|
|
14
|
+
* `.hunch/manifest.json merge=text` override (the manifest has no record id and
|
|
15
|
+
* must use Git's built-in text merge), and the five exact generated-doc rules
|
|
16
|
+
* installed with the grounding merge driver. Reject every other token/pattern pair,
|
|
17
|
+
* including byte-transforming built-ins such as `ident` and
|
|
18
|
+
* `working-tree-encoding`, rather than maintaining a command-key blacklist.
|
|
19
|
+
* Blank lines and comments remain harmless. */
|
|
20
|
+
export declare function hunchAttributesAreSafe(content: string): boolean;
|
|
21
|
+
/** Validate every committed .gitattributes blob in an already-safe ls-tree
|
|
22
|
+
* listing. Blob loading is injected so clone and later-pull seams share one
|
|
23
|
+
* parser without either trusting worktree bytes before materialization. */
|
|
24
|
+
export declare function hunchTreeAttributesAreSafe(listing: string, readBlob: (oid: string) => string | null): boolean;
|
|
@@ -129,7 +129,8 @@ export function safeOverlayGitTreeListing(listing) {
|
|
|
129
129
|
/** A dedicated Hunch overlay needs exactly one attribute capability: selecting
|
|
130
130
|
* the locally installed `merge=hunch` JSON merge driver, plus Hunch's exact
|
|
131
131
|
* `.hunch/manifest.json merge=text` override (the manifest has no record id and
|
|
132
|
-
* must use Git's built-in text merge)
|
|
132
|
+
* must use Git's built-in text merge), and the five exact generated-doc rules
|
|
133
|
+
* installed with the grounding merge driver. Reject every other token/pattern pair,
|
|
133
134
|
* including byte-transforming built-ins such as `ident` and
|
|
134
135
|
* `working-tree-encoding`, rather than maintaining a command-key blacklist.
|
|
135
136
|
* Blank lines and comments remain harmless. */
|
|
@@ -144,6 +145,11 @@ export function hunchAttributesAreSafe(content) {
|
|
|
144
145
|
const attributes = fields.slice(1);
|
|
145
146
|
if (attributes.every((attribute) => attribute === "merge=hunch"))
|
|
146
147
|
continue;
|
|
148
|
+
// These exact document paths are the only grounding-driver routes installed
|
|
149
|
+
// by Hunch. Never accept a wildcard or allow it to target JSON memory.
|
|
150
|
+
if (["CLAUDE.md", "AGENTS.md", ".github/copilot-instructions.md", ".cursor/rules/hunch.mdc", ".windsurf/rules/hunch.md"].includes(fields[0])
|
|
151
|
+
&& attributes.every((attribute) => attribute === "merge=hunch-grounding"))
|
|
152
|
+
continue;
|
|
147
153
|
if (fields[0] === ".hunch/manifest.json"
|
|
148
154
|
&& attributes.every((attribute) => attribute === "merge=text"))
|
|
149
155
|
continue;
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
export declare const HUNCH_DIR = ".hunch";
|
|
2
|
+
/** Canonicalize a free-form path/target to repo-relative POSIX form. Hunch stores
|
|
3
|
+
* every path with forward slashes (git emits "/" on all OSes), so any user- or
|
|
4
|
+
* agent-supplied target must be normalized before comparison — otherwise a
|
|
5
|
+
* Windows caller passing `src\auth\session.ts` never matches the stored
|
|
6
|
+
* `src/auth/session.ts`. Safe on symbol names too: they contain no backslashes. */
|
|
7
|
+
export declare function toPosixTarget(target: string): string;
|
|
8
|
+
export interface HunchPaths {
|
|
9
|
+
/** Repo root (where .hunch/ lives). */
|
|
10
|
+
root: string;
|
|
11
|
+
hunch: string;
|
|
12
|
+
sqlite: string;
|
|
13
|
+
/** `.hunch/manifest.json` — records the on-disk schema version. */
|
|
14
|
+
manifest: string;
|
|
15
|
+
/** `.hunch/config.json` — user runtime config (firmness, etc.). */
|
|
16
|
+
config: string;
|
|
17
|
+
dir(kind: string): string;
|
|
18
|
+
}
|
|
19
|
+
export declare function hunchPaths(root: string): HunchPaths;
|
|
20
|
+
/** Build paths for a hunch-layout directory given DIRECTLY — i.e. `hunchDir` IS
|
|
21
|
+
* the dir holding the kind subdirs (decisions/, bugs/, …). Used for an external
|
|
22
|
+
* PRIVATE overlay store (HUNCH_PRIVATE_DIR), which lives in a separate repo the
|
|
23
|
+
* user controls rather than under the current repo's `.hunch/`. */
|
|
24
|
+
export declare function hunchPathsForDir(hunchDir: string): HunchPaths;
|
|
25
|
+
/** Walk up from `start` to the nearest dir containing a .hunch/ dir OR a .git
|
|
26
|
+
* (repo boundary), else `start`. Lets `hunch` run from subdirs. A `.git`
|
|
27
|
+
* WITHOUT `.hunch` stops the walk: an ancestor `.hunch` above the repo
|
|
28
|
+
* boundary belongs to some other scope (e.g. a stray ~/.hunch) and must never
|
|
29
|
+
* hijack a fresh repo — init would scaffold, index, and scan OUTSIDE the repo. */
|
|
30
|
+
export declare function isDir(path: string): boolean;
|
|
31
|
+
export declare function findRoot(start?: string): string;
|