@davesheffer/hunch 1.7.0 → 1.7.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.
Files changed (63) hide show
  1. package/README.md +214 -0
  2. package/bench/constitution-exp03-v1.json +70 -0
  3. package/dist/cli/index.js +1203 -24
  4. package/dist/constitution/adapters.js +487 -0
  5. package/dist/constitution/behaviorAttestationBinding.js +17 -0
  6. package/dist/constitution/behaviorEvaluator.js +220 -0
  7. package/dist/constitution/behaviorProof.js +205 -0
  8. package/dist/constitution/behaviorWorkspace.js +124 -0
  9. package/dist/constitution/bootstrap.js +133 -0
  10. package/dist/constitution/canonical.js +51 -0
  11. package/dist/constitution/card.js +133 -0
  12. package/dist/constitution/compiler.js +176 -0
  13. package/dist/constitution/composition.js +101 -0
  14. package/dist/constitution/corpus.js +58 -0
  15. package/dist/constitution/delta.js +154 -0
  16. package/dist/constitution/disposition.js +141 -0
  17. package/dist/constitution/evaluator.js +435 -0
  18. package/dist/constitution/experiment.js +948 -0
  19. package/dist/constitution/experimentRunner.js +344 -0
  20. package/dist/constitution/g2.js +291 -0
  21. package/dist/constitution/g2BehaviorAttestation.js +209 -0
  22. package/dist/constitution/g2BehaviorCandidates.js +703 -0
  23. package/dist/constitution/g2BehaviorDependencies.js +379 -0
  24. package/dist/constitution/g2BehaviorMaterialization.js +171 -0
  25. package/dist/constitution/g2BehaviorPolicyMaterializer.js +241 -0
  26. package/dist/constitution/g2CandidateAttestation.js +179 -0
  27. package/dist/constitution/g2Candidates.js +195 -0
  28. package/dist/constitution/g2Drills.js +122 -0
  29. package/dist/constitution/g3.js +511 -0
  30. package/dist/constitution/g3Conformance.js +115 -0
  31. package/dist/constitution/lifecycle.js +189 -0
  32. package/dist/constitution/mutation.js +262 -0
  33. package/dist/constitution/nodeTestEvidence.js +47 -0
  34. package/dist/constitution/plan.js +172 -0
  35. package/dist/constitution/policyRuntime.js +8 -0
  36. package/dist/constitution/proof.js +166 -0
  37. package/dist/constitution/replay.js +361 -0
  38. package/dist/constitution/replayCache.js +89 -0
  39. package/dist/constitution/replayWorker.js +34 -0
  40. package/dist/constitution/repository.js +533 -0
  41. package/dist/constitution/schema.js +545 -0
  42. package/dist/constitution/scorecard.js +106 -0
  43. package/dist/constitution/service.js +1149 -0
  44. package/dist/constitution/shadow.js +235 -0
  45. package/dist/constitution/sourceMutation.js +316 -0
  46. package/dist/constitution/structural.js +601 -0
  47. package/dist/core/autoreview.js +27 -3
  48. package/dist/core/dupdetect.js +10 -3
  49. package/dist/core/events.js +61 -0
  50. package/dist/core/externalImports.js +24 -0
  51. package/dist/core/hookpolicy.js +3 -0
  52. package/dist/core/relativeImports.js +33 -0
  53. package/dist/core/stats.js +115 -0
  54. package/dist/extractors/git.js +81 -0
  55. package/dist/extractors/indexer.js +39 -38
  56. package/dist/extractors/nativeTreeSitter.js +108 -0
  57. package/dist/extractors/parse.js +5 -15
  58. package/dist/integrations/claudemd.js +8 -1
  59. package/dist/integrations/gitignore.js +8 -0
  60. package/dist/integrations/providers.js +32 -10
  61. package/dist/integrations/sync.js +16 -1
  62. package/dist/mcp/server.js +284 -0
  63. package/package.json +5 -1
@@ -0,0 +1,209 @@
1
+ import { existsSync, mkdirSync, readFileSync, readdirSync } from "node:fs";
2
+ import { join } from "node:path";
3
+ import { z } from "zod";
4
+ import { shortHash } from "../core/ids.js";
5
+ import { writeFileAtomic } from "../core/io.js";
6
+ import { canonicalHash } from "./canonical.js";
7
+ import { g2BehaviorCandidateHash, g2BehaviorReplayContentHash, g2BehaviorReviewContentHash, } from "./g2BehaviorCandidates.js";
8
+ const HASH = /^sha1:[a-f0-9]{40}$/;
9
+ const HUMAN_ACTOR = /^(human|github|git):[^\s]+$/i;
10
+ const encode = (value) => `${JSON.stringify(value, null, 2)}\n`;
11
+ export const G2BehaviorDispositionSchema = z.enum(["selected", "rejected"]);
12
+ export const G2BehaviorAttestationSchema = z.object({
13
+ id: z.string().regex(/^g2behaviorattest_[a-f0-9]{10}$/),
14
+ content_hash: z.string().regex(HASH),
15
+ candidate_id: z.string().regex(/^g2behavior_[a-f0-9]{10}$/),
16
+ candidate_hash: z.string().regex(HASH),
17
+ commit: z.string().regex(/^[a-f0-9]{40}$/),
18
+ review_hash: z.string().regex(HASH),
19
+ replay_id: z.string().regex(/^g2behaviorreplay_[a-f0-9]{10}$/),
20
+ replay_hash: z.string().regex(HASH),
21
+ dependency_snapshot_ids: z.array(z.string().regex(/^g2deps_[a-f0-9]{10}$/)).min(1).max(2),
22
+ disposition: G2BehaviorDispositionSchema,
23
+ actor: z.string().regex(HUMAN_ACTOR, "behavior attestation requires an explicit human actor (human:, github:, or git:)"),
24
+ reason: z.string().trim().min(1).max(4000),
25
+ supersedes: z.string().regex(/^g2behaviorattest_[a-f0-9]{10}$/).nullable(),
26
+ data_class: z.literal("private"),
27
+ authority: z.literal("none"),
28
+ effects: z.literal("review_only"),
29
+ created_at: z.string().datetime({ offset: true }),
30
+ }).strict();
31
+ export function g2BehaviorAttestationContentHash(attestation) {
32
+ const { id: _id, content_hash: _contentHash, ...body } = attestation;
33
+ return canonicalHash(body);
34
+ }
35
+ export function compileG2BehaviorAttestation(report, candidateId, reviewHash, replay, disposition, actor, reason, opts = {}) {
36
+ if (report.content_hash !== g2BehaviorReviewContentHash(report)
37
+ || report.id !== `g2behaviorcandidates_${shortHash(report.content_hash)}`) {
38
+ throw new Error(`G2 behavior candidate review ${report.id} content hash mismatch`);
39
+ }
40
+ if (reviewHash !== report.content_hash)
41
+ throw new Error("behavior candidate review hash does not match the exact current review packet");
42
+ const candidate = report.items.find((item) => item.id === candidateId);
43
+ if (!candidate)
44
+ throw new Error(`behavior candidate ${candidateId} is not present in review ${report.id}`);
45
+ if (replay.content_hash !== g2BehaviorReplayContentHash(replay)
46
+ || replay.id !== `g2behaviorreplay_${shortHash(replay.content_hash)}`) {
47
+ throw new Error(`G2 behavior replay ${replay.id} content hash mismatch`);
48
+ }
49
+ const candidateHash = g2BehaviorCandidateHash(candidate);
50
+ if (replay.candidate_id !== candidate.id
51
+ || replay.candidate_hash !== candidateHash
52
+ || replay.review_hash !== report.content_hash) {
53
+ throw new Error(`G2 behavior replay ${replay.id} does not bind the exact reviewed candidate`);
54
+ }
55
+ const parsedDisposition = G2BehaviorDispositionSchema.parse(disposition);
56
+ const dependencySnapshotIds = [...new Set([
57
+ replay.known_bad.dependency_snapshot_id,
58
+ replay.known_good.dependency_snapshot_id,
59
+ ].filter((id) => !!id))].sort();
60
+ if (parsedDisposition === "selected"
61
+ && (replay.verdict !== "behavior_confirmed"
62
+ || replay.known_bad.result !== "failed"
63
+ || replay.known_good.result !== "passed"
64
+ || !replay.known_bad.dependency_snapshot_id
65
+ || !replay.known_good.dependency_snapshot_id)) {
66
+ throw new Error("selecting a behavior candidate requires an exact snapshot-backed behavior_confirmed replay");
67
+ }
68
+ if (!dependencySnapshotIds.length)
69
+ throw new Error("behavior attestation requires at least one exact dependency snapshot");
70
+ const body = {
71
+ candidate_id: candidate.id,
72
+ candidate_hash: candidateHash,
73
+ commit: candidate.commit,
74
+ review_hash: report.content_hash,
75
+ replay_id: replay.id,
76
+ replay_hash: replay.content_hash,
77
+ dependency_snapshot_ids: dependencySnapshotIds,
78
+ disposition: parsedDisposition,
79
+ actor,
80
+ reason: reason.trim(),
81
+ supersedes: opts.supersedes ?? null,
82
+ data_class: "private",
83
+ authority: "none",
84
+ effects: "review_only",
85
+ created_at: opts.now ?? new Date().toISOString(),
86
+ };
87
+ const contentHash = canonicalHash(body);
88
+ return G2BehaviorAttestationSchema.parse({
89
+ id: `g2behaviorattest_${shortHash(contentHash)}`,
90
+ content_hash: contentHash,
91
+ ...body,
92
+ });
93
+ }
94
+ /** Resolve exact-behavior supersession chains without timestamp trust. */
95
+ export function currentG2BehaviorAttestations(records) {
96
+ const parsed = records.map((record) => G2BehaviorAttestationSchema.parse(record));
97
+ const byId = new Map(parsed.map((record) => [record.id, record]));
98
+ if (byId.size !== parsed.length)
99
+ throw new Error("duplicate G2 behavior attestation id");
100
+ const identity = (record) => `${record.candidate_id}:${record.candidate_hash}`;
101
+ const childCount = new Map();
102
+ for (const record of parsed) {
103
+ if (!record.supersedes)
104
+ continue;
105
+ const parent = byId.get(record.supersedes);
106
+ if (!parent)
107
+ throw new Error(`G2 behavior attestation ${record.id} supersedes missing ${record.supersedes}`);
108
+ if (identity(parent) !== identity(record) || parent.commit !== record.commit) {
109
+ throw new Error(`G2 behavior attestation ${record.id} supersedes a different exact candidate`);
110
+ }
111
+ childCount.set(parent.id, (childCount.get(parent.id) ?? 0) + 1);
112
+ if (childCount.get(parent.id) > 1)
113
+ throw new Error(`G2 behavior attestation ${parent.id} has a branched supersession chain`);
114
+ }
115
+ for (const record of parsed) {
116
+ const visited = new Set();
117
+ let cursor = record;
118
+ while (cursor?.supersedes) {
119
+ if (visited.has(cursor.id))
120
+ throw new Error(`G2 behavior attestation chain contains a cycle at ${cursor.id}`);
121
+ visited.add(cursor.id);
122
+ cursor = byId.get(cursor.supersedes);
123
+ }
124
+ }
125
+ const current = parsed.filter((record) => !childCount.has(record.id));
126
+ const targets = new Set();
127
+ for (const record of current) {
128
+ const key = identity(record);
129
+ if (targets.has(key))
130
+ throw new Error(`G2 behavior candidate ${key} has multiple current attestations`);
131
+ targets.add(key);
132
+ }
133
+ return current.sort((left, right) => left.candidate_id.localeCompare(right.candidate_id) || left.id.localeCompare(right.id));
134
+ }
135
+ export class G2BehaviorAttestationRepository {
136
+ store;
137
+ constructor(store) {
138
+ this.store = store;
139
+ }
140
+ list() {
141
+ const dir = this.store.privateDir ? join(this.store.privateDir, "behavior-attestations") : undefined;
142
+ if (!dir || !existsSync(dir))
143
+ return [];
144
+ const records = readdirSync(dir)
145
+ .filter((name) => name.endsWith(".json"))
146
+ .sort()
147
+ .map((name) => {
148
+ try {
149
+ if (!/^g2behaviorattest_[a-f0-9]{10}\.json$/.test(name))
150
+ throw new Error("unexpected attestation filename");
151
+ const parsed = G2BehaviorAttestationSchema.parse(JSON.parse(readFileSync(join(dir, name), "utf8")));
152
+ if (parsed.content_hash !== g2BehaviorAttestationContentHash(parsed)
153
+ || parsed.id !== `g2behaviorattest_${shortHash(parsed.content_hash)}`) {
154
+ throw new Error(`G2 behavior attestation ${parsed.id} content hash mismatch`);
155
+ }
156
+ if (name !== `${parsed.id}.json`)
157
+ throw new Error(`filename does not match attestation ${parsed.id}`);
158
+ return parsed;
159
+ }
160
+ catch (error) {
161
+ throw new Error(`invalid behavior-attestations/${name}: ${error.message}`);
162
+ }
163
+ });
164
+ currentG2BehaviorAttestations(records);
165
+ return records;
166
+ }
167
+ current() {
168
+ return currentG2BehaviorAttestations(this.list());
169
+ }
170
+ resolutions() {
171
+ return this.current().map((record) => ({
172
+ id: record.id,
173
+ candidate_id: record.candidate_id,
174
+ candidate_hash: record.candidate_hash,
175
+ review_hash: record.review_hash,
176
+ replay_id: record.replay_id,
177
+ replay_hash: record.replay_hash,
178
+ disposition: record.disposition,
179
+ actor: record.actor,
180
+ reason: record.reason,
181
+ created_at: record.created_at,
182
+ }));
183
+ }
184
+ put(attestation) {
185
+ if (!this.store.privateDir)
186
+ throw new Error("No private Hunch overlay is configured; refusing to write G2 behavior attestation.");
187
+ const parsed = G2BehaviorAttestationSchema.parse(attestation);
188
+ if (parsed.content_hash !== g2BehaviorAttestationContentHash(parsed)
189
+ || parsed.id !== `g2behaviorattest_${shortHash(parsed.content_hash)}`) {
190
+ throw new Error(`G2 behavior attestation ${parsed.id} content hash mismatch`);
191
+ }
192
+ const records = this.list();
193
+ const existing = records.find((record) => record.id === parsed.id);
194
+ if (existing)
195
+ return existing;
196
+ const current = currentG2BehaviorAttestations(records).find((record) => (record.candidate_id === parsed.candidate_id && record.candidate_hash === parsed.candidate_hash));
197
+ if (current && parsed.supersedes !== current.id) {
198
+ throw new Error(`G2 behavior attestation ${current.id} is current; pass supersedes:${current.id} to append a correction`);
199
+ }
200
+ if (!current && parsed.supersedes)
201
+ throw new Error(`G2 behavior attestation ${parsed.id} supersedes no current exact candidate review`);
202
+ currentG2BehaviorAttestations([...records, parsed]);
203
+ const dir = join(this.store.privateDir, "behavior-attestations");
204
+ mkdirSync(dir, { recursive: true });
205
+ writeFileAtomic(join(dir, `${parsed.id}.json`), encode(parsed));
206
+ return parsed;
207
+ }
208
+ }
209
+ //# sourceMappingURL=g2BehaviorAttestation.js.map