@davesheffer/hunch 1.21.0 → 1.22.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 +2 -2
- package/dist/cli/index.js +1 -1
- package/dist/cli/invocation.js +16 -3
- package/dist/core/projectDna.d.ts +81 -0
- package/dist/core/projectDna.js +105 -6
- package/dist/core/projectDnaDelivery.d.ts +15 -0
- package/dist/core/projectDnaDelta.d.ts +30 -0
- package/dist/core/projectDnaHostEvidence.d.ts +36 -0
- package/dist/core/projectDnaHostEvidence.js +174 -0
- package/dist/integrations/scaffold.js +18 -7
- package/dist/projectDna.d.ts +10 -0
- package/dist/projectDna.js +1 -0
- package/package.json +3 -1
- package/server.json +2 -2
package/README.md
CHANGED
|
@@ -174,7 +174,7 @@ repository, separate from the code repository. Hunch does not host it. Create a
|
|
|
174
174
|
that every teammate can access, install Hunch on team machines and CI, then have one maintainer run:
|
|
175
175
|
|
|
176
176
|
```bash
|
|
177
|
-
npm i -g @davesheffer/hunch@1.
|
|
177
|
+
npm i -g @davesheffer/hunch@1.22.0
|
|
178
178
|
hunch shared --repo git@github.com:acme/project-hunch-memory.git
|
|
179
179
|
git add .gitignore .hunch/team.json
|
|
180
180
|
git commit -m "chore: connect shared Hunch memory"
|
|
@@ -189,7 +189,7 @@ printed by Hunch. Omit `--migrate` for a new setup.
|
|
|
189
189
|
After the pointer commit lands, teammates need Hunch installed and Git access to the memory repo:
|
|
190
190
|
|
|
191
191
|
```bash
|
|
192
|
-
npm i -g @davesheffer/hunch@1.
|
|
192
|
+
npm i -g @davesheffer/hunch@1.22.0
|
|
193
193
|
git pull
|
|
194
194
|
hunch init
|
|
195
195
|
hunch doctor
|
package/dist/cli/index.js
CHANGED
|
@@ -309,7 +309,7 @@ program
|
|
|
309
309
|
// Claude's native hooks run alongside provider-specific hooks below. Every
|
|
310
310
|
// adapter reads firmness at run time, so changing it needs no config rewrite.
|
|
311
311
|
if (opts.agentHooks !== false) {
|
|
312
|
-
const a = installClaudeHooks(root, `${inv.
|
|
312
|
+
const a = installClaudeHooks(root, `${inv.agentHookShell} hook`);
|
|
313
313
|
console.log(` ✓ Claude Code agent hooks ${a.action} (firmness: ${firmness} — change with \`hunch firmness <level>\`)`);
|
|
314
314
|
}
|
|
315
315
|
// Multi-assistant compatibility: MCP + grounding + lifecycle adapters share
|
package/dist/cli/invocation.js
CHANGED
|
@@ -16,6 +16,13 @@ export function dim(s) {
|
|
|
16
16
|
export function publishedMcpInvocation() {
|
|
17
17
|
return { command: "npx", args: ["-y", `--package=${HUNCH_NPX_PACKAGE_SPEC}`, "hunch"] };
|
|
18
18
|
}
|
|
19
|
+
/** Render a structured invocation for the host shell. Safe tokens stay bare so
|
|
20
|
+
* PowerShell can execute the first token; paths and other unsafe tokens use
|
|
21
|
+
* JSON string quoting, which is accepted by PowerShell, cmd, and POSIX sh. */
|
|
22
|
+
export function shellInvocation(inv) {
|
|
23
|
+
const token = (part) => /^[A-Za-z0-9_@:=+.,/-]+$/.test(part) ? part : JSON.stringify(part);
|
|
24
|
+
return [inv.command, ...inv.args].map(token).join(" ");
|
|
25
|
+
}
|
|
19
26
|
/** The doctor command's synthesis-status line(s) for a resolved provider.
|
|
20
27
|
* Exported for testing — the previous version (a bare provider-name switch,
|
|
21
28
|
* before the resolveSynthesisProvider preference system existed) had zero
|
|
@@ -77,23 +84,29 @@ export function resolveInvocation() {
|
|
|
77
84
|
// absolute-node invocation below.
|
|
78
85
|
const installed = !isDev && entry.replace(/\\/g, "/").includes("/node_modules/");
|
|
79
86
|
if (installed) {
|
|
87
|
+
const mcp = publishedMcpInvocation();
|
|
80
88
|
return {
|
|
81
89
|
shell: `${q(process.execPath)} ${q(entry)}`,
|
|
82
|
-
|
|
90
|
+
agentHookShell: shellInvocation(mcp),
|
|
91
|
+
mcp,
|
|
83
92
|
};
|
|
84
93
|
}
|
|
85
94
|
if (isDev) {
|
|
95
|
+
const mcp = { command: "npx", args: ["tsx", entry] };
|
|
86
96
|
return {
|
|
87
97
|
shell: `npx tsx ${q(entry)}`,
|
|
88
|
-
|
|
98
|
+
agentHookShell: shellInvocation(mcp),
|
|
99
|
+
mcp,
|
|
89
100
|
};
|
|
90
101
|
}
|
|
91
102
|
// Source-checkout dist run (e.g. `node dist/cli/index.js`, npm link): inherently
|
|
92
103
|
// per-machine. Use the absolute node binary (process.execPath) rather than a bare
|
|
93
104
|
// `node`, so the hook works even when nvm's `node` isn't on the hook's PATH.
|
|
105
|
+
const mcp = { command: process.execPath, args: [entry] };
|
|
94
106
|
return {
|
|
95
107
|
shell: `${q(process.execPath)} ${q(entry)}`,
|
|
96
|
-
|
|
108
|
+
agentHookShell: shellInvocation(mcp),
|
|
109
|
+
mcp,
|
|
97
110
|
};
|
|
98
111
|
}
|
|
99
112
|
//# sourceMappingURL=invocation.js.map
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { type ProjectDnaHostEvidence } from "./projectDnaHostEvidence.js";
|
|
2
|
+
export declare const PROJECT_DNA_SCHEMA_VERSION: "hunch.project-dna/1";
|
|
3
|
+
export declare const PROJECT_DNA_MATCH_SCHEMA_VERSION: "hunch.project-dna-match/1";
|
|
4
|
+
export declare const PROJECT_DNA_CATEGORIES: readonly ["communication", "engineering", "review", "culture", "vocabulary"];
|
|
5
|
+
export type ProjectDnaCategory = (typeof PROJECT_DNA_CATEGORIES)[number];
|
|
6
|
+
export type ProjectDnaEvidenceKind = "git-history" | "committed-file" | "host-evidence";
|
|
7
|
+
export interface ProjectDnaEvidence {
|
|
8
|
+
kind: ProjectDnaEvidenceKind;
|
|
9
|
+
ref: string;
|
|
10
|
+
revision: string;
|
|
11
|
+
content_hash: string;
|
|
12
|
+
sample_count: number;
|
|
13
|
+
provenance: "committed-repository" | "host-provided";
|
|
14
|
+
visibility: "repository";
|
|
15
|
+
}
|
|
16
|
+
export interface ProjectDnaTrait {
|
|
17
|
+
id: string;
|
|
18
|
+
category: ProjectDnaCategory;
|
|
19
|
+
key: string;
|
|
20
|
+
claim: string;
|
|
21
|
+
confidence: number;
|
|
22
|
+
observation_state: "observed";
|
|
23
|
+
freshness: "current";
|
|
24
|
+
contradiction: "none";
|
|
25
|
+
evidence: ProjectDnaEvidence[];
|
|
26
|
+
}
|
|
27
|
+
export interface ProjectDnaProfile {
|
|
28
|
+
schema: typeof PROJECT_DNA_SCHEMA_VERSION;
|
|
29
|
+
profile_id: string;
|
|
30
|
+
/** Clone-stable identity for the repository lineage, derived from its root commits. */
|
|
31
|
+
repository_id: string;
|
|
32
|
+
repository_revision: string;
|
|
33
|
+
history_sample_count: number;
|
|
34
|
+
source_files: string[];
|
|
35
|
+
traits: ProjectDnaTrait[];
|
|
36
|
+
content_hash: string;
|
|
37
|
+
}
|
|
38
|
+
export interface ProjectDnaDiscoveryOptions {
|
|
39
|
+
/** A sealed batch selected and authorized by the host for this exact revision. */
|
|
40
|
+
hostEvidence?: ProjectDnaHostEvidence;
|
|
41
|
+
}
|
|
42
|
+
export interface ProjectDnaArtifact {
|
|
43
|
+
kind: "commit" | "pull_request" | "issue" | "message";
|
|
44
|
+
title: string;
|
|
45
|
+
body?: string;
|
|
46
|
+
}
|
|
47
|
+
export interface ProjectDnaMatchCheck {
|
|
48
|
+
trait_id: string;
|
|
49
|
+
key: string;
|
|
50
|
+
applicable: boolean;
|
|
51
|
+
passed: boolean | null;
|
|
52
|
+
weight: number;
|
|
53
|
+
detail: string;
|
|
54
|
+
}
|
|
55
|
+
export interface ProjectDnaMatch {
|
|
56
|
+
schema: typeof PROJECT_DNA_MATCH_SCHEMA_VERSION;
|
|
57
|
+
match_id: string;
|
|
58
|
+
profile_id: string;
|
|
59
|
+
repository_id: string;
|
|
60
|
+
repository_revision: string;
|
|
61
|
+
artifact_kind: ProjectDnaArtifact["kind"];
|
|
62
|
+
score: number | null;
|
|
63
|
+
applicable_checks: number;
|
|
64
|
+
checks: ProjectDnaMatchCheck[];
|
|
65
|
+
content_hash: string;
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Derive a deterministic repository DNA profile from one exact Git revision.
|
|
69
|
+
*
|
|
70
|
+
* This is intentionally observation, not authority: it reads bounded committed
|
|
71
|
+
* history and bounded committed convention files. A host may additionally pass a
|
|
72
|
+
* sealed, revision-bound evidence batch that it already authorized; discovery
|
|
73
|
+
* never fetches a provider itself. It does not read the worktree, network, model
|
|
74
|
+
* output, credentials, or private user state, and it never writes into the durable
|
|
75
|
+
* Hunch graph by itself.
|
|
76
|
+
*/
|
|
77
|
+
export declare function discoverProjectDna(root: string, ref?: string, options?: ProjectDnaDiscoveryOptions): ProjectDnaProfile;
|
|
78
|
+
export declare function assertProjectDnaProfile(value: unknown): asserts value is ProjectDnaProfile;
|
|
79
|
+
/** Score only traits that have a deterministic check for the supplied artifact. */
|
|
80
|
+
export declare function evaluateProjectDnaMatch(profileValue: unknown, artifact: ProjectDnaArtifact): ProjectDnaMatch;
|
|
81
|
+
export declare function assertProjectDnaMatch(value: unknown): asserts value is ProjectDnaMatch;
|
package/dist/core/projectDna.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { execFileSync } from "node:child_process";
|
|
2
2
|
import { createHash } from "node:crypto";
|
|
3
3
|
import { compareCodeUnits } from "./canonicalOrder.js";
|
|
4
|
+
import { assertProjectDnaHostEvidence, } from "./projectDnaHostEvidence.js";
|
|
4
5
|
export const PROJECT_DNA_SCHEMA_VERSION = "hunch.project-dna/1";
|
|
5
6
|
export const PROJECT_DNA_MATCH_SCHEMA_VERSION = "hunch.project-dna-match/1";
|
|
6
7
|
const GIT_OBJECT = /^[a-f0-9]{40,64}$/;
|
|
@@ -155,6 +156,17 @@ function fileEvidence(revision, path, bytes) {
|
|
|
155
156
|
visibility: "repository",
|
|
156
157
|
};
|
|
157
158
|
}
|
|
159
|
+
function hostEvidenceEvidence(revision, hostEvidence, sampleCount) {
|
|
160
|
+
return {
|
|
161
|
+
kind: "host-evidence",
|
|
162
|
+
ref: `host:${hostEvidence.evidence_set_id}`,
|
|
163
|
+
revision,
|
|
164
|
+
content_hash: hostEvidence.content_hash,
|
|
165
|
+
sample_count: sampleCount,
|
|
166
|
+
provenance: "host-provided",
|
|
167
|
+
visibility: "repository",
|
|
168
|
+
};
|
|
169
|
+
}
|
|
158
170
|
function repositoryId(root, revision) {
|
|
159
171
|
const roots = gitText(root, ["rev-list", "--max-parents=0", revision, "--"])
|
|
160
172
|
.split("\n")
|
|
@@ -264,6 +276,70 @@ function collectFileTraits(revision, files) {
|
|
|
264
276
|
}
|
|
265
277
|
return [...byKey.values()].map(({ rule, evidence }) => makeTrait(rule.category, rule.key, rule.claim, Math.min(0.98, 0.8 + Math.min(3, evidence.length) * 0.05), evidence));
|
|
266
278
|
}
|
|
279
|
+
function collectHostEvidenceTraits(revision, hostEvidence) {
|
|
280
|
+
if (!hostEvidence)
|
|
281
|
+
return [];
|
|
282
|
+
assertProjectDnaHostEvidence(hostEvidence);
|
|
283
|
+
if (hostEvidence.repository_revision !== revision) {
|
|
284
|
+
throw new Error("Project DNA host evidence revision does not match the repository revision");
|
|
285
|
+
}
|
|
286
|
+
const traits = [];
|
|
287
|
+
const pullRequests = hostEvidence.items.filter((item) => item.kind === "pull_request" && item.disposition === "merged");
|
|
288
|
+
if (pullRequests.length >= MIN_HISTORY) {
|
|
289
|
+
const titles = pullRequests.map((item) => item.title);
|
|
290
|
+
const evidence = [hostEvidenceEvidence(revision, hostEvidence, pullRequests.length)];
|
|
291
|
+
const count = titles.length;
|
|
292
|
+
const conventional = titles.filter(conventionalSubject).length;
|
|
293
|
+
const noTerminalPeriod = titles.filter((title) => !/[.!?]$/.test(title.trim())).length;
|
|
294
|
+
const lowercase = titles.filter((title) => {
|
|
295
|
+
const first = firstAlphabetic(title.replace(/^(?:build|chore|ci|docs|feat|fix|perf|refactor|revert|style|test)(?:\([^)]+\))?!?:\s*/, ""));
|
|
296
|
+
return first !== null && first === first.toLowerCase();
|
|
297
|
+
}).length;
|
|
298
|
+
const issueRefs = titles.filter((title) => /(?:^|\s)#\d+\b/.test(title)).length;
|
|
299
|
+
if (conventional / count >= 0.7) {
|
|
300
|
+
traits.push(makeTrait("communication", "pull_request.conventional_title", "Pull-request titles usually use Conventional Commit prefixes.", confidence(conventional / count, count), evidence));
|
|
301
|
+
}
|
|
302
|
+
if (noTerminalPeriod / count >= 0.8) {
|
|
303
|
+
traits.push(makeTrait("communication", "pull_request.no_terminal_punctuation", "Pull-request titles usually omit terminal punctuation.", confidence(noTerminalPeriod / count, count), evidence));
|
|
304
|
+
}
|
|
305
|
+
if (lowercase / count >= 0.7) {
|
|
306
|
+
traits.push(makeTrait("communication", "pull_request.lowercase_lead", "Pull-request titles usually begin their descriptive phrase with lowercase wording.", confidence(lowercase / count, count), evidence));
|
|
307
|
+
}
|
|
308
|
+
if (issueRefs / count >= 0.45) {
|
|
309
|
+
traits.push(makeTrait("communication", "pull_request.issue_reference", "Pull-request titles frequently reference an issue number.", confidence(issueRefs / count, count), evidence));
|
|
310
|
+
}
|
|
311
|
+
const words = new Map();
|
|
312
|
+
for (const title of titles) {
|
|
313
|
+
const seen = new Set((title
|
|
314
|
+
.replace(/^(?:build|chore|ci|docs|feat|fix|perf|refactor|revert|style|test)(?:\([^)]+\))?!?:\s*/, "")
|
|
315
|
+
.toLowerCase()
|
|
316
|
+
.match(/[a-z][a-z0-9_-]{2,30}/g) ?? [])
|
|
317
|
+
.filter((token) => !STOP_WORDS.has(token) && !/^\d+$/.test(token)));
|
|
318
|
+
for (const token of seen)
|
|
319
|
+
words.set(token, (words.get(token) ?? 0) + 1);
|
|
320
|
+
}
|
|
321
|
+
const vocabulary = [...words.entries()]
|
|
322
|
+
.filter(([, occurrences]) => occurrences >= Math.max(3, Math.ceil(count * 0.2)))
|
|
323
|
+
.sort((left, right) => right[1] - left[1] || compareCodeUnits(left[0], right[0]))
|
|
324
|
+
.slice(0, 8);
|
|
325
|
+
for (const [word, occurrences] of vocabulary) {
|
|
326
|
+
traits.push(makeTrait("vocabulary", `term.${word}`, `The repository repeatedly uses the term “${word}” in merged pull-request titles.`, confidence(occurrences / count, count, 0.55), evidence));
|
|
327
|
+
}
|
|
328
|
+
const bodies = pullRequests.map((item) => item.body).filter((body) => body !== null);
|
|
329
|
+
const rationaleCount = bodies.filter((body) => FILE_RULES.find((rule) => rule.key === "pr.explain_why").pattern.test(body)).length;
|
|
330
|
+
if (rationaleCount >= 2 && rationaleCount / Math.max(1, bodies.length) >= 0.6) {
|
|
331
|
+
traits.push(makeTrait("communication", "pr.explain_why", "Pull requests are expected to explain motivation or rationale, not only the code change.", confidence(rationaleCount / bodies.length, bodies.length), [hostEvidenceEvidence(revision, hostEvidence, rationaleCount)]));
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
const maintainerReviews = hostEvidence.items.filter((item) => item.kind === "review_comment" && item.author_role === "maintainer");
|
|
335
|
+
for (const rule of FILE_RULES) {
|
|
336
|
+
const matching = maintainerReviews.filter((item) => rule.pattern.test(item.body));
|
|
337
|
+
if (matching.length < 2)
|
|
338
|
+
continue;
|
|
339
|
+
traits.push(makeTrait(rule.category, rule.key, rule.claim, confidence(matching.length / maintainerReviews.length, matching.length, 0.65), [hostEvidenceEvidence(revision, hostEvidence, matching.length)]));
|
|
340
|
+
}
|
|
341
|
+
return traits;
|
|
342
|
+
}
|
|
267
343
|
function dedupeTraits(traits) {
|
|
268
344
|
const byKey = new Map();
|
|
269
345
|
for (const trait of traits) {
|
|
@@ -279,11 +355,13 @@ function dedupeTraits(traits) {
|
|
|
279
355
|
* Derive a deterministic repository DNA profile from one exact Git revision.
|
|
280
356
|
*
|
|
281
357
|
* This is intentionally observation, not authority: it reads bounded committed
|
|
282
|
-
* history and bounded committed convention files.
|
|
283
|
-
*
|
|
284
|
-
*
|
|
358
|
+
* history and bounded committed convention files. A host may additionally pass a
|
|
359
|
+
* sealed, revision-bound evidence batch that it already authorized; discovery
|
|
360
|
+
* never fetches a provider itself. It does not read the worktree, network, model
|
|
361
|
+
* output, credentials, or private user state, and it never writes into the durable
|
|
362
|
+
* Hunch graph by itself.
|
|
285
363
|
*/
|
|
286
|
-
export function discoverProjectDna(root, ref = "HEAD") {
|
|
364
|
+
export function discoverProjectDna(root, ref = "HEAD", options = {}) {
|
|
287
365
|
const repositoryRevision = exactCommit(root, ref);
|
|
288
366
|
const repositoryIdentity = repositoryId(root, repositoryRevision);
|
|
289
367
|
const historyRaw = gitText(root, [
|
|
@@ -299,6 +377,7 @@ export function discoverProjectDna(root, ref = "HEAD") {
|
|
|
299
377
|
const traits = dedupeTraits([
|
|
300
378
|
...collectHistoryTraits(repositoryRevision, subjects),
|
|
301
379
|
...collectFileTraits(repositoryRevision, files),
|
|
380
|
+
...collectHostEvidenceTraits(repositoryRevision, options.hostEvidence),
|
|
302
381
|
]);
|
|
303
382
|
const unsigned = {
|
|
304
383
|
schema: PROJECT_DNA_SCHEMA_VERSION,
|
|
@@ -360,10 +439,14 @@ export function assertProjectDnaProfile(value) {
|
|
|
360
439
|
assertExactFields(evidence, [
|
|
361
440
|
"kind", "ref", "revision", "content_hash", "sample_count", "provenance", "visibility",
|
|
362
441
|
], "project DNA evidence");
|
|
363
|
-
|
|
442
|
+
const committedEvidence = evidence.kind === "git-history" || evidence.kind === "committed-file";
|
|
443
|
+
const hostEvidence = evidence.kind === "host-evidence";
|
|
444
|
+
if ((!committedEvidence && !hostEvidence) || !evidence.ref.trim() || evidence.ref.length > 512
|
|
364
445
|
|| evidence.revision !== profile.repository_revision || !SHA256.test(evidence.content_hash)
|
|
365
446
|
|| !Number.isSafeInteger(evidence.sample_count) || evidence.sample_count < 1 || evidence.sample_count > MAX_HISTORY
|
|
366
|
-
|| evidence.provenance !== "committed-repository"
|
|
447
|
+
|| (committedEvidence && evidence.provenance !== "committed-repository")
|
|
448
|
+
|| (hostEvidence && (evidence.provenance !== "host-provided" || !/^host:pdnah_[a-f0-9]{24}$/.test(evidence.ref)))
|
|
449
|
+
|| evidence.visibility !== "repository") {
|
|
367
450
|
throw new Error("project DNA evidence fields are invalid");
|
|
368
451
|
}
|
|
369
452
|
}
|
|
@@ -390,6 +473,22 @@ function artifactCheck(trait, artifact) {
|
|
|
390
473
|
return { trait_id: trait.id, key: trait.key, applicable: first !== null, passed: first === null ? null : first === first.toLowerCase(), weight, detail: "Descriptive title wording begins lowercase." };
|
|
391
474
|
case "subject.issue_reference":
|
|
392
475
|
return { trait_id: trait.id, key: trait.key, applicable: true, passed: /(?:^|\s)#\d+\b/.test(title), weight, detail: "Title carries an issue reference." };
|
|
476
|
+
case "pull_request.conventional_title":
|
|
477
|
+
return artifact.kind === "pull_request"
|
|
478
|
+
? { trait_id: trait.id, key: trait.key, applicable: true, passed: conventionalSubject(title), weight, detail: "PR title follows the repository's observed Conventional Commit pattern." }
|
|
479
|
+
: { trait_id: trait.id, key: trait.key, applicable: false, passed: null, weight, detail: "This trait applies only to pull-request titles." };
|
|
480
|
+
case "pull_request.no_terminal_punctuation":
|
|
481
|
+
return artifact.kind === "pull_request"
|
|
482
|
+
? { trait_id: trait.id, key: trait.key, applicable: true, passed: !/[.!?]$/.test(title), weight, detail: "PR title omits terminal punctuation." }
|
|
483
|
+
: { trait_id: trait.id, key: trait.key, applicable: false, passed: null, weight, detail: "This trait applies only to pull-request titles." };
|
|
484
|
+
case "pull_request.lowercase_lead":
|
|
485
|
+
return artifact.kind === "pull_request"
|
|
486
|
+
? { trait_id: trait.id, key: trait.key, applicable: first !== null, passed: first === null ? null : first === first.toLowerCase(), weight, detail: "Descriptive PR-title wording begins lowercase." }
|
|
487
|
+
: { trait_id: trait.id, key: trait.key, applicable: false, passed: null, weight, detail: "This trait applies only to pull-request titles." };
|
|
488
|
+
case "pull_request.issue_reference":
|
|
489
|
+
return artifact.kind === "pull_request"
|
|
490
|
+
? { trait_id: trait.id, key: trait.key, applicable: true, passed: /(?:^|\s)#\d+\b/.test(title), weight, detail: "PR title carries an issue reference." }
|
|
491
|
+
: { trait_id: trait.id, key: trait.key, applicable: false, passed: null, weight, detail: "This trait applies only to pull-request titles." };
|
|
393
492
|
case "pr.explain_why": {
|
|
394
493
|
const body = artifact.body?.trim() ?? "";
|
|
395
494
|
const applicable = artifact.kind === "pull_request";
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export declare const PROJECT_DNA_SUPPLEMENT_KIND: "project-dna";
|
|
2
|
+
export interface ProjectDnaDeliverySupplement {
|
|
3
|
+
id: string;
|
|
4
|
+
kind: typeof PROJECT_DNA_SUPPLEMENT_KIND;
|
|
5
|
+
text: string;
|
|
6
|
+
priority: number;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Render Project DNA through Hunch's existing DeliverySupplement seam.
|
|
10
|
+
*
|
|
11
|
+
* The caller still owns the final hard budget via buildDeliveryEnvelope(); this
|
|
12
|
+
* function only prepares compact, evidence-identifiable orientation text. The
|
|
13
|
+
* profile ID/revision remain visible so a host can preserve provider provenance.
|
|
14
|
+
*/
|
|
15
|
+
export declare function projectDnaDeliverySupplement(profileValue: unknown, traitCap?: number): ProjectDnaDeliverySupplement | null;
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
export declare const PROJECT_DNA_DELTA_SCHEMA_VERSION: "hunch.project-dna-delta/1";
|
|
2
|
+
export type ProjectDnaChangeKind = "added" | "removed" | "evidence_changed" | "confidence_changed";
|
|
3
|
+
export interface ProjectDnaTraitChange {
|
|
4
|
+
key: string;
|
|
5
|
+
kind: ProjectDnaChangeKind;
|
|
6
|
+
before_trait_id: string | null;
|
|
7
|
+
after_trait_id: string | null;
|
|
8
|
+
before_confidence: number | null;
|
|
9
|
+
after_confidence: number | null;
|
|
10
|
+
}
|
|
11
|
+
export interface ProjectDnaDelta {
|
|
12
|
+
schema: typeof PROJECT_DNA_DELTA_SCHEMA_VERSION;
|
|
13
|
+
delta_id: string;
|
|
14
|
+
repository_id: string;
|
|
15
|
+
from_profile_id: string;
|
|
16
|
+
to_profile_id: string;
|
|
17
|
+
from_revision: string;
|
|
18
|
+
to_revision: string;
|
|
19
|
+
changes: ProjectDnaTraitChange[];
|
|
20
|
+
changed: boolean;
|
|
21
|
+
content_hash: string;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Compare two already-sealed profiles without inferring causality.
|
|
25
|
+
*
|
|
26
|
+
* A delta says only that observed DNA changed between exact revisions. It does
|
|
27
|
+
* not promote the new trait, explain why the change happened, or grant policy.
|
|
28
|
+
*/
|
|
29
|
+
export declare function diffProjectDna(fromValue: unknown, toValue: unknown): ProjectDnaDelta;
|
|
30
|
+
export declare function assertProjectDnaDelta(value: unknown): asserts value is ProjectDnaDelta;
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
export declare const PROJECT_DNA_HOST_EVIDENCE_SCHEMA_VERSION: "hunch.project-dna-host-evidence/1";
|
|
2
|
+
export declare const PROJECT_DNA_HOST_EVIDENCE_KINDS: readonly ["pull_request", "review_comment"];
|
|
3
|
+
export declare const PROJECT_DNA_HOST_EVIDENCE_DISPOSITIONS: readonly ["merged", "approved", "changes_requested", "commented"];
|
|
4
|
+
export declare const PROJECT_DNA_HOST_EVIDENCE_AUTHOR_ROLES: readonly ["maintainer", "contributor", "unknown"];
|
|
5
|
+
export type ProjectDnaHostEvidenceKind = (typeof PROJECT_DNA_HOST_EVIDENCE_KINDS)[number];
|
|
6
|
+
export type ProjectDnaHostEvidenceDisposition = (typeof PROJECT_DNA_HOST_EVIDENCE_DISPOSITIONS)[number];
|
|
7
|
+
export type ProjectDnaHostEvidenceAuthorRole = (typeof PROJECT_DNA_HOST_EVIDENCE_AUTHOR_ROLES)[number];
|
|
8
|
+
export interface ProjectDnaHostEvidenceCandidate {
|
|
9
|
+
kind: ProjectDnaHostEvidenceKind;
|
|
10
|
+
/** Credential-free, repository-local source identity such as github:pull-request:42. */
|
|
11
|
+
ref: string;
|
|
12
|
+
disposition: ProjectDnaHostEvidenceDisposition;
|
|
13
|
+
author_role: ProjectDnaHostEvidenceAuthorRole;
|
|
14
|
+
title: string | null;
|
|
15
|
+
body: string | null;
|
|
16
|
+
}
|
|
17
|
+
export interface ProjectDnaHostEvidenceItem extends ProjectDnaHostEvidenceCandidate {
|
|
18
|
+
item_id: string;
|
|
19
|
+
content_hash: string;
|
|
20
|
+
}
|
|
21
|
+
export interface ProjectDnaHostEvidence {
|
|
22
|
+
schema: typeof PROJECT_DNA_HOST_EVIDENCE_SCHEMA_VERSION;
|
|
23
|
+
repository_revision: string;
|
|
24
|
+
items: ProjectDnaHostEvidenceItem[];
|
|
25
|
+
evidence_set_id: string;
|
|
26
|
+
content_hash: string;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Seal an explicitly authorized, repository-visible host evidence batch.
|
|
30
|
+
*
|
|
31
|
+
* This function never fetches a provider and never grants the host evidence
|
|
32
|
+
* policy authority. The caller owns source authorization and may pass only
|
|
33
|
+
* credential-free refs plus bounded repository-visible text.
|
|
34
|
+
*/
|
|
35
|
+
export declare function sealProjectDnaHostEvidence(repositoryRevision: string, candidates: readonly ProjectDnaHostEvidenceCandidate[]): ProjectDnaHostEvidence;
|
|
36
|
+
export declare function assertProjectDnaHostEvidence(value: unknown): asserts value is ProjectDnaHostEvidence;
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { compareCodeUnits } from "./canonicalOrder.js";
|
|
3
|
+
export const PROJECT_DNA_HOST_EVIDENCE_SCHEMA_VERSION = "hunch.project-dna-host-evidence/1";
|
|
4
|
+
const GIT_OBJECT = /^[a-f0-9]{40,64}$/;
|
|
5
|
+
const SHA256 = /^sha256:[a-f0-9]{64}$/;
|
|
6
|
+
const ITEM_ID = /^pdnahi_[a-f0-9]{20}$/;
|
|
7
|
+
const SET_ID = /^pdnah_[a-f0-9]{24}$/;
|
|
8
|
+
const SOURCE_REF = /^[A-Za-z0-9][A-Za-z0-9._:/#-]{0,255}$/;
|
|
9
|
+
const MAX_ITEMS = 64;
|
|
10
|
+
const MAX_TITLE = 500;
|
|
11
|
+
const MAX_BODY = 8_000;
|
|
12
|
+
export const PROJECT_DNA_HOST_EVIDENCE_KINDS = ["pull_request", "review_comment"];
|
|
13
|
+
export const PROJECT_DNA_HOST_EVIDENCE_DISPOSITIONS = [
|
|
14
|
+
"merged", "approved", "changes_requested", "commented",
|
|
15
|
+
];
|
|
16
|
+
export const PROJECT_DNA_HOST_EVIDENCE_AUTHOR_ROLES = ["maintainer", "contributor", "unknown"];
|
|
17
|
+
function canonical(value) {
|
|
18
|
+
if (Array.isArray(value))
|
|
19
|
+
return `[${value.map(canonical).join(",")}]`;
|
|
20
|
+
if (value && typeof value === "object") {
|
|
21
|
+
return `{${Object.entries(value)
|
|
22
|
+
.filter(([, child]) => child !== undefined)
|
|
23
|
+
.sort(([left], [right]) => compareCodeUnits(left, right))
|
|
24
|
+
.map(([key, child]) => `${JSON.stringify(key)}:${canonical(child)}`)
|
|
25
|
+
.join(",")}}`;
|
|
26
|
+
}
|
|
27
|
+
return JSON.stringify(value) ?? "null";
|
|
28
|
+
}
|
|
29
|
+
function sha256(value) {
|
|
30
|
+
return `sha256:${createHash("sha256").update(value).digest("hex")}`;
|
|
31
|
+
}
|
|
32
|
+
function exactFields(value, fields) {
|
|
33
|
+
return Object.keys(value).sort(compareCodeUnits).join("\0") === [...fields].sort(compareCodeUnits).join("\0");
|
|
34
|
+
}
|
|
35
|
+
function boundedText(value, maximum, label) {
|
|
36
|
+
if (value === null)
|
|
37
|
+
return null;
|
|
38
|
+
if (typeof value !== "string")
|
|
39
|
+
throw new Error(`Project DNA host evidence ${label} is invalid`);
|
|
40
|
+
const normalized = value.replace(/\r\n?/g, "\n").trim();
|
|
41
|
+
if (!normalized || normalized.length > maximum || normalized.includes("\0")) {
|
|
42
|
+
throw new Error(`Project DNA host evidence ${label} is invalid`);
|
|
43
|
+
}
|
|
44
|
+
return normalized;
|
|
45
|
+
}
|
|
46
|
+
function dispositionMatchesKind(kind, disposition) {
|
|
47
|
+
return kind === "pull_request"
|
|
48
|
+
? disposition === "merged"
|
|
49
|
+
: disposition === "approved" || disposition === "changes_requested" || disposition === "commented";
|
|
50
|
+
}
|
|
51
|
+
function sealItem(candidate) {
|
|
52
|
+
if (!candidate || typeof candidate !== "object" || Array.isArray(candidate)
|
|
53
|
+
|| !exactFields(candidate, [
|
|
54
|
+
"kind", "ref", "disposition", "author_role", "title", "body",
|
|
55
|
+
])
|
|
56
|
+
|| !PROJECT_DNA_HOST_EVIDENCE_KINDS.includes(candidate.kind)
|
|
57
|
+
|| !SOURCE_REF.test(candidate.ref)
|
|
58
|
+
|| !PROJECT_DNA_HOST_EVIDENCE_DISPOSITIONS.includes(candidate.disposition)
|
|
59
|
+
|| !PROJECT_DNA_HOST_EVIDENCE_AUTHOR_ROLES.includes(candidate.author_role)
|
|
60
|
+
|| !dispositionMatchesKind(candidate.kind, candidate.disposition)) {
|
|
61
|
+
throw new Error("Project DNA host evidence candidate fields are invalid");
|
|
62
|
+
}
|
|
63
|
+
const unsigned = {
|
|
64
|
+
kind: candidate.kind,
|
|
65
|
+
ref: candidate.ref,
|
|
66
|
+
disposition: candidate.disposition,
|
|
67
|
+
author_role: candidate.author_role,
|
|
68
|
+
title: boundedText(candidate.title, MAX_TITLE, "title"),
|
|
69
|
+
body: boundedText(candidate.body, MAX_BODY, "body"),
|
|
70
|
+
};
|
|
71
|
+
if (unsigned.title === null && unsigned.body === null) {
|
|
72
|
+
throw new Error("Project DNA host evidence candidate has no observable content");
|
|
73
|
+
}
|
|
74
|
+
if (unsigned.kind === "pull_request" && unsigned.title === null) {
|
|
75
|
+
throw new Error("Project DNA pull-request evidence requires a title");
|
|
76
|
+
}
|
|
77
|
+
if (unsigned.kind === "review_comment" && (unsigned.title !== null || unsigned.body === null)) {
|
|
78
|
+
throw new Error("Project DNA review-comment evidence requires only a body");
|
|
79
|
+
}
|
|
80
|
+
const itemId = `pdnahi_${sha256(canonical(unsigned)).slice("sha256:".length, "sha256:".length + 20)}`;
|
|
81
|
+
const sealed = { ...unsigned, item_id: itemId };
|
|
82
|
+
return { ...sealed, content_hash: sha256(canonical(sealed)) };
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Seal an explicitly authorized, repository-visible host evidence batch.
|
|
86
|
+
*
|
|
87
|
+
* This function never fetches a provider and never grants the host evidence
|
|
88
|
+
* policy authority. The caller owns source authorization and may pass only
|
|
89
|
+
* credential-free refs plus bounded repository-visible text.
|
|
90
|
+
*/
|
|
91
|
+
export function sealProjectDnaHostEvidence(repositoryRevision, candidates) {
|
|
92
|
+
if (!GIT_OBJECT.test(repositoryRevision) || !Array.isArray(candidates)
|
|
93
|
+
|| candidates.length < 1 || candidates.length > MAX_ITEMS) {
|
|
94
|
+
throw new Error("Project DNA host evidence set fields are invalid");
|
|
95
|
+
}
|
|
96
|
+
const items = candidates.map(sealItem)
|
|
97
|
+
.sort((left, right) => compareCodeUnits(left.item_id, right.item_id));
|
|
98
|
+
if (new Set(items.map((item) => item.item_id)).size !== items.length
|
|
99
|
+
|| new Set(items.map((item) => item.ref)).size !== items.length) {
|
|
100
|
+
throw new Error("Project DNA host evidence items must have unique source identities");
|
|
101
|
+
}
|
|
102
|
+
const unsigned = {
|
|
103
|
+
schema: PROJECT_DNA_HOST_EVIDENCE_SCHEMA_VERSION,
|
|
104
|
+
repository_revision: repositoryRevision,
|
|
105
|
+
items,
|
|
106
|
+
};
|
|
107
|
+
const evidenceSetId = `pdnah_${sha256(canonical(unsigned)).slice("sha256:".length, "sha256:".length + 24)}`;
|
|
108
|
+
const sealed = { ...unsigned, evidence_set_id: evidenceSetId };
|
|
109
|
+
const result = { ...sealed, content_hash: sha256(canonical(sealed)) };
|
|
110
|
+
assertProjectDnaHostEvidence(result);
|
|
111
|
+
return result;
|
|
112
|
+
}
|
|
113
|
+
export function assertProjectDnaHostEvidence(value) {
|
|
114
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
115
|
+
throw new Error("Project DNA host evidence set is invalid");
|
|
116
|
+
}
|
|
117
|
+
const evidence = value;
|
|
118
|
+
if (!exactFields(value, [
|
|
119
|
+
"schema", "repository_revision", "items", "evidence_set_id", "content_hash",
|
|
120
|
+
])
|
|
121
|
+
|| evidence.schema !== PROJECT_DNA_HOST_EVIDENCE_SCHEMA_VERSION
|
|
122
|
+
|| !GIT_OBJECT.test(evidence.repository_revision)
|
|
123
|
+
|| !Array.isArray(evidence.items) || evidence.items.length < 1 || evidence.items.length > MAX_ITEMS
|
|
124
|
+
|| !SET_ID.test(evidence.evidence_set_id) || !SHA256.test(evidence.content_hash)) {
|
|
125
|
+
throw new Error("Project DNA host evidence set fields are invalid");
|
|
126
|
+
}
|
|
127
|
+
const refs = new Set();
|
|
128
|
+
const ids = new Set();
|
|
129
|
+
let previous = "";
|
|
130
|
+
for (const item of evidence.items) {
|
|
131
|
+
if (!item || typeof item !== "object" || Array.isArray(item)
|
|
132
|
+
|| !exactFields(item, [
|
|
133
|
+
"kind", "ref", "disposition", "author_role", "title", "body", "item_id", "content_hash",
|
|
134
|
+
])
|
|
135
|
+
|| !PROJECT_DNA_HOST_EVIDENCE_KINDS.includes(item.kind)
|
|
136
|
+
|| !SOURCE_REF.test(item.ref)
|
|
137
|
+
|| !PROJECT_DNA_HOST_EVIDENCE_DISPOSITIONS.includes(item.disposition)
|
|
138
|
+
|| !PROJECT_DNA_HOST_EVIDENCE_AUTHOR_ROLES.includes(item.author_role)
|
|
139
|
+
|| !dispositionMatchesKind(item.kind, item.disposition)
|
|
140
|
+
|| !ITEM_ID.test(item.item_id) || !SHA256.test(item.content_hash)
|
|
141
|
+
|| refs.has(item.ref) || ids.has(item.item_id) || (previous && compareCodeUnits(previous, item.item_id) >= 0)) {
|
|
142
|
+
throw new Error("Project DNA host evidence item fields are invalid");
|
|
143
|
+
}
|
|
144
|
+
const unsigned = {
|
|
145
|
+
kind: item.kind,
|
|
146
|
+
ref: item.ref,
|
|
147
|
+
disposition: item.disposition,
|
|
148
|
+
author_role: item.author_role,
|
|
149
|
+
title: boundedText(item.title, MAX_TITLE, "title"),
|
|
150
|
+
body: boundedText(item.body, MAX_BODY, "body"),
|
|
151
|
+
};
|
|
152
|
+
if (item.title !== unsigned.title || item.body !== unsigned.body
|
|
153
|
+
|| (unsigned.title === null && unsigned.body === null)
|
|
154
|
+
|| (unsigned.kind === "pull_request" && unsigned.title === null)
|
|
155
|
+
|| (unsigned.kind === "review_comment" && (unsigned.title !== null || unsigned.body === null))) {
|
|
156
|
+
throw new Error("Project DNA host evidence item content is invalid");
|
|
157
|
+
}
|
|
158
|
+
const expectedId = `pdnahi_${sha256(canonical(unsigned)).slice("sha256:".length, "sha256:".length + 20)}`;
|
|
159
|
+
const sealed = { ...unsigned, item_id: item.item_id };
|
|
160
|
+
if (item.item_id !== expectedId || item.content_hash !== sha256(canonical(sealed))) {
|
|
161
|
+
throw new Error("Project DNA host evidence item seal is invalid");
|
|
162
|
+
}
|
|
163
|
+
refs.add(item.ref);
|
|
164
|
+
ids.add(item.item_id);
|
|
165
|
+
previous = item.item_id;
|
|
166
|
+
}
|
|
167
|
+
const { evidence_set_id: _setId, content_hash: _hash, ...base } = evidence;
|
|
168
|
+
const expectedSetId = `pdnah_${sha256(canonical(base)).slice("sha256:".length, "sha256:".length + 24)}`;
|
|
169
|
+
const sealed = { ...base, evidence_set_id: evidence.evidence_set_id };
|
|
170
|
+
if (evidence.evidence_set_id !== expectedSetId || evidence.content_hash !== sha256(canonical(sealed))) {
|
|
171
|
+
throw new Error("Project DNA host evidence set seal is invalid");
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
//# sourceMappingURL=projectDnaHostEvidence.js.map
|
|
@@ -101,14 +101,25 @@ Reconcile decision-grounding drift for **$ARGUMENTS** (or the whole repo).
|
|
|
101
101
|
3. Only if I explicitly say "the DECISION is stale, not the doc" (Heal B): run /capture to record a superseding decision, then return to step 2 — the prose re-derives from the new decision as a separate confirm.
|
|
102
102
|
4. Report: healed (Heal A), superseded (Heal B), skipped. Never touch the graph except via an explicit Heal B capture.
|
|
103
103
|
`;
|
|
104
|
-
/** A settings.json hook entry is Hunch's if any of its commands
|
|
105
|
-
*
|
|
106
|
-
*
|
|
107
|
-
*
|
|
108
|
-
*
|
|
109
|
-
*
|
|
104
|
+
/** A settings.json hook entry is Hunch's if any of its commands is either the
|
|
105
|
+
* native/source CLI entry (`…/index.js hook`) or the exact published-package
|
|
106
|
+
* launcher written by older Hunch versions (`npx --package=…@davesheffer/hunch…
|
|
107
|
+
* hunch hook`). Matching both generations makes an upgrade idempotent instead
|
|
108
|
+
* of leaving the portable old hook alongside the new native invocation. The
|
|
109
|
+
* source form still requires `index` to be a full path segment, and the npx form
|
|
110
|
+
* requires both the scoped package and the `hunch hook` tail, so foreign hooks
|
|
111
|
+
* are preserved. */
|
|
110
112
|
function isHunchHook(entry) {
|
|
111
|
-
return !!entry.hooks?.some((h) =>
|
|
113
|
+
return !!entry.hooks?.some((h) => {
|
|
114
|
+
if (typeof h.command !== "string")
|
|
115
|
+
return false;
|
|
116
|
+
const command = h.command;
|
|
117
|
+
const nativeOrSource = /[\\/]index\.(js|ts)"?\s+hook\s*$/.test(command);
|
|
118
|
+
const publishedNpx = /^\s*"?npx(?:\.cmd)?"?\s+/i.test(command)
|
|
119
|
+
&& /--package=(?:hunch-exact@npm:)?@davesheffer\/hunch(?:@[^"\s]+)?/.test(command)
|
|
120
|
+
&& /\s"?hunch"?\s+"?hook"?\s*$/.test(command);
|
|
121
|
+
return nativeOrSource || publishedNpx;
|
|
122
|
+
});
|
|
112
123
|
}
|
|
113
124
|
/**
|
|
114
125
|
* Install the Claude Code AGENT hooks into `.claude/settings.json` so the agent
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Stable public programmatic surface for the Project DNA Engine.
|
|
3
|
+
*
|
|
4
|
+
* Keep transport/orchestration consumers on this barrel so internal core file
|
|
5
|
+
* layout can evolve without changing the published contract entry point.
|
|
6
|
+
*/
|
|
7
|
+
export { PROJECT_DNA_CATEGORIES, PROJECT_DNA_MATCH_SCHEMA_VERSION, PROJECT_DNA_SCHEMA_VERSION, assertProjectDnaMatch, assertProjectDnaProfile, discoverProjectDna, evaluateProjectDnaMatch, type ProjectDnaArtifact, type ProjectDnaCategory, type ProjectDnaDiscoveryOptions, type ProjectDnaEvidence, type ProjectDnaEvidenceKind, type ProjectDnaMatch, type ProjectDnaMatchCheck, type ProjectDnaProfile, type ProjectDnaTrait, } from "./core/projectDna.js";
|
|
8
|
+
export { PROJECT_DNA_HOST_EVIDENCE_AUTHOR_ROLES, PROJECT_DNA_HOST_EVIDENCE_DISPOSITIONS, PROJECT_DNA_HOST_EVIDENCE_KINDS, PROJECT_DNA_HOST_EVIDENCE_SCHEMA_VERSION, assertProjectDnaHostEvidence, sealProjectDnaHostEvidence, type ProjectDnaHostEvidence, type ProjectDnaHostEvidenceAuthorRole, type ProjectDnaHostEvidenceCandidate, type ProjectDnaHostEvidenceDisposition, type ProjectDnaHostEvidenceItem, type ProjectDnaHostEvidenceKind, } from "./core/projectDnaHostEvidence.js";
|
|
9
|
+
export { PROJECT_DNA_DELTA_SCHEMA_VERSION, assertProjectDnaDelta, diffProjectDna, type ProjectDnaChangeKind, type ProjectDnaDelta, type ProjectDnaTraitChange, } from "./core/projectDnaDelta.js";
|
|
10
|
+
export { PROJECT_DNA_SUPPLEMENT_KIND, projectDnaDeliverySupplement, type ProjectDnaDeliverySupplement, } from "./core/projectDnaDelivery.js";
|
package/dist/projectDna.js
CHANGED
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
* layout can evolve without changing the published contract entry point.
|
|
6
6
|
*/
|
|
7
7
|
export { PROJECT_DNA_CATEGORIES, PROJECT_DNA_MATCH_SCHEMA_VERSION, PROJECT_DNA_SCHEMA_VERSION, assertProjectDnaMatch, assertProjectDnaProfile, discoverProjectDna, evaluateProjectDnaMatch, } from "./core/projectDna.js";
|
|
8
|
+
export { PROJECT_DNA_HOST_EVIDENCE_AUTHOR_ROLES, PROJECT_DNA_HOST_EVIDENCE_DISPOSITIONS, PROJECT_DNA_HOST_EVIDENCE_KINDS, PROJECT_DNA_HOST_EVIDENCE_SCHEMA_VERSION, assertProjectDnaHostEvidence, sealProjectDnaHostEvidence, } from "./core/projectDnaHostEvidence.js";
|
|
8
9
|
export { PROJECT_DNA_DELTA_SCHEMA_VERSION, assertProjectDnaDelta, diffProjectDna, } from "./core/projectDnaDelta.js";
|
|
9
10
|
export { PROJECT_DNA_SUPPLEMENT_KIND, projectDnaDeliverySupplement, } from "./core/projectDnaDelivery.js";
|
|
10
11
|
//# sourceMappingURL=projectDna.js.map
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@davesheffer/hunch",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.22.0",
|
|
4
4
|
"mcpName": "io.github.davesheffer/hunch",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"author": "Dave Sheffer <dave.sheffer1@gmail.com>",
|
|
@@ -27,6 +27,8 @@
|
|
|
27
27
|
},
|
|
28
28
|
"files": [
|
|
29
29
|
"dist/**/*.js",
|
|
30
|
+
"dist/projectDna.d.ts",
|
|
31
|
+
"dist/core/projectDna*.d.ts",
|
|
30
32
|
"server.json",
|
|
31
33
|
"bench/constitution-exp03-v1.json",
|
|
32
34
|
"tooling/competitive-watch.mjs",
|
package/server.json
CHANGED
|
@@ -7,13 +7,13 @@
|
|
|
7
7
|
"source": "github"
|
|
8
8
|
},
|
|
9
9
|
"websiteUrl": "https://hunch-pi.vercel.app",
|
|
10
|
-
"version": "1.
|
|
10
|
+
"version": "1.22.0",
|
|
11
11
|
"packages": [
|
|
12
12
|
{
|
|
13
13
|
"registryType": "npm",
|
|
14
14
|
"registryBaseUrl": "https://registry.npmjs.org",
|
|
15
15
|
"identifier": "@davesheffer/hunch",
|
|
16
|
-
"version": "1.
|
|
16
|
+
"version": "1.22.0",
|
|
17
17
|
"runtimeHint": "npx",
|
|
18
18
|
"packageArguments": [
|
|
19
19
|
{
|