@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
@@ -1,15 +1,5 @@
1
- /**
2
- * Deterministic tree-sitter parsing (no LLM). Extracts, per file:
3
- * - symbols: functions, methods, classes, interfaces, types, arrow-fn consts
4
- * - imports: module specifiers (for dependency edges)
5
- * - calls: callee names + byte offset (mapped to the enclosing symbol)
6
- *
7
- * Uses NATIVE tree-sitter (synchronous, prebuilt for Node 20 — see decision in
8
- * the commit history; web-tree-sitter's WASM grammars had an incompatible ABI).
9
- */
10
- import Parser from "tree-sitter";
11
- import TS from "tree-sitter-typescript";
12
- const { typescript, tsx } = TS;
1
+ import { loadNativeTreeSitter } from "./nativeTreeSitter.js";
2
+ const { Parser, typescript, tsx } = loadNativeTreeSitter();
13
3
  /** Extremely common builtin/array/object/string/promise method names. Member
14
4
  * calls to these (e.g. `arr.map(...)`) must NOT create call edges to unrelated
15
5
  * repo symbols that happen to share the name (DESIGN: keep the graph clean). */
@@ -112,12 +102,12 @@ export function parseSource(file, source) {
112
102
  imports.push(node.text.replace(STR_QUOTES, ""));
113
103
  }
114
104
  else if (cname === "call.id") {
115
- calls.push({ callee: node.text, atByte: node.startIndex, member: false });
105
+ calls.push({ callee: node.text, atByte: node.startIndex, endByte: node.endIndex, member: false });
116
106
  }
117
107
  else if (cname === "call.member") {
118
108
  // skip builtin method names to avoid false edges to similarly-named symbols
119
109
  if (!BUILTIN_METHODS.has(node.text))
120
- calls.push({ callee: node.text, atByte: node.startIndex, member: true });
110
+ calls.push({ callee: node.text, atByte: node.startIndex, endByte: node.endIndex, member: true });
121
111
  }
122
112
  }
123
113
  for (const { kind, def, name } of pendingDefs.values()) {
@@ -131,7 +121,7 @@ export function parseSource(file, source) {
131
121
  });
132
122
  }
133
123
  symbols.sort((a, b) => a.startByte - b.startByte);
134
- return { symbols, imports, calls };
124
+ return { symbols, imports, calls, parseable: !tree.rootNode.hasError };
135
125
  }
136
126
  /** Walk up to the nearest node whose type is a definition we recognize. */
137
127
  function ascendToDef(node) {
@@ -6,6 +6,7 @@
6
6
  import { readFileSync, writeFileSync, existsSync, mkdirSync } from "node:fs";
7
7
  import { join, dirname } from "node:path";
8
8
  import { wikiSummary } from "../wiki/wiki.js";
9
+ import { PolicyRepository } from "../constitution/repository.js";
9
10
  const START = "<!-- HUNCH:START — auto-generated, do not edit by hand -->";
10
11
  const END = "<!-- HUNCH:END -->";
11
12
  export function renderHunchSection(store, root) {
@@ -18,6 +19,7 @@ export function renderHunchSection(store, root) {
18
19
  bugs: store.json.loadAll("bugs").length,
19
20
  constraints: store.json.loadAll("constraints").length,
20
21
  components: store.json.loadAll("components").length,
22
+ policies: root ? new PolicyRepository(root, store).listPolicies({ publicOnly: true }).length : 0,
21
23
  };
22
24
  const lines = [];
23
25
  lines.push(START);
@@ -25,7 +27,7 @@ export function renderHunchSection(store, root) {
25
27
  lines.push("");
26
28
  lines.push("This repo has **Hunch** — a curated graph of *why* the code is the way it is " +
27
29
  "(decisions, bug history, invariants). It currently holds " +
28
- `**${counts.decisions} decisions, ${counts.bugs} bugs, ${counts.constraints} constraints, ${counts.components} components**.`);
30
+ `**${counts.decisions} decisions, ${counts.bugs} bugs, ${counts.constraints} constraints, ${counts.components} components, ${counts.policies} policies**.`);
29
31
  lines.push("");
30
32
  lines.push("**Consult Hunch via the `hunch_*` MCP tools — pick by MOMENT, not from memory:**");
31
33
  lines.push("");
@@ -47,8 +49,13 @@ export function renderHunchSection(store, root) {
47
49
  lines.push("");
48
50
  lines.push("**Before committing / merging:**");
49
51
  lines.push("- `hunch_conformance()` — does the code still SATISFY recorded intent? Run before and after a refactor.");
52
+ lines.push("- `hunch_policy_evaluate(policy_id?, active_only?)` / `hunch_policy_plan(policy_id)` / `hunch_policy_card(policy_id)` / `hunch_policy_proof(policy_id)` — evaluate canonical policy, inspect the planned corpus, review the evidence/uncertainty card, and inspect raw replay receipts; only an explicit human activation grants authority.");
50
53
  lines.push("- `hunch_pr_impact(base?)` / `hunch_merge_verdict(...)` — a change's memory surface; would it re-open a closed bug?");
51
54
  lines.push("");
55
+ lines.push("**Build the Constitution review queue:**");
56
+ lines.push("- `hunch constitution bootstrap --since 90d --max-candidates 3` (CLI) — normalize recent structured human evidence into at most three non-active policy candidates; add `--history` for exact, human-identifier-grounded fix/revert deltas or explicit dependency retirements. Coincidence/ambiguity stays uncompilable; neither path grants authority.");
57
+ lines.push("- `hunch constitution ingest --since 90d [--instructions] [--from export.json]` (CLI) — normalize corrections/failures plus bounded committed instructions/ADRs and strict local review/conversation/PR exports into Git-native evidence; raw prose is hash-only, unsupported intent remains uncompilable, and no policy is minted.");
58
+ lines.push("");
52
59
  lines.push("**After deciding / when corrected:**");
53
60
  lines.push("- `hunch_capture_decision(topic?)` → `hunch_record_decision(...)` — interview first, then write; status `proposed` = roadmap intent (shows in `hunch now`).");
54
61
  lines.push("- `hunch_record_correction(...)` — a human correction becomes an ENFORCED rule (Never Twice), not a one-session memory.");
@@ -19,6 +19,7 @@ const ENTRIES = [
19
19
  ".hunch/*.sqlite-wal",
20
20
  ".hunch/*.sqlite-journal",
21
21
  ".hunch/**/*.tmp*",
22
+ ".hunch-cache/",
22
23
  // Per-machine private-overlay pointer written by `hunch private` (holds the local
23
24
  // path to the private store) — never committed.
24
25
  ".hunch/local.json",
@@ -39,6 +40,13 @@ const MEM_ENTRIES = [
39
40
  ".hunch/bugs/",
40
41
  ".hunch/constraints/",
41
42
  ".hunch/components/",
43
+ ".hunch/evidence/",
44
+ ".hunch/corpora/",
45
+ ".hunch/policies/",
46
+ ".hunch/proofs/",
47
+ ".hunch/plans/",
48
+ ".hunch/dispositions/",
49
+ ".hunch/shadow/",
42
50
  ".hunch/symbols/",
43
51
  ".hunch/edges/",
44
52
  ];
@@ -21,6 +21,7 @@ import { readFileSync, writeFileSync, existsSync, mkdirSync } from "node:fs";
21
21
  import { homedir } from "node:os";
22
22
  import { join, dirname } from "node:path";
23
23
  import { renderHunchSection, upsertSection, updateClaudeMd } from "./claudemd.js";
24
+ import { isGitCleanPath } from "../extractors/git.js";
24
25
  /** Strip // line and block comments + trailing commas (JSONC → JSON). String-aware
25
26
  * (double-quoted, with escapes) so a // inside a value isn't mangled. VS Code's
26
27
  * .vscode/mcp.json is JSONC, so we must tolerate comments. */
@@ -405,23 +406,24 @@ export function regenerateGrounding(root, store) {
405
406
  writeWindsurfRule(root, store),
406
407
  ];
407
408
  }
408
- /** Self-heal: refresh the Hunch section in each grounding doc that ALREADY exists,
409
- * and report which ones actually changed. Unlike regenerateGrounding it NEVER creates
410
- * a file (so it can't scaffold grounding into a project that opted out of an
411
- * assistant). Run by `hunch index` and non-hook `hunch sync` so a project silently
412
- * picks up generator fixes (e.g. corrected MCP tool param names) and fresh record
413
- * counts on the next refresh — no manual `hunch init`. Not run from the commit hook,
414
- * which deliberately avoids dirtying the working tree on every commit. */
415
- export function refreshExistingGrounding(root, store) {
416
- const targets = [
409
+ function groundingTargets(root, store) {
410
+ return [
417
411
  ["CLAUDE.md", () => updateClaudeMd(root, store)],
418
412
  ["AGENTS.md", () => writeAgentsMd(root, store)],
419
413
  [join(".github", "copilot-instructions.md"), () => writeCopilotInstructions(root, store)],
420
414
  [join(".cursor", "rules", "hunch.mdc"), () => writeCursorRule(root, store)],
421
415
  [join(".windsurf", "rules", "hunch.md"), () => writeWindsurfRule(root, store)],
422
416
  ];
417
+ }
418
+ /** Self-heal: refresh the Hunch section in each grounding doc that ALREADY exists,
419
+ * and report which ones actually changed. Unlike regenerateGrounding it NEVER creates
420
+ * a file (so it can't scaffold grounding into a project that opted out of an
421
+ * assistant). Run by `hunch index` and non-hook `hunch sync` so a project silently
422
+ * picks up generator fixes (e.g. corrected MCP tool param names) and fresh record
423
+ * counts on the next refresh — no manual `hunch init`. */
424
+ export function refreshExistingGrounding(root, store) {
423
425
  const changed = [];
424
- for (const [rel, write] of targets) {
426
+ for (const [rel, write] of groundingTargets(root, store)) {
425
427
  const file = join(root, rel);
426
428
  if (!existsSync(file))
427
429
  continue; // refresh-only: never scaffold a doc the project doesn't have
@@ -432,6 +434,26 @@ export function refreshExistingGrounding(root, store) {
432
434
  }
433
435
  return changed;
434
436
  }
437
+ /** Capture-commit refresh: rewrite ONLY grounding docs that are git-clean, and return the
438
+ * absolute paths of the ones that changed so the caller folds them into the memory commit
439
+ * (commitAndPushHunch alsoStage). This keeps committed record counts permanently true —
440
+ * every capture used to bump the count and re-stale the committed docs, failing the
441
+ * release gate's clean-tree check on the next CI index (the refresh-counts treadmill).
442
+ * A user-dirty or untracked doc is left completely untouched (never refreshed, never
443
+ * staged); it heals on the next manual `hunch sync` or `hunch index`. */
444
+ export function refreshCommittableGrounding(root, store) {
445
+ const changed = [];
446
+ for (const [rel, write] of groundingTargets(root, store)) {
447
+ const file = join(root, rel);
448
+ if (!existsSync(file) || !isGitCleanPath(root, rel))
449
+ continue;
450
+ const before = readFileSync(file, "utf8");
451
+ write();
452
+ if (readFileSync(file, "utf8") !== before)
453
+ changed.push(file);
454
+ }
455
+ return changed;
456
+ }
435
457
  /** A malformed configuration for one surface (for example an MCP file) must not
436
458
  * prevent the same assistant's rule or lifecycle hook from being installed. */
437
459
  function runProvider(writers) {
@@ -1,4 +1,15 @@
1
+ /**
2
+ * The single funnel for auto-committing memory after a capture, so memory never has to be
3
+ * committed by hand — in EVERY mode (auto-commit is ON by default; `--no-auto-commit` opts out).
4
+ * A private record commits + two-way-syncs (merge remote, then push) its dedicated overlay repo.
5
+ * A public record commits the repo-tracked .hunch/ WITHOUT pushing — an automatic pull/push on
6
+ * the user's CODE repo would merge the remote into their working branch and publish unpushed
7
+ * commits (bug_overlay_clobber); the memory commit rides their next push instead.
8
+ * Used by every CLI/MCP path that writes a record.
9
+ */
10
+ import { dirname } from "node:path";
1
11
  import { commitAndPushHunch } from "../extractors/git.js";
12
+ import { refreshCommittableGrounding } from "./providers.js";
2
13
  /** Auto-commit + push the overlay after a private write, when auto-commit is on. No-op
3
14
  * otherwise (manual `hunch private --sync` still works). Never throws. */
4
15
  export function flushPrivate(store, message) {
@@ -21,6 +32,10 @@ export function flushCapture(store, publicHunchDir, isPrivate, message) {
21
32
  }
22
33
  if (!store.autoCommit)
23
34
  return null;
24
- return commitAndPushHunch(publicHunchDir, message, { push: false });
35
+ // A public capture changes record counts, so refresh git-clean grounding docs and fold
36
+ // them into the SAME memory commit — otherwise every capture re-stales the committed
37
+ // counts and the release gate's clean-tree check fails on the next CI index.
38
+ const grounding = refreshCommittableGrounding(dirname(publicHunchDir), store);
39
+ return commitAndPushHunch(publicHunchDir, message, { push: false, alsoStage: grounding });
25
40
  }
26
41
  //# sourceMappingURL=sync.js.map
@@ -22,9 +22,12 @@ import { ensureTeamOverlay } from "../integrations/team.js";
22
22
  import { formatContext, formatStructure } from "../core/format.js";
23
23
  import { compareCandidates } from "../core/compare.js";
24
24
  import { checkConformance } from "../core/conformance.js";
25
+ import { ConstitutionService } from "../constitution/service.js";
26
+ import { G2_RUNBOOK_CATEGORIES } from "../constitution/g2.js";
25
27
  import { renderMarkdown, renderImpact, verdict } from "../core/checkreport.js";
26
28
  import { nowData, wikiStatus, publicHome, readWikiManifestAt } from "../wiki/wiki.js";
27
29
  import { HUNCH_VERSION } from "../core/version.js";
30
+ import { indexRepo } from "../extractors/indexer.js";
28
31
  import { liveForTopic, historyForTopic, rejectedForTopic, captureConflicts } from "../core/topics.js";
29
32
  import { issueCaptureToken as issueToken, consumeCaptureToken as consumeToken } from "../core/capturetoken.js";
30
33
  import { randomUUID } from "node:crypto";
@@ -723,6 +726,191 @@ export function buildServer(root) {
723
726
  return err(`Failed to compare candidates: ${e.message}`);
724
727
  }
725
728
  });
729
+ // -- Hunch Constitution (read-first, agent-neutral Policy IR) ------------
730
+ server.registerTool("hunch_policy_candidates", {
731
+ title: "List Constitution policy candidates",
732
+ description: "List compiled/proposed deterministic Policy IR candidates. Read-only; candidates carry no authority and cannot block. Uses the same Git-native policy store for every MCP client.",
733
+ inputSchema: {
734
+ public_only: z.boolean().optional().describe("Exclude the private overlay from this response."),
735
+ },
736
+ }, async ({ public_only }) => {
737
+ try {
738
+ const service = new ConstitutionService(store, root);
739
+ const candidates = service.list({ publicOnly: public_only }).filter((p) => p.state === "compiled" || p.state === "validating" || p.state === "proposed");
740
+ if (!candidates.length)
741
+ return ok("No Constitution policy candidates.");
742
+ return ok(JSON.stringify(candidates.map((p) => ({ id: p.id, state: p.state, statement: p.statement, proof: p.proof, data_class: p.data_class })), null, 2));
743
+ }
744
+ catch (e) {
745
+ return err(`Failed to list policy candidates: ${e.message}`);
746
+ }
747
+ });
748
+ server.registerTool("hunch_policy_plan", {
749
+ title: "Generate or inspect a Constitution proof plan",
750
+ description: "Return the canonical Git-native ProofPlan for a policy candidate: immutable source/current commits, known-good/bad corpus, mutation operators, expectations, and budgets. Planning executes no replay, model, test, or authority transition.",
751
+ inputSchema: {
752
+ policy_id: z.string().describe("Policy id (pol_*)."),
753
+ public_only: z.boolean().optional().describe("Exclude private-overlay policy and evidence records."),
754
+ },
755
+ }, async ({ policy_id, public_only }) => {
756
+ try {
757
+ return ok(JSON.stringify(new ConstitutionService(store, root).plan(policy_id, { publicOnly: public_only }), null, 2));
758
+ }
759
+ catch (e) {
760
+ return err(`Failed to generate policy proof plan: ${e.message}`);
761
+ }
762
+ });
763
+ server.registerTool("hunch_policy_card", {
764
+ title: "Inspect a Constitution proof card",
765
+ description: "Return the deterministic proof-card view for a policy: exact assertion/scope, raw evidence vector, uncertainty, blocking readiness, authority, limitations, and next actions. Read-only and grants no authority.",
766
+ inputSchema: {
767
+ policy_id: z.string().describe("Policy id (pol_*)."),
768
+ public_only: z.boolean().optional().describe("Exclude private-overlay policy and proof records."),
769
+ },
770
+ }, async ({ policy_id, public_only }) => {
771
+ try {
772
+ return ok(JSON.stringify(new ConstitutionService(store, root).card(policy_id, { publicOnly: public_only }), null, 2));
773
+ }
774
+ catch (e) {
775
+ return err(`Failed to build policy proof card: ${e.message}`);
776
+ }
777
+ });
778
+ server.registerTool("hunch_policy_shadow", {
779
+ title: "Inspect Constitution shadow precision",
780
+ description: "Return the append-only shadow evaluation ledger, current human dispositions, raw precision counts, unknown/error rate, thresholds, and P4-review recommendation for one policy. Read-only: it never records a sample, changes lifecycle, activates, warns, or blocks.",
781
+ inputSchema: {
782
+ policy_id: z.string().describe("Policy id (pol_*)."),
783
+ public_only: z.boolean().optional().describe("Exclude private-overlay shadow records."),
784
+ },
785
+ }, async ({ policy_id, public_only }) => {
786
+ try {
787
+ return ok(JSON.stringify(new ConstitutionService(store, root).shadowReport(policy_id, {}, { publicOnly: public_only }), null, 2));
788
+ }
789
+ catch (e) {
790
+ return err(`Failed to inspect policy shadow precision: ${e.message}`);
791
+ }
792
+ });
793
+ server.registerTool("hunch_policy_proof", {
794
+ title: "Inspect a Constitution policy proof",
795
+ description: "Return the full content-addressed proof artifact for a policy. Read-only; exposes baseline, mutations, proof class, artifact hashes, and limitations without changing authority.",
796
+ inputSchema: {
797
+ policy_id: z.string().describe("Policy id (pol_*)."),
798
+ public_only: z.boolean().optional().describe("Exclude the private overlay from this response."),
799
+ },
800
+ }, async ({ policy_id, public_only }) => {
801
+ try {
802
+ const service = new ConstitutionService(store, root);
803
+ const policy = service.get(policy_id, { publicOnly: public_only });
804
+ if (!policy.proof)
805
+ return ok(`Policy ${policy_id} has no proof yet.`);
806
+ return ok(JSON.stringify(service.proof(policy.proof, { publicOnly: public_only }), null, 2));
807
+ }
808
+ catch (e) {
809
+ return err(`Failed to read policy proof: ${e.message}`);
810
+ }
811
+ });
812
+ server.registerTool("hunch_policy_evaluate", {
813
+ title: "Evaluate Constitution policy",
814
+ description: "Evaluate one or all deterministic Policy IR records and return canonical neutral receipts (satisfied, violated, not_applicable, unknown, error). This is the same evaluator used by CLI and strict CI; models never decide the verdict.",
815
+ inputSchema: {
816
+ policy_id: z.string().optional().describe("Optional policy id; omit for all policies."),
817
+ active_only: z.boolean().optional().describe("Evaluate only active advisory/blocking policies."),
818
+ public_only: z.boolean().optional().describe("Exclude private-overlay policies and graph records."),
819
+ workspace: z.enum(["staged", "working"]).optional().describe("For executable-behavior policies, evaluate the staged index or complete working snapshot in a disposable checkout."),
820
+ commit: z.string().optional().describe("For executable-behavior policies, evaluate an exact commit ref instead of the current committed HEAD."),
821
+ },
822
+ }, async ({ policy_id, active_only, public_only, workspace, commit }) => {
823
+ try {
824
+ if (workspace && commit)
825
+ throw new Error("choose either workspace or commit for executable-behavior evaluation");
826
+ if (commit && !revExists(commit, root))
827
+ throw new Error(`commit ref ${JSON.stringify(commit)} does not resolve`);
828
+ indexRepo(store, root, { churn: false });
829
+ store.reindex();
830
+ const behavior = workspace ? { workspace }
831
+ : commit ? { commit: revParse(commit, root) }
832
+ : undefined;
833
+ const receipts = new ConstitutionService(store, root)
834
+ .evaluate({ id: policy_id, activeOnly: active_only, publicOnly: public_only, behavior })
835
+ .map((r) => r.evaluation);
836
+ return ok(JSON.stringify(receipts, null, 2));
837
+ }
838
+ catch (e) {
839
+ return err(`Failed to evaluate policy: ${e.message}`);
840
+ }
841
+ });
842
+ server.registerTool("hunch_constitution_g2_readiness", {
843
+ title: "Inspect Constitution G2 readiness",
844
+ description: "Return the exact private G2 dogfood evidence packet: human-selected policies, bound proof/corpus/shadow evidence, operational runbook rehearsals, and blockers. Read-only; it never creates evidence, signs off G2, activates policy, warns, or blocks.",
845
+ inputSchema: {},
846
+ }, async () => {
847
+ try {
848
+ return ok(JSON.stringify(new ConstitutionService(store, root).g2Readiness(), null, 2));
849
+ }
850
+ catch (e) {
851
+ return err(`Failed to inspect G2 readiness: ${e.message}`);
852
+ }
853
+ });
854
+ server.registerTool("hunch_constitution_g3_readiness", {
855
+ title: "Inspect Constitution G3 readiness",
856
+ description: "Return the exact private G3 advisory packet: human-selected policies and clients, immutable experiment preregistrations, proof-card comprehension/review measurements, executable adapter conformance, scorecard, and blockers. Read-only; it never records evidence, activates policy, or signs off G3.",
857
+ inputSchema: {},
858
+ }, async () => {
859
+ try {
860
+ return ok(JSON.stringify(new ConstitutionService(store, root).g3Readiness(), null, 2));
861
+ }
862
+ catch (e) {
863
+ return err(`Failed to inspect G3 readiness: ${e.message}`);
864
+ }
865
+ });
866
+ server.registerTool("hunch_constitution_g2_shadow_queue", {
867
+ title: "Review unclassified G2 shadow violations",
868
+ description: "Return a bounded private queue of exact-current-proof G2 shadow violations that still require human classification. Read-only; it never records an observation or disposition, changes lifecycle, grants authority, warns, or blocks.",
869
+ inputSchema: {
870
+ limit: z.number().int().min(1).max(100).optional().describe("Maximum queue items to return (default 20)."),
871
+ },
872
+ }, async ({ limit }) => {
873
+ try {
874
+ return ok(JSON.stringify(new ConstitutionService(store, root).g2ShadowQueue(limit ?? 20), null, 2));
875
+ }
876
+ catch (e) {
877
+ return err(`Failed to inspect the G2 shadow queue: ${e.message}`);
878
+ }
879
+ });
880
+ server.registerTool("hunch_constitution_g2_operational_drill", {
881
+ title: "Execute one exact G2 operational drill",
882
+ description: "Execute the selected private G2 runbook's exact safety regression and return a content-addressed hash-only receipt. Diagnostic only: it writes no rehearsal or shadow evidence, grants no authority, and never signs off G2.",
883
+ inputSchema: {
884
+ category: z.enum(G2_RUNBOOK_CATEGORIES).describe("Exact operational category selected by the current private G2 plan."),
885
+ },
886
+ }, async ({ category }) => {
887
+ try {
888
+ return ok(JSON.stringify(new ConstitutionService(store, root).g2OperationalDrill(category), null, 2));
889
+ }
890
+ catch (e) {
891
+ return err(`Failed to execute the G2 operational drill: ${e.message}`);
892
+ }
893
+ });
894
+ server.registerTool("hunch_constitution_g2_candidates", {
895
+ title: "Review potential G2 dogfood candidates",
896
+ description: "Return a bounded private review packet of exact structural candidates from fix-labeled git history, including the current append-only human selection/rejection when present. Read-only: proposed before/after corpus refs are not replayed evidence, and the tool creates no attestation, policy, proof, corpus, authority, warning, or block.",
897
+ inputSchema: {
898
+ since: z.string().min(1).max(100).optional().describe("Git history window (default 180d)."),
899
+ max_commits: z.number().int().min(1).max(200).optional().describe("Maximum fix-labeled commits to inspect (default 100)."),
900
+ limit: z.number().int().min(1).max(100).optional().describe("Maximum ranked candidates to return (default 30)."),
901
+ },
902
+ }, async ({ since, max_commits, limit }) => {
903
+ try {
904
+ return ok(JSON.stringify(new ConstitutionService(store, root).g2CandidateReview({
905
+ since: since ?? "180d",
906
+ maxCommits: max_commits ?? 100,
907
+ limit: limit ?? 30,
908
+ }), null, 2));
909
+ }
910
+ catch (e) {
911
+ return err(`Failed to inspect G2 candidates: ${e.message}`);
912
+ }
913
+ });
726
914
  // -- hunch_conformance ----------------------------------------------------
727
915
  server.registerTool("hunch_conformance", {
728
916
  title: "Does the code still satisfy the recorded intent?",
@@ -737,6 +925,102 @@ export function buildServer(root) {
737
925
  const head = violations.length ? `⛔ ${violations.length} intent(s) the code no longer satisfies` : "✅ the code satisfies every recorded intent";
738
926
  return ok(`Intent-conformance (${results.length - violations.length}/${results.length} satisfied):\n\n${lines.join("\n")}\n\n${head}`);
739
927
  });
928
+ server.registerTool("hunch_constitution_g2_behavior_candidates", {
929
+ title: "Review executable G2 behavior candidates",
930
+ description: "Derive a bounded private review packet from human-grounded rejected structural proxies and newly added literal node:test cases in their exact fixing commits. Read-only: candidates remain unselected and create no policy, corpus, proof, authority, warning, or block.",
931
+ inputSchema: {
932
+ decision_id: z.string().regex(/^dec_[A-Za-z0-9_-]+$/).optional().describe("Exact current human-confirmed decision to use as the direct behavior grounding batch."),
933
+ since: z.string().min(1).max(100).optional().describe("Git history window (default 180d)."),
934
+ max_commits: z.number().int().min(1).max(200).optional().describe("Maximum fix-labeled commits to inspect (default 100)."),
935
+ limit: z.number().int().min(1).max(100).optional().describe("Maximum behavior candidates to return (default 30)."),
936
+ },
937
+ }, async ({ decision_id, since, max_commits, limit }) => {
938
+ try {
939
+ return ok(JSON.stringify(new ConstitutionService(store, root).g2BehaviorCandidateReview({
940
+ since: since ?? "180d",
941
+ maxCommits: max_commits ?? 100,
942
+ limit: limit ?? 30,
943
+ decisionId: decision_id,
944
+ }), null, 2));
945
+ }
946
+ catch (e) {
947
+ return err(`Failed to inspect G2 behavior candidates: ${e.message}`);
948
+ }
949
+ });
950
+ server.registerTool("hunch_constitution_g2_behavior_replay", {
951
+ title: "Replay one G2 behavior candidate",
952
+ description: "Execute one exact behavior candidate without a shell in disposable known-bad and known-good worktrees, transplanting the hash-bound known-good test file into both. Diagnostic only: writes no Constitution artifact and grants no policy or G2 authority.",
953
+ inputSchema: {
954
+ candidate_id: z.string().regex(/^g2behavior_[a-f0-9]{10}$/),
955
+ review_hash: z.string().regex(/^sha1:[a-f0-9]{40}$/),
956
+ decision_id: z.string().regex(/^dec_[A-Za-z0-9_-]+$/).optional().describe("Exact decision batch used by the reviewed candidate."),
957
+ since: z.string().min(1).max(100).optional().describe("Git history window used by the exact review packet (default 180d)."),
958
+ max_commits: z.number().int().min(1).max(200).optional().describe("Fix-commit bound used by the exact review packet (default 100)."),
959
+ limit: z.number().int().min(1).max(100).optional().describe("Item limit used by the exact review packet (default 30)."),
960
+ timeout_ms: z.number().int().min(1).max(120000).optional().describe("Per-leg execution timeout (default 30000ms)."),
961
+ },
962
+ }, async ({ candidate_id, review_hash, decision_id, since, max_commits, limit, timeout_ms }) => {
963
+ try {
964
+ return ok(JSON.stringify(new ConstitutionService(store, root).g2BehaviorCandidateReplay(candidate_id, review_hash, {
965
+ since: since ?? "180d",
966
+ maxCommits: max_commits ?? 100,
967
+ limit: limit ?? 30,
968
+ decisionId: decision_id,
969
+ timeoutMs: timeout_ms ?? 30_000,
970
+ }), null, 2));
971
+ }
972
+ catch (e) {
973
+ return err(`Failed to replay G2 behavior candidate: ${e.message}`);
974
+ }
975
+ });
976
+ server.registerTool("hunch_constitution_g2_behavior_materialization", {
977
+ title: "Assess selected G2 behavior materialization",
978
+ description: "Bind the complete current private behavior review and exact selected attestations, then report whether their durable meanings are expressible by the supported Policy IR. Read-only and fail-closed: unsupported behavior creates no policy, corpus, plan, proof, authority, warning, or block.",
979
+ inputSchema: {
980
+ decision_id: z.string().regex(/^dec_[A-Za-z0-9_-]+$/).optional().describe("Exact decision batch to assess."),
981
+ since: z.string().min(1).max(100).optional().describe("Git history window used by the exact review packet (default 180d)."),
982
+ max_commits: z.number().int().min(1).max(200).optional().describe("Fix-commit bound used by the exact review packet (default 100)."),
983
+ limit: z.number().int().min(1).max(100).optional().describe("Item limit used by the exact review packet (default 30)."),
984
+ },
985
+ }, async ({ decision_id, since, max_commits, limit }) => {
986
+ try {
987
+ return ok(JSON.stringify(new ConstitutionService(store, root).g2BehaviorMaterializationAssessment({
988
+ since: since ?? "180d",
989
+ maxCommits: max_commits ?? 100,
990
+ limit: limit ?? 30,
991
+ decisionId: decision_id,
992
+ }), null, 2));
993
+ }
994
+ catch (e) {
995
+ return err(`Failed to assess G2 behavior materialization: ${e.message}`);
996
+ }
997
+ });
998
+ server.registerTool("hunch_constitution_g2_behavior_policy_materialize", {
999
+ title: "Materialize selected G2 behavior policies",
1000
+ description: "Materialize every current exact selected behavior attestation into a separate private Policy IR v2 proposal, exact corpus and plan, and P3 executable proof. Writes private non-authoritative artifacts only; activation remains a separate explicit human action.",
1001
+ inputSchema: {
1002
+ decision_id: z.string().regex(/^dec_[A-Za-z0-9_-]+$/).optional().describe("Exact decision batch to materialize."),
1003
+ since: z.string().min(1).max(100).optional().describe("Git history window used by the complete exact review packet (default 180d)."),
1004
+ max_commits: z.number().int().min(1).max(200).optional().describe("Fix-commit bound used by the exact review packet (default 100)."),
1005
+ limit: z.number().int().min(1).max(100).optional().describe("Item limit used by the exact review packet (default 30)."),
1006
+ allow_install_scripts: z.array(z.string().min(1).max(214)).max(20).optional().describe("Exact dependency package names allowed to run lifecycle scripts while provisioning snapshots."),
1007
+ dependency_timeout_ms: z.number().int().min(1).max(900000).optional().describe("Timeout for each exact dependency snapshot operation (default 300000ms)."),
1008
+ },
1009
+ }, async ({ decision_id, since, max_commits, limit, allow_install_scripts, dependency_timeout_ms }) => {
1010
+ try {
1011
+ return ok(JSON.stringify(new ConstitutionService(store, root).g2BehaviorPolicyMaterialize({
1012
+ since: since ?? "180d",
1013
+ maxCommits: max_commits ?? 100,
1014
+ limit: limit ?? 30,
1015
+ decisionId: decision_id,
1016
+ allowInstallScripts: allow_install_scripts ?? [],
1017
+ dependencyTimeoutMs: dependency_timeout_ms ?? 300_000,
1018
+ }), null, 2));
1019
+ }
1020
+ catch (e) {
1021
+ return err(`Failed to materialize G2 behavior policies: ${e.message}`);
1022
+ }
1023
+ });
740
1024
  return server;
741
1025
  }
742
1026
  function provLine(record) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@davesheffer/hunch",
3
- "version": "1.7.0",
3
+ "version": "1.7.1",
4
4
  "license": "Apache-2.0",
5
5
  "author": "Dave Sheffer <dave.sheffer1@gmail.com>",
6
6
  "description": "Architectural Conformance for AI-generated code: a git-native graph that deterministically blocks AI changes which break your architecture — the semantic invariants (layering, must-reach, dependency direction) pattern-SAST can't express — grounded in the decisions and bugs behind each rule, across any MCP assistant (Claude Code, Cursor, Copilot, Windsurf, Antigravity, Codex).",
@@ -18,6 +18,7 @@
18
18
  },
19
19
  "files": [
20
20
  "dist/**/*.js",
21
+ "bench/constitution-exp03-v1.json",
21
22
  "LICENSE",
22
23
  "NOTICE"
23
24
  ],
@@ -48,6 +49,9 @@
48
49
  "hunch": "tsx src/cli/index.ts",
49
50
  "test": "tsx --test test/*.test.ts",
50
51
  "typecheck": "tsc -p tsconfig.json --noEmit",
52
+ "rehearse:constitution": "npm run build && node tooling/constitution-clean-rehearsal.mjs",
53
+ "gate:release": "node tooling/release-gate.mjs",
54
+ "site:proof": "npm run build && node tooling/generate-public-proof.mjs",
51
55
  "prepublishOnly": "npm run build"
52
56
  },
53
57
  "dependencies": {