@davesheffer/hunch 1.20.2 → 1.21.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 +6 -3
- package/dist/cli/dna.js +116 -0
- package/dist/cli/index.js +114 -3
- package/dist/core/projectDna.js +478 -0
- package/dist/core/projectDnaDelivery.js +54 -0
- package/dist/core/projectDnaDelta.js +159 -0
- package/dist/extractors/git.js +47 -0
- package/dist/mcp/server.js +162 -9
- package/dist/projectDna.js +10 -0
- package/dist/store/hunchStore.js +112 -5
- package/package.json +9 -1
- package/server.json +2 -2
package/README.md
CHANGED
|
@@ -58,6 +58,9 @@ existing configuration.
|
|
|
58
58
|
passes, needs attention, or should be blocked.
|
|
59
59
|
- **Past bugs stay useful** — see which old incident a piece of code fixed before accidentally
|
|
60
60
|
undoing it.
|
|
61
|
+
- **Fresh context without lost history** — decisions whose code anchors are still current rank
|
|
62
|
+
ahead of ones whose files changed after verification; older evidence remains visible and keeps
|
|
63
|
+
its existing authority.
|
|
61
64
|
- **Understands how code connects** — for TypeScript, JavaScript, Python, Go, PHP, YAML, and Helm, Hunch
|
|
62
65
|
can see what calls or depends on the code you are about to change. Its memory works with any
|
|
63
66
|
language.
|
|
@@ -171,7 +174,7 @@ repository, separate from the code repository. Hunch does not host it. Create a
|
|
|
171
174
|
that every teammate can access, install Hunch on team machines and CI, then have one maintainer run:
|
|
172
175
|
|
|
173
176
|
```bash
|
|
174
|
-
npm i -g @davesheffer/hunch@1.
|
|
177
|
+
npm i -g @davesheffer/hunch@1.21.0
|
|
175
178
|
hunch shared --repo git@github.com:acme/project-hunch-memory.git
|
|
176
179
|
git add .gitignore .hunch/team.json
|
|
177
180
|
git commit -m "chore: connect shared Hunch memory"
|
|
@@ -186,7 +189,7 @@ printed by Hunch. Omit `--migrate` for a new setup.
|
|
|
186
189
|
After the pointer commit lands, teammates need Hunch installed and Git access to the memory repo:
|
|
187
190
|
|
|
188
191
|
```bash
|
|
189
|
-
npm i -g @davesheffer/hunch@1.
|
|
192
|
+
npm i -g @davesheffer/hunch@1.21.0
|
|
190
193
|
git pull
|
|
191
194
|
hunch init
|
|
192
195
|
hunch doctor
|
|
@@ -234,7 +237,7 @@ but stops automatic memory commits and pushes. As a team-coordinated rollback, r
|
|
|
234
237
|
commit to stop discovery after teammates pull the revert. Existing machines retain their ignored
|
|
235
238
|
local overlay until they are deliberately disconnected; do not delete the memory repo as part of a
|
|
236
239
|
rollback. For this rollout, reinstall the previous published package with
|
|
237
|
-
`npm i -g @davesheffer/hunch@1.20.
|
|
240
|
+
`npm i -g @davesheffer/hunch@1.20.3`; the release receipt resolves and records the verified rollback
|
|
238
241
|
target from the npm registry instead of trusting Git tags. Pause enforcement first as shown above,
|
|
239
242
|
and keep every team client on the same release before resuming Matrix policy workflows.
|
|
240
243
|
|
package/dist/cli/dna.js
ADDED
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { execFileSync } from "node:child_process";
|
|
3
|
+
import { Command } from "commander";
|
|
4
|
+
import { discoverProjectDna, evaluateProjectDnaMatch } from "../core/projectDna.js";
|
|
5
|
+
import { diffProjectDna } from "../core/projectDnaDelta.js";
|
|
6
|
+
import { projectDnaDeliverySupplement } from "../core/projectDnaDelivery.js";
|
|
7
|
+
function gitRoot() {
|
|
8
|
+
try {
|
|
9
|
+
return execFileSync("git", ["rev-parse", "--show-toplevel"], {
|
|
10
|
+
encoding: "utf8",
|
|
11
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
12
|
+
timeout: 5_000,
|
|
13
|
+
}).trim();
|
|
14
|
+
}
|
|
15
|
+
catch {
|
|
16
|
+
throw new Error("Project DNA must run inside a Git repository");
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
function printProfile(root, revision, json) {
|
|
20
|
+
const profile = discoverProjectDna(root, revision);
|
|
21
|
+
if (json) {
|
|
22
|
+
process.stdout.write(`${JSON.stringify(profile, null, 2)}\n`);
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
process.stdout.write(`Project DNA ${profile.profile_id}\n`);
|
|
26
|
+
process.stdout.write(`revision: ${profile.repository_revision}\n`);
|
|
27
|
+
process.stdout.write(`history sample: ${profile.history_sample_count}\n`);
|
|
28
|
+
process.stdout.write(`committed convention files: ${profile.source_files.length ? profile.source_files.join(", ") : "none"}\n`);
|
|
29
|
+
if (!profile.traits.length) {
|
|
30
|
+
process.stdout.write("traits: none yet (insufficient evidence)\n");
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
for (const trait of profile.traits) {
|
|
34
|
+
process.stdout.write(`- [${trait.category}] ${trait.claim} (${trait.confidence.toFixed(2)}; ${trait.id})\n`);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
const program = new Command();
|
|
38
|
+
program
|
|
39
|
+
.name("hunch-dna")
|
|
40
|
+
.description("Derive and inspect Hunch Project DNA from exact committed repository evidence.")
|
|
41
|
+
.option("--revision <ref>", "Git revision to inspect", "HEAD")
|
|
42
|
+
.option("--json", "emit the sealed machine-readable profile")
|
|
43
|
+
.action((options) => {
|
|
44
|
+
printProfile(gitRoot(), options.revision, !!options.json);
|
|
45
|
+
});
|
|
46
|
+
program
|
|
47
|
+
.command("match")
|
|
48
|
+
.description("Score a commit/PR/issue/message against deterministic applicable DNA traits.")
|
|
49
|
+
.requiredOption("--kind <kind>", "commit | pull_request | issue | message")
|
|
50
|
+
.requiredOption("--title <title>", "artifact title/subject")
|
|
51
|
+
.option("--body <body>", "artifact body")
|
|
52
|
+
.option("--revision <ref>", "Git revision whose DNA applies", "HEAD")
|
|
53
|
+
.option("--json", "emit the sealed match contract")
|
|
54
|
+
.action((options) => {
|
|
55
|
+
if (!(new Set(["commit", "pull_request", "issue", "message"])).has(options.kind)) {
|
|
56
|
+
throw new Error("--kind must be commit, pull_request, issue, or message");
|
|
57
|
+
}
|
|
58
|
+
const artifact = {
|
|
59
|
+
kind: options.kind,
|
|
60
|
+
title: options.title,
|
|
61
|
+
...(options.body !== undefined ? { body: options.body } : {}),
|
|
62
|
+
};
|
|
63
|
+
const globals = program.opts();
|
|
64
|
+
const revision = options.revision ?? globals.revision ?? "HEAD";
|
|
65
|
+
const json = options.json ?? globals.json ?? false;
|
|
66
|
+
const profile = discoverProjectDna(gitRoot(), revision);
|
|
67
|
+
const match = evaluateProjectDnaMatch(profile, artifact);
|
|
68
|
+
if (json)
|
|
69
|
+
process.stdout.write(`${JSON.stringify(match, null, 2)}\n`);
|
|
70
|
+
else {
|
|
71
|
+
process.stdout.write(`Project DNA match: ${match.score === null ? "n/a" : `${match.score}%`} (${match.applicable_checks} applicable checks)\n`);
|
|
72
|
+
for (const check of match.checks.filter((item) => item.applicable)) {
|
|
73
|
+
process.stdout.write(`- ${check.passed ? "PASS" : "FAIL"} ${check.key}: ${check.detail}\n`);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
});
|
|
77
|
+
program
|
|
78
|
+
.command("context")
|
|
79
|
+
.description("Render the bounded advisory DNA supplement intended for Hunch delivery/context assembly.")
|
|
80
|
+
.option("--revision <ref>", "Git revision to inspect", "HEAD")
|
|
81
|
+
.option("--traits <count>", "maximum DNA traits in the orientation slice", "8")
|
|
82
|
+
.option("--json", "emit supplement JSON")
|
|
83
|
+
.action((options) => {
|
|
84
|
+
const globals = program.opts();
|
|
85
|
+
const revision = options.revision ?? globals.revision ?? "HEAD";
|
|
86
|
+
const json = options.json ?? globals.json ?? false;
|
|
87
|
+
const cap = Number(options.traits);
|
|
88
|
+
const supplement = projectDnaDeliverySupplement(discoverProjectDna(gitRoot(), revision), cap);
|
|
89
|
+
if (json)
|
|
90
|
+
process.stdout.write(`${JSON.stringify(supplement, null, 2)}\n`);
|
|
91
|
+
else
|
|
92
|
+
process.stdout.write(`${supplement?.text ?? "No evidence-backed Project DNA traits yet."}\n`);
|
|
93
|
+
});
|
|
94
|
+
program
|
|
95
|
+
.command("diff")
|
|
96
|
+
.description("Compare DNA at two exact revisions; reports observation drift without inferring causality.")
|
|
97
|
+
.argument("<from>", "older Git revision")
|
|
98
|
+
.argument("<to>", "newer Git revision")
|
|
99
|
+
.option("--json", "emit the sealed delta contract")
|
|
100
|
+
.action((from, to, options) => {
|
|
101
|
+
const root = gitRoot();
|
|
102
|
+
const delta = diffProjectDna(discoverProjectDna(root, from), discoverProjectDna(root, to));
|
|
103
|
+
const json = options.json ?? program.opts().json ?? false;
|
|
104
|
+
if (json)
|
|
105
|
+
process.stdout.write(`${JSON.stringify(delta, null, 2)}\n`);
|
|
106
|
+
else {
|
|
107
|
+
process.stdout.write(`Project DNA drift ${delta.delta_id}: ${delta.changed ? `${delta.changes.length} change(s)` : "no observed change"}\n`);
|
|
108
|
+
for (const change of delta.changes)
|
|
109
|
+
process.stdout.write(`- ${change.kind}: ${change.key}\n`);
|
|
110
|
+
}
|
|
111
|
+
});
|
|
112
|
+
program.parseAsync().catch((error) => {
|
|
113
|
+
process.stderr.write(`hunch-dna: ${error instanceof Error ? error.message : String(error)}\n`);
|
|
114
|
+
process.exitCode = 1;
|
|
115
|
+
});
|
|
116
|
+
//# sourceMappingURL=dna.js.map
|
package/dist/cli/index.js
CHANGED
|
@@ -57,6 +57,9 @@ import { compileVerifiedEvidenceMap, formatVerifiedEvidenceMap } from "../core/e
|
|
|
57
57
|
import { collectCorrectionStageSources } from "../extractors/correctionSources.js";
|
|
58
58
|
import { buildDeliveryEnvelope, DELIVERY_PROFILES } from "../core/delivery.js";
|
|
59
59
|
import { deriveChangeIdentity } from "../core/changeIdentity.js";
|
|
60
|
+
import { discoverProjectDna, evaluateProjectDnaMatch } from "../core/projectDna.js";
|
|
61
|
+
import { diffProjectDna } from "../core/projectDnaDelta.js";
|
|
62
|
+
import { projectDnaDeliverySupplement } from "../core/projectDnaDelivery.js";
|
|
60
63
|
import { readConfig, writeConfig, FIRMNESS_LEVELS, isFirmness } from "../core/config.js";
|
|
61
64
|
import { blockingInScope, vetoInScope, proposedEditLines } from "../core/hookpolicy.js";
|
|
62
65
|
import { isHumanConfirmed } from "../core/strictgate.js";
|
|
@@ -1292,7 +1295,7 @@ program
|
|
|
1292
1295
|
const emb = await selectEmbedder();
|
|
1293
1296
|
if (!store.semanticReady(emb)) {
|
|
1294
1297
|
console.log("· semantic search isn't enabled yet — run `hunch embed` (using keyword search for now).\n");
|
|
1295
|
-
hits = store.
|
|
1298
|
+
hits = store.rankedSearch(q, 12);
|
|
1296
1299
|
}
|
|
1297
1300
|
else {
|
|
1298
1301
|
hits = await store.hybridSearch(q, 12, { embedder: emb });
|
|
@@ -1300,7 +1303,7 @@ program
|
|
|
1300
1303
|
}
|
|
1301
1304
|
}
|
|
1302
1305
|
else {
|
|
1303
|
-
hits = store.
|
|
1306
|
+
hits = store.rankedSearch(q, 12);
|
|
1304
1307
|
}
|
|
1305
1308
|
if (!hits.length) {
|
|
1306
1309
|
console.log(`No matches for "${q}".`);
|
|
@@ -2903,6 +2906,114 @@ landscapeCmd
|
|
|
2903
2906
|
console.log(` hunch landscape adopt --ref ${discovery.sourceRevision} --expected ${discovery.discoveryHash} --all --reviewed-by <you>${discovery.issues.length ? " --acknowledge-issues" : ""}`);
|
|
2904
2907
|
console.log("Or pass --candidate <hash...> to adopt an explicit subset; relationships require both endpoint resources.");
|
|
2905
2908
|
});
|
|
2909
|
+
// ---- dna (read-only, exact-revision repository convention profile) -------
|
|
2910
|
+
const dnaCmd = program
|
|
2911
|
+
.command("dna")
|
|
2912
|
+
.description("Inspect evidence-backed Project DNA and evaluate repository-native artifacts without writing graph authority.");
|
|
2913
|
+
dnaCmd
|
|
2914
|
+
.command("inspect")
|
|
2915
|
+
.description("Derive a deterministic Project DNA profile from one exact Git revision. This command never writes memory.")
|
|
2916
|
+
.option("--ref <ref>", "Git commit/ref to inspect", "HEAD")
|
|
2917
|
+
.option("--json", "emit the complete machine-readable profile")
|
|
2918
|
+
.action((opts) => {
|
|
2919
|
+
try {
|
|
2920
|
+
const profile = discoverProjectDna(findRoot(), opts.ref);
|
|
2921
|
+
if (opts.json) {
|
|
2922
|
+
console.log(JSON.stringify(profile, null, 2));
|
|
2923
|
+
return;
|
|
2924
|
+
}
|
|
2925
|
+
console.log(`Project DNA at ${profile.repository_revision}`);
|
|
2926
|
+
console.log(`Profile: ${profile.profile_id} (${profile.content_hash})`);
|
|
2927
|
+
console.log(`Evidence: ${profile.history_sample_count} commit subject(s), ${profile.source_files.length} convention file(s)`);
|
|
2928
|
+
if (profile.source_files.length)
|
|
2929
|
+
console.log(`Sources: ${profile.source_files.join(", ")}`);
|
|
2930
|
+
console.log(`\nTRAITS (${profile.traits.length})`);
|
|
2931
|
+
for (const trait of profile.traits) {
|
|
2932
|
+
console.log(` ${trait.id} [${trait.category}/${trait.confidence.toFixed(3)}] ${trait.claim}`);
|
|
2933
|
+
console.log(` ${trait.evidence.map((evidence) => `${evidence.ref} @ ${evidence.revision.slice(0, 12)} (${evidence.content_hash})`).join("; ")}`);
|
|
2934
|
+
}
|
|
2935
|
+
console.log("\nAdvisory observation only; no trait was adopted into graph authority.");
|
|
2936
|
+
}
|
|
2937
|
+
catch (error) {
|
|
2938
|
+
fail(error instanceof Error ? error.message : String(error));
|
|
2939
|
+
}
|
|
2940
|
+
});
|
|
2941
|
+
dnaCmd
|
|
2942
|
+
.command("match")
|
|
2943
|
+
.description("Explainably score a commit, PR, issue, or message against deterministic checks in an exact-revision DNA profile.")
|
|
2944
|
+
.requiredOption("--kind <kind>", "artifact kind: commit, pull_request, issue, or message")
|
|
2945
|
+
.requiredOption("--title <title>", "artifact title or commit subject")
|
|
2946
|
+
.option("--body <body>", "artifact body (for example a PR description)")
|
|
2947
|
+
.option("--ref <ref>", "Git commit/ref whose DNA should be used", "HEAD")
|
|
2948
|
+
.option("--json", "emit the complete machine-readable match envelope")
|
|
2949
|
+
.action((opts) => {
|
|
2950
|
+
try {
|
|
2951
|
+
if (!["commit", "pull_request", "issue", "message"].includes(opts.kind)) {
|
|
2952
|
+
return fail("--kind must be commit, pull_request, issue, or message");
|
|
2953
|
+
}
|
|
2954
|
+
const profile = discoverProjectDna(findRoot(), opts.ref);
|
|
2955
|
+
const match = evaluateProjectDnaMatch(profile, {
|
|
2956
|
+
kind: opts.kind,
|
|
2957
|
+
title: opts.title,
|
|
2958
|
+
body: opts.body,
|
|
2959
|
+
});
|
|
2960
|
+
if (opts.json) {
|
|
2961
|
+
console.log(JSON.stringify(match, null, 2));
|
|
2962
|
+
return;
|
|
2963
|
+
}
|
|
2964
|
+
console.log(`Project Match: ${match.score === null ? "not enough deterministic checks" : `${match.score.toFixed(1)}/100`}`);
|
|
2965
|
+
console.log(`Profile: ${match.profile_id} @ ${match.repository_revision}`);
|
|
2966
|
+
for (const check of match.checks.filter((candidate) => candidate.applicable)) {
|
|
2967
|
+
console.log(` ${check.passed ? "PASS" : "FAIL"} ${check.key} — ${check.detail}`);
|
|
2968
|
+
}
|
|
2969
|
+
console.log("Advisory only; the score does not grant or change enforcement authority.");
|
|
2970
|
+
}
|
|
2971
|
+
catch (error) {
|
|
2972
|
+
fail(error instanceof Error ? error.message : String(error));
|
|
2973
|
+
}
|
|
2974
|
+
});
|
|
2975
|
+
dnaCmd
|
|
2976
|
+
.command("context")
|
|
2977
|
+
.description("Render the bounded advisory Project DNA supplement used by normal Hunch context delivery.")
|
|
2978
|
+
.option("--ref <ref>", "Git commit/ref to inspect", "HEAD")
|
|
2979
|
+
.option("--traits <count>", "maximum traits in the orientation slice", "8")
|
|
2980
|
+
.option("--json", "emit the complete machine-readable supplement")
|
|
2981
|
+
.action((opts) => {
|
|
2982
|
+
try {
|
|
2983
|
+
const traitCap = Number(opts.traits);
|
|
2984
|
+
const supplement = projectDnaDeliverySupplement(discoverProjectDna(findRoot(), opts.ref), traitCap);
|
|
2985
|
+
if (opts.json)
|
|
2986
|
+
console.log(JSON.stringify(supplement, null, 2));
|
|
2987
|
+
else
|
|
2988
|
+
console.log(supplement?.text ?? "No evidence-backed Project DNA traits yet.");
|
|
2989
|
+
}
|
|
2990
|
+
catch (error) {
|
|
2991
|
+
fail(error instanceof Error ? error.message : String(error));
|
|
2992
|
+
}
|
|
2993
|
+
});
|
|
2994
|
+
dnaCmd
|
|
2995
|
+
.command("diff")
|
|
2996
|
+
.description("Compare two exact-revision DNA profiles without rewriting either snapshot.")
|
|
2997
|
+
.argument("<from>", "older Git revision")
|
|
2998
|
+
.argument("<to>", "newer Git revision")
|
|
2999
|
+
.option("--json", "emit the complete sealed delta")
|
|
3000
|
+
.action((from, to, opts) => {
|
|
3001
|
+
try {
|
|
3002
|
+
const root = findRoot();
|
|
3003
|
+
const delta = diffProjectDna(discoverProjectDna(root, from), discoverProjectDna(root, to));
|
|
3004
|
+
if (opts.json) {
|
|
3005
|
+
console.log(JSON.stringify(delta, null, 2));
|
|
3006
|
+
return;
|
|
3007
|
+
}
|
|
3008
|
+
console.log(`Project DNA drift ${delta.delta_id}: ${delta.changed ? `${delta.changes.length} change(s)` : "no observed change"}`);
|
|
3009
|
+
for (const change of delta.changes)
|
|
3010
|
+
console.log(` ${change.kind} ${change.key}`);
|
|
3011
|
+
console.log("Observation only; no Project DNA profile or graph authority was rewritten.");
|
|
3012
|
+
}
|
|
3013
|
+
catch (error) {
|
|
3014
|
+
fail(error instanceof Error ? error.message : String(error));
|
|
3015
|
+
}
|
|
3016
|
+
});
|
|
2906
3017
|
landscapeCmd
|
|
2907
3018
|
.command("adopt")
|
|
2908
3019
|
.description("Human-confirm a reviewed candidate set and write only those exact records through Hunch's normal graph boundary.")
|
|
@@ -3904,7 +4015,7 @@ program
|
|
|
3904
4015
|
!ctx.landscape?.resources.length &&
|
|
3905
4016
|
!ctx.landscape?.relationships.length;
|
|
3906
4017
|
if (empty && !asOf) {
|
|
3907
|
-
const hits = store.
|
|
4018
|
+
const hits = store.rankedSearch(target, 8);
|
|
3908
4019
|
if (hits.length) {
|
|
3909
4020
|
console.log(`No file/symbol resolves for "${target}" — closest graph matches instead:\n`);
|
|
3910
4021
|
for (const h of hits)
|