@davesheffer/hunch 1.44.0 → 1.45.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +6 -16
- package/dist/cli/index.js +153 -70
- package/dist/core/agenthook.d.ts +1 -0
- package/dist/core/agenthook.js +11 -0
- package/dist/core/escalations.d.ts +20 -0
- package/dist/core/escalations.js +47 -0
- package/dist/core/hookText.js +1 -1
- package/dist/integrations/claudemd.js +1 -1
- package/dist/integrations/gitignore.d.ts +16 -0
- package/dist/integrations/gitignore.js +30 -0
- package/dist/integrations/hooks.d.ts +20 -0
- package/dist/integrations/hooks.js +13 -6
- package/dist/integrations/providers.d.ts +9 -0
- package/dist/integrations/providers.js +73 -9
- package/dist/integrations/scaffold.js +1 -1
- package/dist/integrations/uninstall.d.ts +2 -0
- package/dist/integrations/uninstall.js +72 -11
- package/dist/mcp/leanToolList.d.ts +9 -0
- package/dist/mcp/leanToolList.js +89 -0
- package/dist/mcp/server.js +4 -2
- package/dist/serve/mcpHttp.js +2 -0
- package/package.json +2 -5
- package/server.json +2 -2
- package/tooling/md1-benchmark.mjs +1 -1
- package/tooling/competitive-watch.mjs +0 -142
package/README.md
CHANGED
|
@@ -127,7 +127,7 @@ hunch task list
|
|
|
127
127
|
hunch report <task-id> --html
|
|
128
128
|
```
|
|
129
129
|
|
|
130
|
-
A delivered lesson, an agent's claim, and a passing test are different evidence. Hunch keeps them separate. Automatic presentation depends on the host following the task lifecycle; missing evidence stays unverified.
|
|
130
|
+
A delivered lesson, an agent's claim, and a passing test are different evidence. Hunch keeps them separate. Automatic presentation depends on the host following the task lifecycle; missing evidence stays unverified.
|
|
131
131
|
|
|
132
132
|
## Share one living repository memory with your team
|
|
133
133
|
|
|
@@ -152,19 +152,19 @@ Hunch ships `hunch serve`: a self-hosted HTTP service for organization, team, us
|
|
|
152
152
|
|
|
153
153
|
Open `/operator` on your server to inspect current records, completed work and commitments in a read-only browser view. Writer-supplied citations can point to an exact summary field or text passage and its recorded sources. They show traceability; they do not prove that a source supports a claim.
|
|
154
154
|
|
|
155
|
-
Agents can use the same contract through MCP, `hunch state read|write|records|subscribe`, the `@davesheffer/hunch/state` TypeScript client, or the
|
|
155
|
+
Agents can use the same contract through MCP, `hunch state read|write|records|subscribe`, the `@davesheffer/hunch/state` TypeScript client, or the Python client. The Python package is built and tested from this repository; it is not yet published to PyPI. Scoped conventions let a person record sourced user, team or organization preferences. Those preferences remain advisory and do not silently become blocking rules.
|
|
156
156
|
|
|
157
157
|
Records can describe decisions, action outcomes, commitments, entities, relationships, and summaries that name their dependencies. Actions retain their status, including unknown or unverified outcomes. Repeated writes have stable identities, conflicting current decisions are refused, and confirmed human records receive protections against agent overwrites. These are defined checks on structured records; Hunch cannot establish every fact in the outside world on its own.
|
|
158
158
|
|
|
159
159
|
This is what **deterministic state** means here: explicit rules govern the stored record, rather than having each agent reconstruct it from scratch. Git holds the durable data; SQLite is a rebuildable index. The server binds to loopback and requires deployment and agent integration by its operator. Hunch does not provide a managed CRM or email connector service.
|
|
160
160
|
|
|
161
|
-
|
|
161
|
+
See the [full documentation](https://www.hunchmemory.com/docs).
|
|
162
162
|
|
|
163
163
|
### The vision, and what is still being tested
|
|
164
164
|
|
|
165
165
|
The vision is continuity across people, tools, and agents: an operations agent records a customer issue, a coding agent finds the relevant decision and fixes the cause, and the operations agent closes the commitment using evidence of the fix.
|
|
166
166
|
|
|
167
|
-
The underlying memory and state tools ship today. The broader claim—that different agents reliably use that shared state and avoid contradictory work—is still being measured
|
|
167
|
+
The underlying memory and state tools ship today. The broader claim—that different agents reliably use that shared state and avoid contradictory work—is still being measured.
|
|
168
168
|
|
|
169
169
|
**Hunch remains the product name.** The `nuryel.state/1` protocol and `nuryel_*` tool names are existing technical identifiers; a possible future rename is undecided.
|
|
170
170
|
|
|
@@ -178,7 +178,7 @@ hunch dna context
|
|
|
178
178
|
hunch dna diff <older-ref> <newer-ref>
|
|
179
179
|
```
|
|
180
180
|
|
|
181
|
-
Profiles retain their revision, sources, confidence, and freshness.
|
|
181
|
+
Profiles retain their revision, sources, confidence, and freshness.
|
|
182
182
|
|
|
183
183
|
## Your data and your authority
|
|
184
184
|
|
|
@@ -192,18 +192,8 @@ Profiles retain their revision, sources, confidence, and freshness. [Project DNA
|
|
|
192
192
|
|
|
193
193
|
- [Full documentation](https://www.hunchmemory.com/docs)
|
|
194
194
|
- [Copy-paste cookbook](https://www.hunchmemory.com/cookbook)
|
|
195
|
-
- [
|
|
196
|
-
- [Deterministic organizational state](docs/deterministic-state.md)
|
|
197
|
-
- [Project DNA](docs/project-dna.md)
|
|
198
|
-
- [Native change proof](docs/change-proof.md)
|
|
199
|
-
- [Engineering Landscape](docs/engineering-landscape.md)
|
|
200
|
-
- [Review memory](docs/review-memory.md)
|
|
201
|
-
- [Agent-origin handling](docs/agent-origin.md)
|
|
202
|
-
- [Autonomy ladder](docs/autonomy-ladder.md)
|
|
203
|
-
- [Autonomous development](https://github.com/davesheffer/hunch-private/blob/main/projects/hunch/docs/autonomous-development.md)
|
|
204
|
-
- [Changelog](CHANGELOG.md) · [Roadmap](ROADMAP.md)
|
|
195
|
+
- [Changelog](CHANGELOG.md)
|
|
205
196
|
- [VS Code extension](vscode-extension/README.md)
|
|
206
|
-
- [Architecture benchmark](bench/architectural-conformance.md)
|
|
207
197
|
- [Contributing](CONTRIBUTING.md)
|
|
208
198
|
|
|
209
199
|
Apache-2.0
|
package/dist/cli/index.js
CHANGED
|
@@ -60,7 +60,7 @@ import { installPostCommitHook, installPreCommitHook, installPostMergeHook, inst
|
|
|
60
60
|
import { ensureSharedOverlayPointer } from "../integrations/worktree.js";
|
|
61
61
|
import { flushCapture, flushMemoryHome, flushMemoryHomes, pinSharedRemote, sharedRemoteFor } from "../integrations/sync.js";
|
|
62
62
|
import { installMergeDriver } from "../integrations/mergeDriver.js";
|
|
63
|
-
import { ensureGitignore, ignoreHunchMemory, HUNCH_MEMORY_DIRS, upgradeManagedGitignore, describeGitignoreUpgrade } from "../integrations/gitignore.js";
|
|
63
|
+
import { ensureGitignore, ignoreHunchMemory, HUNCH_MEMORY_DIRS, upgradeManagedGitignore, describeGitignoreUpgrade, memoryIgnoreRule, describeMemoryIgnoreRule } from "../integrations/gitignore.js";
|
|
64
64
|
import { writeCiWorkflow } from "../integrations/ciAction.js";
|
|
65
65
|
import { applyUninstall, formatUninstallPlan, isOptedOut, OPT_OUT_FILE, optOutMessage, planUninstall } from "../integrations/uninstall.js";
|
|
66
66
|
import { updateClaudeMd, renderHunchSection } from "../integrations/claudemd.js";
|
|
@@ -69,7 +69,7 @@ import { measureFootprint } from "../core/footprint.js";
|
|
|
69
69
|
import { classifyGroundingBlock, describeGroundingFreshness } from "../core/groundingLag.js";
|
|
70
70
|
import { mergeGroundingFile } from "../core/groundingMerge.js";
|
|
71
71
|
import { writeMcpJson, writeSlashCommands, installClaudeHooks } from "../integrations/scaffold.js";
|
|
72
|
-
import { scaffoldProviders, regenerateGrounding, refreshExistingGrounding, refreshCommittableGrounding, GROUNDING_DOC_PATHS } from "../integrations/providers.js";
|
|
72
|
+
import { scaffoldProviders, detectAssistants, PROVIDER_ASSISTANTS, regenerateGrounding, refreshExistingGrounding, refreshCommittableGrounding, GROUNDING_DOC_PATHS } from "../integrations/providers.js";
|
|
73
73
|
import { healClaudeConfigCaseSplit } from "../integrations/claudeConfig.js";
|
|
74
74
|
import { formatSearchHit, formatStructure } from "../core/format.js";
|
|
75
75
|
import { isStateKind, renderStateLine, stateSupplements } from "../core/stateDelivery.js";
|
|
@@ -94,7 +94,7 @@ import { isHumanConfirmed } from "../core/strictgate.js";
|
|
|
94
94
|
import { appendEvent, readEvents } from "../core/events.js";
|
|
95
95
|
import { computeStats, formatStats } from "../core/stats.js";
|
|
96
96
|
import { clearTaskSelection, injectionMode, loadTaskSelection, resetSessionInjections, saveTaskSelection, taskSelectionEnabled } from "../core/hookcache.js";
|
|
97
|
-
import { isBareFollowUp, isFilterableSelectionId, liveSelectionRecords, selectForTask } from "../core/taskSelection.js";
|
|
97
|
+
import { isBareFollowUp, isFilterableSelectionId, liveSelectionRecords, promptPaths, selectForTask } from "../core/taskSelection.js";
|
|
98
98
|
import { recordServed, servedSummary } from "../core/served.js";
|
|
99
99
|
import { readTaskReport, recordTaskDelivery, reportActivity, reportHash, reportPresentationEnabled, unseenLessons } from "../core/taskReport.js";
|
|
100
100
|
import { snapshotDeliveredRecords } from "../core/taskReportEvidence.js";
|
|
@@ -102,7 +102,7 @@ import { renderRecalledLine } from "../core/taskReportRender.js";
|
|
|
102
102
|
import { closeHookTask, hookReportTaskId, isNotificationPrompt, nativeHookCwd, settleHookSession, startHookReport, stopHookReport, observeHookDenial } from "../core/taskReportHook.js";
|
|
103
103
|
import { persistTaskRecord } from "../core/taskRecord.js";
|
|
104
104
|
import { recordHookObservation } from "../core/hookObservations.js";
|
|
105
|
-
import { contextHookOutput, denyHookOutput, hookProvider, normalizeHookEvent, stopHookOutput } from "../core/agenthook.js";
|
|
105
|
+
import { contextHookOutput, deliversPromptEvent, denyHookOutput, hookProvider, normalizeHookEvent, stopHookOutput } from "../core/agenthook.js";
|
|
106
106
|
import { PIPELINE_LOOP, armExecutionObligations, beforeEditProbeVerdict, compileExecutableProbes, environmentExecutableProbes, environmentExecutionObligations, executionObligationBrief, isProductPath, loadPipelineState, onCommand, onEdit, onPrompt, onSkill, pipelineEnabled, proofCheckpoint, savePipelineState, stopVerdict, onLessonsDelivered, lessonReminder, unverifiedNag, } from "../core/pipeline.js";
|
|
107
107
|
import { draftDuplicateOf, isAcceptedDuplicateAnchor } from "../core/dupdetect.js";
|
|
108
108
|
import { planAutoReview, planMutations } from "../core/autoreview.js";
|
|
@@ -118,7 +118,7 @@ import { countersignConstraint, countersignDecision } from "../core/countersign.
|
|
|
118
118
|
import { applyImportedAdrReview, carryImportedAdrReview, importedAdrReviewHash, importedAdrSourceHash, isImportedAdrDecision, pendingImportedAdrReviews } from "../core/importReview.js";
|
|
119
119
|
import { exportMadrCorpus, isRegenerableMadr } from "../integrations/madrExport.js";
|
|
120
120
|
import { buildMadrManifest, writeMadrManifest, refreshMadrCorpus } from "../integrations/madrManifest.js";
|
|
121
|
-
import { pendingEscalations, policyEscalations, commitRepairEscalations, actionableEscalations, escalationHeadline } from "../core/escalations.js";
|
|
121
|
+
import { pendingEscalations, policyEscalations, commitRepairEscalations, actionableEscalations, escalationHeadline, relevantEscalations } from "../core/escalations.js";
|
|
122
122
|
import { premiseEscalations } from "../core/premises.js";
|
|
123
123
|
import { parseDocAnchors, renderDocGrounding } from "../core/docanchors.js";
|
|
124
124
|
import { pathMatchesGlob } from "../core/glob.js";
|
|
@@ -354,6 +354,8 @@ program
|
|
|
354
354
|
.option("--private-sync", "post-commit synthesis writes captured decisions into the overlay repo (HUNCH_PRIVATE_DIR), never the public store")
|
|
355
355
|
.option("--shared-sync", "alias of --private-sync (for teams using one shared overlay repo for any code repo)")
|
|
356
356
|
.option("--no-auto-commit", "DON'T auto-commit captures (default: ON in every mode — the overlay repo is committed+pushed; the public .hunch/ is committed only and rides your next push)")
|
|
357
|
+
.option("--all-assistants", "configure every supported assistant, not only those found on this machine or in this repository")
|
|
358
|
+
.option("--details", "print every setup step and the per-assistant integration status")
|
|
357
359
|
.action((opts) => {
|
|
358
360
|
// Validate --firmness up front, before any side effects (indexing, git hooks,
|
|
359
361
|
// .mcp.json) or opening the store — a bad value must not leave a half-init.
|
|
@@ -367,32 +369,51 @@ program
|
|
|
367
369
|
// teammate/agent resolves the same single source of truth with zero manual setup.
|
|
368
370
|
const { store, teamWired } = openTeamStore(root);
|
|
369
371
|
const inv = resolveInvocation();
|
|
370
|
-
|
|
372
|
+
// Step lines are collected: the default output is a short summary with one next
|
|
373
|
+
// step; `--details` prints every step as written. Warnings always print.
|
|
374
|
+
const lines = [];
|
|
375
|
+
const say = (text) => { lines.push({ text, warn: text.includes("⚠") }); };
|
|
376
|
+
// A hook warning is followed by the snippet to add by hand: keep the group together.
|
|
377
|
+
const sayHook = (group, installed) => {
|
|
378
|
+
const warn = group.some((l) => l.includes("⚠"));
|
|
379
|
+
for (const text of group)
|
|
380
|
+
lines.push({ text, warn });
|
|
381
|
+
if (!warn)
|
|
382
|
+
summary.hooks.push(installed);
|
|
383
|
+
};
|
|
384
|
+
const summary = { index: "", hooks: [], assistants: ["Claude Code"], skipped: [] };
|
|
385
|
+
say(`🧠 Initializing Hunch at ${root}`);
|
|
371
386
|
store.json.ensureDirs(); // stamps the manifest at the current version when fresh
|
|
372
|
-
|
|
387
|
+
say(` ✓ .hunch/ scaffolded (schema v${readManifest(paths).schema_version})`);
|
|
373
388
|
if (teamWired)
|
|
374
|
-
|
|
389
|
+
say(` ✓ connected to the team's shared memory store (from .hunch/team.json) → ${teamWired}`);
|
|
375
390
|
// Exclude the derived SQLite index BEFORE it's written, so the working tree
|
|
376
391
|
// never goes dirty on the MCP server's index writes (which blocks branch
|
|
377
392
|
// switches). The .hunch/*.json graph stays tracked.
|
|
378
393
|
const gi = ensureGitignore(root);
|
|
379
394
|
if (gi.action !== "unchanged")
|
|
380
|
-
|
|
395
|
+
say(` ✓ .gitignore ${gi.action} (Hunch runtime index excluded)`);
|
|
381
396
|
// A repo migrated to a private overlay by an older release: bring its private-only
|
|
382
397
|
// block up to date (and stop publishing newly ignored memory dirs).
|
|
383
398
|
for (const line of describeGitignoreUpgrade(upgradeManagedGitignore(root)))
|
|
384
|
-
|
|
399
|
+
say(` ✓ ${line}`);
|
|
400
|
+
if (!store.privateDir) {
|
|
401
|
+
const ignored = memoryIgnoreRule(root);
|
|
402
|
+
if (ignored)
|
|
403
|
+
say(` ⚠ ${describeMemoryIgnoreRule(ignored)}`);
|
|
404
|
+
}
|
|
385
405
|
if (opts.index !== false) {
|
|
386
406
|
if (isGitRepo(root) && revExists("HEAD", root)) {
|
|
387
407
|
const res = indexRepo(store, root, { source: { kind: "commit", ref: "HEAD" } });
|
|
388
408
|
store.reindex();
|
|
389
|
-
|
|
409
|
+
say(` ✓ indexed committed HEAD (${res.files} files) → ${res.symbols} symbols, ${res.edges} edges, ${res.components} components`);
|
|
410
|
+
summary.index = `indexed committed HEAD (${res.files} files, ${res.symbols} symbols)`;
|
|
390
411
|
for (const item of res.coverage) {
|
|
391
412
|
const reasons = Object.entries(item.reasons).map(([reason, count]) => `${reason}:${count}`).join(", ");
|
|
392
|
-
|
|
413
|
+
say(` coverage ${item.language}: ${item.eligible} eligible · ${item.parsed} parsed · ${item.skipped} skipped${reasons ? ` (${reasons})` : ""}`);
|
|
393
414
|
}
|
|
394
415
|
if (res.skipped)
|
|
395
|
-
|
|
416
|
+
say(` ⚠ ${res.skipped} file(s) could not be parsed (skipped)`);
|
|
396
417
|
}
|
|
397
418
|
else {
|
|
398
419
|
// A checkout-derived graph can be swept into a later memory commit by
|
|
@@ -402,7 +423,7 @@ program
|
|
|
402
423
|
store.json.replaceAll("edges", mergeScannedEdges(store.json.loadAll("edges"), []));
|
|
403
424
|
store.json.replaceAll("components", []);
|
|
404
425
|
store.reindex();
|
|
405
|
-
|
|
426
|
+
say(" ⚠ skipped code graph: no committed HEAD — commit code, then run `hunch index`");
|
|
406
427
|
}
|
|
407
428
|
}
|
|
408
429
|
// Auto-commit is ON by default in every mode; --no-auto-commit persists the opt-out in
|
|
@@ -424,11 +445,11 @@ program
|
|
|
424
445
|
}
|
|
425
446
|
}
|
|
426
447
|
if (existing === null) {
|
|
427
|
-
|
|
448
|
+
say(` ⚠ refusing to rewrite ${localFile}: it is not a JSON object and may hold your private-overlay pointer. Fix or remove it, then re-run \`hunch init --no-auto-commit\`.`);
|
|
428
449
|
}
|
|
429
450
|
else {
|
|
430
451
|
writeFileAtomic(localFile, JSON.stringify({ ...existing, autoCommit: false }, null, 2) + "\n");
|
|
431
|
-
|
|
452
|
+
say(" ✓ local auto-commit preference OFF (an explicit hook --commit flag still takes precedence)");
|
|
432
453
|
}
|
|
433
454
|
}
|
|
434
455
|
// Collected so the linked-worktree note below can say what actually happened
|
|
@@ -446,21 +467,18 @@ program
|
|
|
446
467
|
}
|
|
447
468
|
const h = installPostCommitHook(root, inv.shell, { private: syncToOverlay, commit: hookCommit, localOnly: syncToOverlay });
|
|
448
469
|
installs.push(h);
|
|
449
|
-
|
|
450
|
-
console.log(line);
|
|
470
|
+
sayHook(formatHookInstall(root, "post-commit hook", h, ` (learning loop)${syncToOverlay ? " — syncs to the shared overlay" : ""}${hookCommit ? " — auto-commit on" : ""}`), hookCommit ? "post-commit (auto-commit on)" : "post-commit");
|
|
451
471
|
if (opts.autoCommit === false && hookReport(root).postCommit.flags?.includes("--commit")) {
|
|
452
|
-
|
|
472
|
+
say(" ⚠ the shared post-commit hook still auto-commits. To change it, re-run `hunch init --no-auto-commit` using a global install or the main checkout's Hunch.");
|
|
453
473
|
}
|
|
454
474
|
const pm = installPostMergeHook(root, inv.shell);
|
|
455
475
|
installs.push(pm);
|
|
456
|
-
|
|
457
|
-
console.log(line);
|
|
476
|
+
sayHook(formatHookInstall(root, "post-merge hook", pm, " (squash-merge provenance repair + re-syncs grounding docs after a merge that brought memory in)"), "post-merge");
|
|
458
477
|
const pc = installPostCheckoutHook(root, inv.shell);
|
|
459
478
|
installs.push(pc);
|
|
460
|
-
|
|
461
|
-
console.log(line);
|
|
479
|
+
sayHook(formatHookInstall(root, "post-checkout hook", pc, " (workspace ledger: records this machine's branches + worktrees on checkout)"), "post-checkout");
|
|
462
480
|
const m = installMergeDriver(root, inv.shell);
|
|
463
|
-
|
|
481
|
+
say(` ✓ team merge driver ${m.action}`);
|
|
464
482
|
// Auto-install the pre-commit guard by default (advisory: flags invariants
|
|
465
483
|
// touched directly OR via blast radius, never blocks). Opt out with
|
|
466
484
|
// --no-enforce; --enforce-strict makes blocking near/direct hits fail the commit.
|
|
@@ -468,12 +486,11 @@ program
|
|
|
468
486
|
const strict = !!opts.enforceStrict;
|
|
469
487
|
const p = installPreCommitHook(root, inv.shell, strict);
|
|
470
488
|
installs.push(p);
|
|
471
|
-
|
|
472
|
-
console.log(line);
|
|
489
|
+
sayHook(formatHookInstall(root, "pre-commit constraint guard", p, ` (${strict ? "strict — fails only on direct, high-confidence, non-stale blocking invariants" : "advisory — flags invariants in scope or blast radius"})`), `pre-commit guard (${strict ? "strict" : "advisory"})`);
|
|
473
490
|
}
|
|
474
491
|
}
|
|
475
492
|
else {
|
|
476
|
-
|
|
493
|
+
say(" ⚠ not a git repo — skipped hooks (run `git init` to enable the learning loop)");
|
|
477
494
|
}
|
|
478
495
|
// .mcp.json is the CANONICAL registration: Claude Code resolves it by file path,
|
|
479
496
|
// so it's immune to the Windows ~/.claude.json drive-letter case-split that a
|
|
@@ -481,18 +498,18 @@ program
|
|
|
481
498
|
// existing file is refused (con_8460b6770f) — warn and continue, never clobber.
|
|
482
499
|
try {
|
|
483
500
|
const mcp = writeMcpJson(root, inv.mcp);
|
|
484
|
-
|
|
501
|
+
say(` ✓ wrote ${rel(root, mcp)} (registers the Hunch MCP server — canonical, path-keyed; prefer over a global \`claude mcp add\`)`);
|
|
485
502
|
}
|
|
486
503
|
catch (e) {
|
|
487
|
-
|
|
504
|
+
say(` ⚠ skipped .mcp.json: ${e.message}`);
|
|
488
505
|
}
|
|
489
506
|
const cmds = writeSlashCommands(root);
|
|
490
|
-
|
|
507
|
+
say(` ✓ wrote ${cmds.written.length} slash commands (/hunch-why, /hunch-fix, /hunch-fragile, /capture, /heal, /audit)`);
|
|
491
508
|
for (const s of cmds.skipped) {
|
|
492
|
-
|
|
509
|
+
say(` ⚠ kept your existing ${rel(root, s)} (no hunch:generated marker — delete the file and re-run init to adopt Hunch's version)`);
|
|
493
510
|
}
|
|
494
511
|
const cmd = updateClaudeMd(root, store);
|
|
495
|
-
|
|
512
|
+
say(` ✓ updated ${rel(root, cmd)} with ambient Hunch context`);
|
|
496
513
|
// Firmness: stamp .hunch/config.json (default advisory) so `hunch hook` reads a
|
|
497
514
|
// level even before the user runs `hunch firmness` (--firmness validated above).
|
|
498
515
|
const firmness = writeConfig(paths, opts.firmness ? { firmness: opts.firmness } : {}).firmness;
|
|
@@ -500,18 +517,25 @@ program
|
|
|
500
517
|
// adapter reads firmness at run time, so changing it needs no config rewrite.
|
|
501
518
|
if (opts.agentHooks !== false) {
|
|
502
519
|
const a = installClaudeHooks(root, `${inv.agentHookShell} hook`);
|
|
503
|
-
|
|
520
|
+
say(` ✓ Claude Code agent hooks ${a.action} (firmness: ${firmness} — change with \`hunch firmness <level>\`)`);
|
|
504
521
|
}
|
|
505
522
|
// Multi-assistant compatibility: MCP + grounding + lifecycle adapters share
|
|
506
523
|
// the same .hunch/ graph across Cursor / VS Code / Windsurf / Antigravity.
|
|
507
524
|
if (opts.providers !== false) {
|
|
508
|
-
const
|
|
525
|
+
const detected = detectAssistants(root);
|
|
526
|
+
const only = opts.allAssistants || detected.size === 0 ? undefined : detected;
|
|
527
|
+
const ps = scaffoldProviders(root, inv.mcp, store, { agentHooks: opts.agentHooks !== false, only });
|
|
509
528
|
const ok = ps.filter((p) => !p.error);
|
|
529
|
+
summary.assistants.push(...ok.map((p) => p.assistant === "Any (AGENTS.md)" ? "any tool that reads AGENTS.md" : p.assistant));
|
|
530
|
+
if (only)
|
|
531
|
+
summary.skipped = PROVIDER_ASSISTANTS.filter((a) => !only.has(a));
|
|
532
|
+
if (summary.skipped.length)
|
|
533
|
+
say(` ✓ not configured (not found on this machine or in this repository): ${summary.skipped.join(", ")} — add them with \`hunch init --all-assistants\``);
|
|
510
534
|
const total = ok.reduce((a, p) => a + p.files.length, 0);
|
|
511
|
-
|
|
535
|
+
say(` ✓ wrote ${total} multi-assistant config file(s) → ${ok.map((p) => p.assistant).join(", ")}`);
|
|
512
536
|
for (const p of ps)
|
|
513
537
|
if (p.error)
|
|
514
|
-
|
|
538
|
+
say(` ⚠ skipped ${p.assistant}: ${p.error}`);
|
|
515
539
|
}
|
|
516
540
|
// Windows self-heal: if an earlier global `claude mcp add` left a drive-letter
|
|
517
541
|
// case-split in ~/.claude.json, merge it so hunch resolves under either casing.
|
|
@@ -521,16 +545,36 @@ program
|
|
|
521
545
|
// worktree of this repo auto-discovers it (also backfills pre-0.32 single-worktree setups),
|
|
522
546
|
// and note when we're initializing inside a linked worktree (memory is shared, not separate).
|
|
523
547
|
if (ensureSharedOverlayPointer(root, store.privateDir, store.privateAutoCommit, store.mode === "shared" ? "shared" : "private", true)) {
|
|
524
|
-
|
|
548
|
+
say(` ✓ private overlay registered at the git common dir — shared by every worktree of this repo`);
|
|
525
549
|
}
|
|
526
550
|
for (const line of sharedHooksNote(root, installs))
|
|
527
|
-
|
|
551
|
+
say(line);
|
|
528
552
|
store.close();
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
553
|
+
if (opts.details) {
|
|
554
|
+
for (const l of lines)
|
|
555
|
+
console.log(l.text);
|
|
556
|
+
console.log("\n" + formatIntegrationHealth(inspectIntegrations(root)));
|
|
557
|
+
console.log("\nTask reporting is configured through Hunch's agent instructions; actual agent activity has not been verified by setup. Reconnect the agent, then work normally. Completed task reports are available with `hunch report`.");
|
|
558
|
+
console.log(reportActivity(root));
|
|
559
|
+
console.log("Cold start? Seed from history: hunch backfill --since 90d");
|
|
560
|
+
console.log("\n⭐ If Hunch earns its keep, a star helps others find it → https://github.com/davesheffer/hunch");
|
|
561
|
+
return;
|
|
562
|
+
}
|
|
563
|
+
console.log(`🧠 Hunch is set up in ${root}`);
|
|
564
|
+
console.log(` ✓ memory: .hunch/${teamWired ? ` (connected to your team's shared store)` : ""}`);
|
|
565
|
+
if (summary.index)
|
|
566
|
+
console.log(` ✓ code index: ${summary.index}`);
|
|
567
|
+
if (summary.hooks.length)
|
|
568
|
+
console.log(` ✓ git hooks: ${summary.hooks.join(", ")}`);
|
|
569
|
+
console.log(` ✓ assistants: ${summary.assistants.join(", ")}`);
|
|
570
|
+
if (summary.skipped.length)
|
|
571
|
+
console.log(` not found here, so not configured: ${summary.skipped.join(", ")} (add with --all-assistants)`);
|
|
572
|
+
for (const l of lines)
|
|
573
|
+
if (l.warn)
|
|
574
|
+
console.log(l.text);
|
|
575
|
+
console.log("\nNext: reload your assistant, then ask it why a file you're about to change is built that way.");
|
|
576
|
+
console.log("No memory yet? Draft it from this repository's history: hunch backfill --since 90d");
|
|
577
|
+
console.log("More: hunch init --details (everything that was written) · hunch doctor (check the setup)");
|
|
534
578
|
});
|
|
535
579
|
// ---- uninstall ------------------------------------------------------------
|
|
536
580
|
program
|
|
@@ -5190,6 +5234,16 @@ program
|
|
|
5190
5234
|
}
|
|
5191
5235
|
}
|
|
5192
5236
|
catch { /* the next Stop or prompt retries */ }
|
|
5237
|
+
// Escalations this prompt's task touches (dec_12ba37db1c): asked once per
|
|
5238
|
+
// session, never answered for the human; the rest stay pending in
|
|
5239
|
+
// `hunch escalations`. A broken store must not take the prompt hook down.
|
|
5240
|
+
try {
|
|
5241
|
+
store ??= new HunchStore(paths);
|
|
5242
|
+
const ask = await promptEscalations(root, store, provider, evt);
|
|
5243
|
+
if (ask)
|
|
5244
|
+
parts.push(ask);
|
|
5245
|
+
}
|
|
5246
|
+
catch { /* escalations stay listed by `hunch escalations` */ }
|
|
5193
5247
|
// Pipeline turn bookkeeping (fresh block budget) + the one nag that must
|
|
5194
5248
|
// repeat: edits from an earlier turn still unverified.
|
|
5195
5249
|
if (evt.session_id && pipelineEnabled()) {
|
|
@@ -5351,36 +5405,17 @@ program
|
|
|
5351
5405
|
// further than a terminal.
|
|
5352
5406
|
const decisions = s.advisoryRecs("decisions");
|
|
5353
5407
|
const { pendingReview } = nowData(decisions, 3);
|
|
5354
|
-
|
|
5355
|
-
|
|
5356
|
-
//
|
|
5357
|
-
//
|
|
5358
|
-
//
|
|
5359
|
-
|
|
5360
|
-
// out of session transcripts. Only the id and commit shas surface.
|
|
5361
|
-
const sessionStartQueue = readActivePendingRepairs(paths.root);
|
|
5362
|
-
escalations.push(...commitRepairEscalations(sessionStartQueue, decisions, s.recs("decisions"), withheldRewrites(paths.root, sessionStartQueue)));
|
|
5363
|
-
try {
|
|
5364
|
-
// Constitution human moments ride the same line; a broken policy store
|
|
5365
|
-
// must never take session-start orientation down (fail open). Public
|
|
5366
|
-
// store only — session transcripts travel further than a terminal.
|
|
5367
|
-
const { ConstitutionService: CS } = await import("../constitution/service.js");
|
|
5368
|
-
escalations.push(...policyEscalations(new CS(s, paths.root).list({ publicOnly: true }).map((p) => ({ ...p, last_action: p.audit.at(-1)?.action ?? null }))));
|
|
5369
|
-
}
|
|
5370
|
-
catch { /* constitution unavailable */ }
|
|
5371
|
-
// Only ACTIONABLE entries are worth asking inline — a duplicate-id
|
|
5372
|
-
// commit-repair follower whose own resolution says "act on a
|
|
5373
|
-
// different entry first" isn't a question the assistant can put to
|
|
5374
|
-
// the human directly (#61). Filtered BEFORE the bail check below so
|
|
5375
|
-
// "is there anything to say" and "what do we say" share one
|
|
5376
|
-
// predicate — an escalations list that's entirely non-actionable
|
|
5377
|
-
// must bail exactly like an empty one would.
|
|
5378
|
-
const actionableEsc = actionableEscalations(escalations);
|
|
5408
|
+
// Escalations are task-scoped (dec_12ba37db1c): session start has no task
|
|
5409
|
+
// yet, so it asks nothing when the provider delivers prompts — the prompt
|
|
5410
|
+
// hook asks about the ones a prompt touches; every entry stays pending in
|
|
5411
|
+
// `hunch escalations`. A provider with no prompt event would never ask,
|
|
5412
|
+
// so its orientation event keeps asking every actionable entry.
|
|
5413
|
+
const actionableEsc = deliversPromptEvent(provider) ? [] : (await actionableEscalationsWithAnchors(paths.root, s)).actionable;
|
|
5379
5414
|
if (!decisions.length && !actionableEsc.length) {
|
|
5380
5415
|
// Fresh graph and nothing else to raise: nothing to orient on, but
|
|
5381
5416
|
// the operating loop still ships. A queued commit-repair escalation
|
|
5382
|
-
// (checked against the full store
|
|
5383
|
-
//
|
|
5417
|
+
// (checked against the full store) is enough reason NOT to bail here
|
|
5418
|
+
// even when the visible decisions list is empty.
|
|
5384
5419
|
if (pipelineEnabled())
|
|
5385
5420
|
emitContext(provider, "SessionStart", [PIPELINE_LOOP, controllerBrief].filter(Boolean).join("\n\n"));
|
|
5386
5421
|
return;
|
|
@@ -7501,6 +7536,9 @@ program
|
|
|
7501
7536
|
console.log(team
|
|
7502
7537
|
? `overlay: off, but .hunch/team.json advertises the team store (${team.shared_repo}) — ${isTeamStoreTrusted(root, team) ? "run \`hunch init\` to auto-connect" : "if it is your team's store, run \`hunch shared --trust\` to connect"}`
|
|
7503
7538
|
: dim(`private: off — run \`hunch shared\` (or \`hunch private\`) to use one overlay repo across teammates/worktrees (or set HUNCH_PRIVATE_DIR)`));
|
|
7539
|
+
const ignored = memoryIgnoreRule(root);
|
|
7540
|
+
if (ignored)
|
|
7541
|
+
console.log(`memory: ⚠ ${describeMemoryIgnoreRule(ignored)}`);
|
|
7504
7542
|
}
|
|
7505
7543
|
// Worktree posture: linked worktrees share ONE memory via the git common dir. Only
|
|
7506
7544
|
// surfaced in a linked worktree (no noise in a normal single checkout), so a
|
|
@@ -7625,6 +7663,51 @@ function shellWriteGrounding(root, store, provider, evt, written) {
|
|
|
7625
7663
|
* A follow-up prompt widens the qualifying set rather than replacing it, so a
|
|
7626
7664
|
* "go" cannot unselect the task's memory. */
|
|
7627
7665
|
const TASK_SELECTION_TITLE_CHARS = 80;
|
|
7666
|
+
/** Every actionable escalation plus each entry id's anchor paths (a decision's
|
|
7667
|
+
* related_files, a policy's scope paths/globs). Titles come from the advisory
|
|
7668
|
+
* scope (public-only in private mode: session transcripts travel further than
|
|
7669
|
+
* a terminal); commit-repair liveness and the anchors read the full store, so a
|
|
7670
|
+
* private decision's repair stays answerable by id. */
|
|
7671
|
+
async function actionableEscalationsWithAnchors(root, store) {
|
|
7672
|
+
const decisions = store.advisoryRecs("decisions");
|
|
7673
|
+
const all = store.recs("decisions");
|
|
7674
|
+
const anchors = new Map(all.map((d) => [d.id, d.related_files]));
|
|
7675
|
+
const escalations = pendingEscalations(decisions);
|
|
7676
|
+
escalations.push(...premiseEscalations(decisions, { now: new Date().toISOString(), exists: (p) => existsSync(join(root, p)) }));
|
|
7677
|
+
const queue = readActivePendingRepairs(root);
|
|
7678
|
+
escalations.push(...commitRepairEscalations(queue, decisions, all, withheldRewrites(root, queue)));
|
|
7679
|
+
try {
|
|
7680
|
+
// Constitution human moments ride the same line; a broken policy store must
|
|
7681
|
+
// never take the hook down (fail open). Public store only.
|
|
7682
|
+
const { ConstitutionService: CS } = await import("../constitution/service.js");
|
|
7683
|
+
const policies = new CS(store, root).list({ publicOnly: true });
|
|
7684
|
+
for (const p of policies)
|
|
7685
|
+
anchors.set(p.id, p.scope.paths);
|
|
7686
|
+
escalations.push(...policyEscalations(policies.map((p) => ({ ...p, last_action: p.audit.at(-1)?.action ?? null }))));
|
|
7687
|
+
}
|
|
7688
|
+
catch { /* constitution unavailable */ }
|
|
7689
|
+
// Only ACTIONABLE entries are worth asking inline — a duplicate-id commit-repair
|
|
7690
|
+
// follower whose resolution says "act on a different entry first" isn't a
|
|
7691
|
+
// question the assistant can put to the human directly (#61).
|
|
7692
|
+
return { actionable: actionableEscalations(escalations), anchors };
|
|
7693
|
+
}
|
|
7694
|
+
/** The ASK line for the escalations one prompt touches, or "" (dec_12ba37db1c). */
|
|
7695
|
+
async function promptEscalations(root, store, provider, evt) {
|
|
7696
|
+
if (!evt.prompt || isNotificationPrompt(evt.prompt))
|
|
7697
|
+
return "";
|
|
7698
|
+
const { actionable, anchors } = await actionableEscalationsWithAnchors(root, store);
|
|
7699
|
+
if (!actionable.length)
|
|
7700
|
+
return "";
|
|
7701
|
+
// A qualifying record outside the top-K is still part of this task.
|
|
7702
|
+
const selection = loadTaskSelection(hookReportTaskId(root, provider, evt));
|
|
7703
|
+
const selectedIds = new Set([...(selection?.top ?? []), ...(selection?.qualifying ?? [])]);
|
|
7704
|
+
const relevant = relevantEscalations(actionable, { prompt: evt.prompt, paths: promptPaths(evt.prompt, root), selectedIds }, (id) => anchors.get(id) ?? []);
|
|
7705
|
+
// Each question is asked once per session, however the touched set changes.
|
|
7706
|
+
const unasked = relevant.filter((e) => injectionMode(evt.session_id, `prompt-escalation:${e.kind}:${e.topic}:${e.decisionIds.join(",")}`, e.question) === "full");
|
|
7707
|
+
if (!unasked.length)
|
|
7708
|
+
return "";
|
|
7709
|
+
return `⚖ ${unasked.length} decision(s) this task touches need the human's call — ASK the user inline, never decide for them: ${unasked.map((e) => e.question).join(" · ")}`;
|
|
7710
|
+
}
|
|
7628
7711
|
function promptTaskSelection(root, store, provider, evt) {
|
|
7629
7712
|
// A host notification is not a task prompt: the earlier selection stands untouched.
|
|
7630
7713
|
if (!taskSelectionEnabled() || isNotificationPrompt(evt.prompt))
|
package/dist/core/agenthook.d.ts
CHANGED
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
*/
|
|
8
8
|
export declare const HOOK_PROVIDERS: readonly ["claude", "codex", "vscode", "windsurf", "antigravity", "cursor"];
|
|
9
9
|
export type HookProvider = (typeof HOOK_PROVIDERS)[number];
|
|
10
|
+
export declare function deliversPromptEvent(provider: HookProvider): boolean;
|
|
10
11
|
export type HunchHookEvent = "PreToolUse" | "PostToolUse" | "PostToolUseFailure" | "UserPromptSubmit" | "SessionStart" | "SubagentStart" | "PreCompact" | "Stop";
|
|
11
12
|
/** One file section of a Codex `apply_patch` payload. */
|
|
12
13
|
export interface HunchPatchFile {
|
package/dist/core/agenthook.js
CHANGED
|
@@ -6,6 +6,17 @@
|
|
|
6
6
|
* the same small, fail-open shape regardless of the assistant that emitted it.
|
|
7
7
|
*/
|
|
8
8
|
export const HOOK_PROVIDERS = ["claude", "codex", "vscode", "windsurf", "antigravity", "cursor"];
|
|
9
|
+
/** Whether a provider's hooks deliver the user's prompt text (a UserPromptSubmit
|
|
10
|
+
* equivalent). Prompt-scoped behavior needs it; a provider without one gets that
|
|
11
|
+
* behavior at its session/orientation event instead. Antigravity's PreInvocation
|
|
12
|
+
* payload carries only conversation/invocation counters, no turn text. A Record,
|
|
13
|
+
* so a new provider must declare its answer. */
|
|
14
|
+
const PROMPT_EVENT = {
|
|
15
|
+
claude: true, codex: true, vscode: true, windsurf: true, cursor: true, antigravity: false,
|
|
16
|
+
};
|
|
17
|
+
export function deliversPromptEvent(provider) {
|
|
18
|
+
return PROMPT_EVENT[provider];
|
|
19
|
+
}
|
|
9
20
|
function obj(value) {
|
|
10
21
|
return value && typeof value === "object" && !Array.isArray(value) ? value : null;
|
|
11
22
|
}
|
|
@@ -127,3 +127,23 @@ export interface PolicyLite {
|
|
|
127
127
|
* accept/reject) is a human call. Machine conclusions never appear here as
|
|
128
128
|
* approvals — every entry is a QUESTION with its explicit resolution verb. */
|
|
129
129
|
export declare function policyEscalations(policies: readonly PolicyLite[]): Escalation[];
|
|
130
|
+
/** What one prompt is about, for task-scoped escalation delivery (dec_12ba37db1c). */
|
|
131
|
+
export interface EscalationScope {
|
|
132
|
+
/** the prompt text as the human wrote it. */
|
|
133
|
+
prompt: string;
|
|
134
|
+
/** repo-relative paths the prompt names (taskSelection.promptPaths). */
|
|
135
|
+
paths: readonly string[];
|
|
136
|
+
/** record ids the prompt's task selection put on top, when there is one. */
|
|
137
|
+
selectedIds?: ReadonlySet<string>;
|
|
138
|
+
}
|
|
139
|
+
/** The escalations one prompt's task touches. A prompt hook asks the human only
|
|
140
|
+
* about these; every other entry stays exactly as pending — still listed by
|
|
141
|
+
* `hunch escalations`, hunch_escalations and hunch_now, never dropped and
|
|
142
|
+
* never answered for the human. Deterministic explicit anchors only: a
|
|
143
|
+
* prompt that asks about escalations themselves (path tokens aside), an entry
|
|
144
|
+
* id or topic the prompt names, an id the task selection chose, or a recorded
|
|
145
|
+
* anchor path the prompt names (a directory covers the files under it; a glob
|
|
146
|
+
* covers the paths it matches). `relatedFiles` maps an entry id to its anchor
|
|
147
|
+
* paths: a decision's related_files, a policy's scope paths/globs. An entry
|
|
148
|
+
* with no anchor the prompt names stays CLI/MCP-only. */
|
|
149
|
+
export declare function relevantEscalations(items: readonly Escalation[], scope: EscalationScope, relatedFiles: (id: string) => readonly string[]): Escalation[];
|
package/dist/core/escalations.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { topicCollisions } from "./topics.js";
|
|
2
2
|
import { liveRewrites, deadRewrites, commitRepairReviewHash, firstFor } from "./commitrepair.js";
|
|
3
3
|
import { importedAdrReviewHash, importedAdrSourceHash, pendingImportedAdrReviews } from "./importReview.js";
|
|
4
|
+
import { pathMatchesGlob } from "./glob.js";
|
|
4
5
|
/** Escalations gating surfaces (CI exit codes, "needs your call" counts/lists)
|
|
5
6
|
* should act on: everything except an entry explicitly marked
|
|
6
7
|
* actionable:false (#61). The full list — including non-actionable
|
|
@@ -259,4 +260,50 @@ export function policyEscalations(policies) {
|
|
|
259
260
|
}
|
|
260
261
|
return out;
|
|
261
262
|
}
|
|
263
|
+
const trimSlash = (x) => x.replace(/\/+$/, "");
|
|
264
|
+
const samePathOrNested = (x, y) => {
|
|
265
|
+
const a = trimSlash(x), b = trimSlash(y);
|
|
266
|
+
return !!a && !!b && (a === b || a.startsWith(`${b}/`) || b.startsWith(`${a}/`));
|
|
267
|
+
};
|
|
268
|
+
/** A recorded related path or scope glob vs a path the prompt names. A plain
|
|
269
|
+
* entry relates both ways by directory nesting; a glob entry matches a path it
|
|
270
|
+
* covers, or a directory that holds its literal prefix (`src/` ~ `src/store/**`). */
|
|
271
|
+
const relatedPathHit = (entry, promptPath) => {
|
|
272
|
+
if (!/[*?]/.test(entry))
|
|
273
|
+
return samePathOrNested(entry, promptPath);
|
|
274
|
+
if (pathMatchesGlob(promptPath, entry))
|
|
275
|
+
return true;
|
|
276
|
+
const literal = trimSlash(entry.split("/").filter((_, i, segs) => !segs.slice(0, i + 1).some((s) => /[*?]/.test(s))).join("/"));
|
|
277
|
+
const dir = trimSlash(promptPath);
|
|
278
|
+
return !!literal && !!dir && (literal === dir || literal.startsWith(`${dir}/`));
|
|
279
|
+
};
|
|
280
|
+
const escapeRegExp = (s) => s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
281
|
+
/** The prompt's prose without path-like tokens (a `/` or a file extension), so a
|
|
282
|
+
* file named `escalations.test.ts` is not a question about escalations. */
|
|
283
|
+
const proseOnly = (prompt) => prompt.split(/\s+/).filter((t) => { const w = t.replace(/[)\],.;:!?'"`]+$/, ""); return !w.includes("/") && !/\.[a-z0-9]{1,8}$/i.test(w); }).join(" ");
|
|
284
|
+
/** The escalations one prompt's task touches. A prompt hook asks the human only
|
|
285
|
+
* about these; every other entry stays exactly as pending — still listed by
|
|
286
|
+
* `hunch escalations`, hunch_escalations and hunch_now, never dropped and
|
|
287
|
+
* never answered for the human. Deterministic explicit anchors only: a
|
|
288
|
+
* prompt that asks about escalations themselves (path tokens aside), an entry
|
|
289
|
+
* id or topic the prompt names, an id the task selection chose, or a recorded
|
|
290
|
+
* anchor path the prompt names (a directory covers the files under it; a glob
|
|
291
|
+
* covers the paths it matches). `relatedFiles` maps an entry id to its anchor
|
|
292
|
+
* paths: a decision's related_files, a policy's scope paths/globs. An entry
|
|
293
|
+
* with no anchor the prompt names stays CLI/MCP-only. */
|
|
294
|
+
export function relevantEscalations(items, scope, relatedFiles) {
|
|
295
|
+
const prompt = scope.prompt ?? "";
|
|
296
|
+
if (/(?:^|[^a-z0-9-])escalations?(?:$|[^a-z0-9])/i.test(proseOnly(prompt)))
|
|
297
|
+
return [...items];
|
|
298
|
+
const lower = prompt.toLowerCase();
|
|
299
|
+
const named = (term) => {
|
|
300
|
+
const t = term.trim().toLowerCase();
|
|
301
|
+
if (t.length < 3)
|
|
302
|
+
return false;
|
|
303
|
+
return new RegExp(`(?:^|[^a-z0-9_-])${escapeRegExp(t)}(?:$|[^a-z0-9_-])`).test(lower);
|
|
304
|
+
};
|
|
305
|
+
return items.filter((e) => named(e.topic)
|
|
306
|
+
|| e.decisionIds.some((id) => named(id) || scope.selectedIds?.has(id))
|
|
307
|
+
|| e.decisionIds.some((id) => relatedFiles(id).some((file) => scope.paths.some((p) => relatedPathHit(file, p)))));
|
|
308
|
+
}
|
|
262
309
|
//# sourceMappingURL=escalations.js.map
|
package/dist/core/hookText.js
CHANGED
|
@@ -4,5 +4,5 @@
|
|
|
4
4
|
export const HOOK_REMINDER = "Hunch (engineering memory) is available for this repo. Before editing, call " +
|
|
5
5
|
"hunch_check_constraints(scope) for do-not-break invariants and hunch_why(target) " +
|
|
6
6
|
"for the rationale; use hunch_get_dependents for blast radius and hunch_bug_lineage " +
|
|
7
|
-
"for prior root causes.
|
|
7
|
+
"for prior root causes. Record only what the human decides or corrects.";
|
|
8
8
|
//# sourceMappingURL=hookText.js.map
|
|
@@ -98,7 +98,7 @@ export function renderHunchSection(store, root) {
|
|
|
98
98
|
lines.push("This repo has **Hunch**, a graph of *why* the code is the way it is. It holds " +
|
|
99
99
|
`${renderCountsMatch(counts)}. Use the \`hunch_*\` MCP tools by moment:`);
|
|
100
100
|
lines.push("");
|
|
101
|
-
lines.push("- **Start:** reuse the task ID and `task verify` command the prompt hook printed; with none, call `hunch_task(action: \"start\", title)` once. Then `hunch_context(target, task_id)` first. Orient with `hunch_structure`, `hunch_workspaces`, `hunch_runbook(task)`.
|
|
101
|
+
lines.push("- **Start:** reuse the task ID and `task verify` command the prompt hook printed; with none, call `hunch_task(action: \"start\", title)` once. Then `hunch_context(target, task_id)` first. Orient with `hunch_structure`, `hunch_workspaces`, `hunch_runbook(task)`. When the prompt hook raises an escalation this task touches, ask the user; silence is never approval. `hunch_escalations()` lists the rest.");
|
|
102
102
|
lines.push("- **Design:** `hunch_why(target)` (includes what was rejected), `hunch_current_decision(topic)`, `hunch_bug_lineage(symptom_or_symbol)`, `hunch_compare(candidates)`, `hunch_query(query)`.");
|
|
103
103
|
lines.push("- **Edit:** `hunch_check_constraints(scope)`, `hunch_get_dependents(symbol)` / `hunch_blast_radius(target)`, `hunch_findings(scope?)`.");
|
|
104
104
|
lines.push("- **Merge:** `hunch_conformance()`, `hunch_pr_impact(base?)`, `hunch_merge_verdict`." +
|
|
@@ -39,6 +39,22 @@ export interface ManagedGitignoreUpgrade {
|
|
|
39
39
|
export declare function upgradeManagedGitignore(root: string): ManagedGitignoreUpgrade;
|
|
40
40
|
/** Output lines describing what an upgrade changed; empty when nothing did. */
|
|
41
41
|
export declare function describeGitignoreUpgrade(upgrade: ManagedGitignoreUpgrade): string[];
|
|
42
|
+
export interface MemoryIgnoreRule {
|
|
43
|
+
/** Ignore file that holds the rule, as git reports it (e.g. `.gitignore`). */
|
|
44
|
+
source: string;
|
|
45
|
+
/** 1-based line of the rule in `source`. */
|
|
46
|
+
line: number;
|
|
47
|
+
pattern: string;
|
|
48
|
+
}
|
|
49
|
+
/** The user's ignore rule that keeps engineering memory out of Git, or null.
|
|
50
|
+
* A repository that ignores `.hunch/` (or `.hunch/decisions/`) silently loses every
|
|
51
|
+
* capture: the records are written, never committed, and gone on a fresh clone.
|
|
52
|
+
* Hunch's own private-only block ignores the same paths on purpose (memory lives
|
|
53
|
+
* in an overlay), so a rule inside that block is not reported. Unknown git state
|
|
54
|
+
* yields null: the caller then says nothing rather than guessing. */
|
|
55
|
+
export declare function memoryIgnoreRule(root: string): MemoryIgnoreRule | null;
|
|
56
|
+
/** One line telling the user their memory will not reach Git, and how to fix it. */
|
|
57
|
+
export declare function describeMemoryIgnoreRule(rule: MemoryIgnoreRule): string;
|
|
42
58
|
/** `.gitignore` text with both managed Hunch blocks removed (markers inclusive),
|
|
43
59
|
* every other byte kept, for `hunch uninstall --purge` (#449). `damaged` when a
|
|
44
60
|
* marker line has no matching end line: the block is never guessed at. */
|