kenaz 0.3.17 → 0.3.19
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/core/.claude/commands/agent-worker.md +1 -1
- package/core/.claude/commands/architect.md +1 -2
- package/core/.claude/commands/spec.md +1 -1
- package/core/.claude/commands//347/224/242/345/223/201.md +1 -1
- package/core/.claude/hooks/__tests__/task-1977-dedupe-guard.test.js +8 -8
- package/core/.claude/hooks/synapse-first-investigation.js +15 -0
- package/core/.claude/skills/architect_agent/SKILL.md +11 -92
- package/core/.claude/skills/assessment_agent/SKILL.md +0 -1
- package/core/.claude/skills/backend_engineer/SKILL.md +3 -6
- package/core/.claude/skills/code_reviewer/SKILL.md +1 -1
- package/core/.claude/skills/code_reviewer/patterns.yaml +1 -1
- package/core/.claude/skills/designer/SKILL.md +3 -6
- package/core/.claude/skills/dev_agent/SKILL.md +30 -491
- package/core/.claude/skills/developer/SKILL.md +3 -6
- package/core/.claude/skills/feature_agent/SKILL.md +3 -17
- package/core/.claude/skills/product_manager/SKILL.md +29 -1064
- package/core/.claude/skills/qa_engineer/SKILL.md +12 -17
- package/core/.claude/skills/spec_optimizer/SKILL.md +25 -275
- package/core/.claude/skills/sre_agent/SKILL.md +3 -6
- package/core/.collaboration/README.md +2 -3
- package/core/.collaboration/core/task-api.js +5 -138
- package/core/.collaboration/docs/CLI.md +0 -2
- package/core/.failure/INDEX.yaml +6 -40
- package/core/.failure/agent-execution/FAIL-013-search-cwd-drift-and-case-mismatch.yaml +1 -1
- package/core/.failure/tools/README.md +9 -227
- package/core/.formation/bifrost/exec.js +29 -4
- package/core/.formation/bifrost/launch.js +1 -6
- package/core/.formation/bifrost/launch.test.js +1 -1
- package/core/.formation/bifrost/provider-launch.js +18 -11
- package/core/.formation/bifrost/provider-launch.test.js +26 -2
- package/core/.formation/bifrost/shared/ansuz-boot-policy.js +3 -3
- package/core/.formation/bifrost/shared/ansuz-boot-policy.test.js +1 -2
- package/core/.formation/tools/__tests__/dispatch-cwd-project.test.js +42 -0
- package/core/.formation/tools/__tests__/m1-hmap-convid.test.js +1 -124
- package/core/.formation/tools/__tests__/subagent-executor.test.js +3 -2
- package/core/.formation/tools/dispatch.js +15 -2
- package/core/.formation/tools/loader.js +17 -140
- package/core/.formation/tools/post-complete.js +0 -31
- package/core/.rule/INDEX.yaml +0 -18
- package/core/.rule/general/agent-workflow.yaml +14 -164
- package/core/.rule/general/project-self-containment.yaml +8 -19
- package/core/.rule/general/rust-verify-target.yaml +6 -5
- package/core/.rule/general/synapse-code-intelligence.yaml +3 -3
- package/core/.specialist/Amelia/capability.yaml +12 -12
- package/core/.specialist/Amelia/workflow.yaml +2 -2
- package/core/.specialist/Ansuz/SYSTEM_DOC.md +20 -578
- package/core/.specialist/Ansuz/cognitive_principles.yaml +2 -3
- package/core/.specialist/Ansuz/concepts/boot_rules_summary.yaml +0 -1
- package/core/.specialist/Ansuz/concepts/deep_knowledge_pointers.yaml +1 -2
- package/core/.specialist/Ansuz/core.yaml +6 -108
- package/core/.specialist/Ansuz/guards/lesson_guards.yaml +0 -3
- package/core/.specialist/Ansuz/identity_contract.yaml +0 -1
- package/core/.specialist/Ansuz/knowledge.yaml +0 -4
- package/core/.specialist/Ansuz/rule.yaml +0 -1
- package/core/.specialist/Ansuz/tools/handoff-session-identity.test.js +6 -150
- package/core/.specialist/tests/runtime-discovery/README.md +1 -1
- package/core/.specialist/tools/__tests__/task-2374-usage-counter.test.js +1 -12
- package/core/.specialist/tools/prompt-files.test.js +1 -0
- package/core/.specialist/tools/task-bridge.js +1 -66
- package/core/.system/README.md +11 -552
- package/core/.system/collaboration/README.md +10 -174
- package/core/CLAUDE.md +1 -6
- package/core/KENAZ_CORE_VERSION +1 -1
- package/core/dev/scripts/js-yaml-resolution.test.js +1 -1
- package/core/dev/scripts/run-rust-tests.js +30 -46
- package/core/dev/scripts/run-rust-tests.test.js +17 -0
- package/core/dev/scripts/rust-verify-worktree.js +4 -4
- package/core/dev/scripts/verify-target-dir.js +6 -6
- package/core/plugins/kenaz/INVENTORY.md +8 -8
- package/core/plugins/kenaz/commands/architect.md +1 -2
- package/core/plugins/kenaz/commands/spec.md +1 -1
- package/core/plugins/kenaz/commands//347/224/242/345/223/201.md +1 -1
- package/core/plugins/kenaz/guidance.md +1 -1
- package/core/plugins/kenaz/scripts/sync.js +1 -1
- package/core/plugins/kenaz/scripts/ygg.js +6 -0
- package/core/plugins/kenaz/skills/architect_agent/SKILL.md +11 -92
- package/core/plugins/kenaz/skills/assessment_agent/SKILL.md +0 -1
- package/core/plugins/kenaz/skills/backend_engineer/SKILL.md +3 -6
- package/core/plugins/kenaz/skills/code_reviewer/SKILL.md +1 -1
- package/core/plugins/kenaz/skills/code_reviewer/patterns.yaml +1 -1
- package/core/plugins/kenaz/skills/designer/SKILL.md +3 -6
- package/core/plugins/kenaz/skills/dev_agent/SKILL.md +30 -491
- package/core/plugins/kenaz/skills/developer/SKILL.md +3 -6
- package/core/plugins/kenaz/skills/feature_agent/SKILL.md +3 -17
- package/core/plugins/kenaz/skills/product_manager/SKILL.md +29 -1064
- package/core/plugins/kenaz/skills/qa_engineer/SKILL.md +12 -17
- package/core/plugins/kenaz/skills/spec_optimizer/SKILL.md +25 -275
- package/core/plugins/kenaz/skills/sre_agent/SKILL.md +3 -6
- package/package.json +6 -6
- package/core/.claude/commands/archive.md +0 -24
- package/core/.claude/commands/init.md +0 -112
- package/core/.claude/commands/legacy_dev.md +0 -18
- package/core/.claude/commands/product-review.md +0 -131
- package/core/.claude/commands/strategy.md +0 -141
- package/core/.claude/commands/sunny.md +0 -1
- package/core/.claude/commands//347/224/242/345/223/201/346/252/242/350/246/226.md +0 -129
- package/core/.claude/commands//351/200/262/345/272/246.md +0 -15
- package/core/.claude/hooks/__tests__/substrate.test.js +0 -122
- package/core/.claude/hooks/_substrate-paths.js +0 -95
- package/core/.claude/hooks/context-check.js +0 -51
- package/core/.claude/hooks/fatigue-check.js +0 -52
- package/core/.claude/hooks/handoff-snapshot-on-commit.js +0 -154
- package/core/.claude/hooks/handoff-snapshot-on-question.js +0 -158
- package/core/.claude/hooks/handoff-staleness.js +0 -197
- package/core/.claude/hooks/jev-thalamus-context.js +0 -17
- package/core/.claude/hooks/jev-thalamus-context.test.js +0 -41
- package/core/.claude/hooks/lease-release.js +0 -119
- package/core/.claude/hooks/lease-touch.js +0 -96
- package/core/.claude/hooks/reflex-feedback.js +0 -5
- package/core/.claude/hooks/session-init.ps1 +0 -49
- package/core/.claude/hooks/sound-event.js +0 -70
- package/core/.claude/hooks/sound-event.sh +0 -37
- package/core/.claude/hooks/stream-delta.js +0 -169
- package/core/.claude/no-hooks.settings.json +0 -3
- package/core/.claude/skills/legacy_dev/SKILL.md +0 -202
- package/core/.claude/skills/sunny/SKILL.md +0 -74
- package/core/.collaboration/cli/commands/assign.js +0 -39
- package/core/.collaboration/cli/commands/claim.js +0 -33
- package/core/.collaboration/cli/commands/complete.js +0 -44
- package/core/.collaboration/cli/commands/create.js +0 -110
- package/core/.collaboration/cli/commands/get.js +0 -83
- package/core/.collaboration/cli/commands/index.js +0 -52
- package/core/.collaboration/cli/commands/list.js +0 -51
- package/core/.collaboration/cli/commands/lock.js +0 -45
- package/core/.collaboration/cli/commands/rebuild.js +0 -39
- package/core/.collaboration/cli/commands/search.js +0 -61
- package/core/.collaboration/cli/commands/stats.js +0 -61
- package/core/.collaboration/cli/commands/transfer.js +0 -41
- package/core/.collaboration/cli/commands/unlock.js +0 -48
- package/core/.collaboration/cli/formatters/help-formatter.js +0 -70
- package/core/.collaboration/cli/formatters/index.js +0 -7
- package/core/.collaboration/cli/task-cli.js +0 -46
- package/core/.collaboration/cli/utils/arg-parser.js +0 -74
- package/core/.collaboration/cli/utils/git-user.js +0 -25
- package/core/.collaboration/cli/utils/index.js +0 -9
- package/core/.collaboration/cli/utils/output.js +0 -10
- package/core/.collaboration/config/default.yaml +0 -189
- package/core/.collaboration/core/conflict-resolver.js +0 -461
- package/core/.collaboration/core/conflict-resolver.test.js +0 -389
- package/core/.collaboration/core/migrate-legacy.js +0 -260
- package/core/.collaboration/docs/API.md +0 -787
- package/core/.collaboration/docs/ARCHITECTURE.md +0 -596
- package/core/.collaboration/docs/FAQ.md +0 -695
- package/core/.collaboration/docs/MIGRATION.md +0 -804
- package/core/.collaboration/docs/examples/README.md +0 -320
- package/core/.collaboration/docs/examples/advanced-queries.js +0 -261
- package/core/.collaboration/docs/examples/basic-usage.js +0 -184
- package/core/.collaboration/docs/examples/team-workflow.js +0 -352
- package/core/.failure/agent-execution/FAIL-001-agent-no-output.yaml +0 -391
- package/core/.failure/agent-execution/FAIL-007-pm-lead-wrong-team-invocation.yaml +0 -0
- package/core/.failure/agent-execution/FAIL-010-phase0-poem-format-skipped.yaml +0 -28
- package/core/.failure/agent-execution/FAIL-010-skipping-cognitive-checkpoint-after-verification.yaml +0 -59
- package/core/.failure/agent-execution/FAIL-012-grep-shortcut-during-investigation.yaml +0 -85
- package/core/.failure/architecture/FAIL-006-python-version-compatibility.yaml +0 -445
- package/core/.failure/pipeline/FAIL-003-queue-pollution.yaml +0 -335
- package/core/.failure/pipeline/README.md +0 -95
- package/core/.failure/retired/FAIL-009-skipping-synapse-pulse-before-code-change.yaml +0 -49
- package/core/.failure/tools/USAGE_GUIDE.md +0 -323
- package/core/.failure/tools/check_failures.ps1 +0 -387
- package/core/.failure/tools/log_failure.ps1 +0 -474
- package/core/.formation/.episode-input.json +0 -10
- package/core/.formation/.merge-lock +0 -1
- package/core/.formation/lead.yaml +0 -118
- package/core/.formation/legacy.bak/designer.yaml +0 -57
- package/core/.formation/legacy.bak/pm.yaml +0 -57
- package/core/.formation/legacy.bak/qa.yaml +0 -57
- package/core/.formation/legacy.bak/review.yaml +0 -62
- package/core/.formation/legacy.bak/scheduler.yaml +0 -129
- package/core/.formation/legacy.bak/sre.yaml +0 -57
- package/core/.formation/role-map.yaml +0 -274
- package/core/.formation/roles/_shared/modules/code_review.yaml +0 -98
- package/core/.formation/roles/_shared/modules/security.yaml +0 -103
- package/core/.formation/roles/_shared/modules/testing.yaml +0 -14
- package/core/.formation/roles/backend/role.yaml +0 -93
- package/core/.formation/roles/code_reviewer/role.yaml +0 -102
- package/core/.formation/roles/database_optimizer/role.yaml +0 -107
- package/core/.formation/roles/designer/role.yaml +0 -66
- package/core/.formation/roles/developer/role.yaml +0 -75
- package/core/.formation/roles/frontend/role.yaml +0 -134
- package/core/.formation/roles/incident_response/role.yaml +0 -89
- package/core/.formation/roles/qa/role.yaml +0 -89
- package/core/.formation/roles/security/role.yaml +0 -87
- package/core/.formation/roles/sre/role.yaml +0 -66
- package/core/.formation/skills/coordinator.yaml +0 -52
- package/core/.formation/solo/diagnosis.yaml +0 -78
- package/core/.formation/solo/strategy.yaml +0 -121
- package/core/.formation/tests/hello.txt +0 -2
- package/core/.formation/tools/coordinator.js +0 -350
- package/core/.formation/tools/jev-correction.js +0 -214
- package/core/.formation/tools/jev-correction.test.js +0 -178
- package/core/.formation/tools/log-reader.js +0 -232
- package/core/.formation/tools/revert-daemon-state.js +0 -162
- package/core/.formation/tools/test-drift.js +0 -148
- package/core/.formation/unit/heimdall.yaml +0 -96
- package/core/.formation/unit/plan.yaml +0 -67
- package/core/.formation/unit/think.yaml +0 -58
- package/core/.kenaz/MUNINN_SNAPSHOT9_EPISODE.json +0 -32
- package/core/.kenaz/MUNINN_SNAPSHOT9_RUNE.json +0 -23
- package/core/.kenaz/MUNINN_SNAPSHOT9_SUMMARY.md +0 -89
- package/core/.kenaz/ash_TASK_1428-20260507.json +0 -14
- package/core/.kenaz/muninn_episode_write.ps1 +0 -17
- package/core/.kenaz/muninn_snapshot11_rune_candidates.md +0 -63
- package/core/.kenaz/muninn_snapshot1_rune_candidates.md +0 -49
- package/core/.kenaz/runes_TASK_1428.json +0 -57
- package/core/.kenaz/tmp_heimdall_proposal_8d3e392.json +0 -13
- package/core/.kenaz/tmp_props.json +0 -1
- package/core/.kenaz/tmp_sounddrop_t1_prompt.txt +0 -371
- package/core/.rule/MECHANIZED.md +0 -61
- package/core/.rule/general/cortex-memory-recall.yaml +0 -446
- package/core/.rule/general/pipeline.yaml +0 -246
- package/core/.rule/general/sunny-access-control.yaml +0 -60
- package/core/.rule/general/system-invariants.yaml +0 -231
- package/core/.rule/pipeline/PPL-002-completed-task-archival.yaml +0 -528
- package/core/.rule/pipeline/auto-update.yaml +0 -282
- package/core/.rule/pipeline/task-execution.yaml +0 -365
- package/core/.specialist/Ansuz/concepts/phase_protocol.yaml +0 -65
- package/core/.specialist/Ansuz/experiments/boot_ablation_replay.js +0 -197
- package/core/.specialist/Ansuz/tools/bootstrap-anchors.js +0 -289
- package/core/.specialist/Ansuz/workflow.yaml +0 -1225
- package/core/.specialist/experiments/rg_benchmark.js +0 -523
- package/core/.specialist/experiments/rg_benchmark_results.json +0 -27
- package/core/.specialist/tools/jev-optional-context.js +0 -107
- package/core/.specialist/tools/jev-optional-context.test.js +0 -152
- package/core/.specialist/tools/memory-action-log.js +0 -839
- package/core/.specialist/tools/phase-gate.js +0 -3534
- package/core/.specialist/tools/phase-gate.test.js +0 -85
- package/core/.specialist/tools/precompute-runner.js +0 -444
- package/core/.specialist/tools/rule-engine.js +0 -621
- package/core/.specialist/tools/task-bridge-jev.test.js +0 -170
- package/core/.synapse/memory-shared.json.sync +0 -1
- package/core/.system/agent-skills/README.md +0 -724
- package/core/.system/cli-tools/README.md +0 -724
- package/core/.system/cortex/MODULES.md +0 -1046
- package/core/.system/dashboard/FRONTEND.md +0 -812
- package/core/.system/dashboard/README.md +0 -737
- package/core/.system/failure/README.md +0 -723
- package/core/.system/formation/LEGACY_SKILLS.md +0 -974
- package/core/.system/formation/POST_COMPLETE.md +0 -602
- package/core/.system/formation/README.md +0 -1094
- package/core/.system/formation/TOOLS_REFERENCE.md +0 -1145
- package/core/.system/kenaz/README.md +0 -787
- package/core/.system/memory/README.md +0 -719
- package/core/.system/pipeline/README.md +0 -712
- package/core/.system/rule/README.md +0 -794
- package/core/.system/sunny/README.md +0 -476
- package/core/.system/sunny/RULES.json +0 -89
- package/core/.system/sunny/knowledge/INDEX.md +0 -37
- package/core/.system/sunny/knowledge/agent-teams-api.md +0 -0
- package/core/.system/sunny/knowledge/ansuz-specialist.md +0 -500
- package/core/.system/sunny/knowledge/claude-cli-spawn.md +0 -177
- package/core/.system/sunny/knowledge/collaboration-api.md +0 -925
- package/core/.system/sunny/teammates/README.md +0 -107
- package/core/.system/sunny/teammates/how_to_use.yaml +0 -101
- package/core/.system/sunny/teammates/tools/headless/launcher.js +0 -318
- package/core/.system/sunny/teammates/tools/powershell/launcher.js +0 -241
- package/core/.system/sunny/teammates/tools/powershell/start.bat +0 -50
- package/core/.system/sunny/teammates/tools/utils/env.js +0 -96
- package/core/.system/sunny/teammates/tools/utils/format-output.js +0 -329
- package/core/.system/sunny/tools.json +0 -101
- package/core/dev/scripts/cleanup-queue.js +0 -184
- package/core/dev/scripts/cleanup_queue.js +0 -86
- package/core/dev/scripts/legacy/README.md +0 -39
- package/core/dev/scripts/legacy/dev_agent_scheduler.ps1.bak +0 -536
- package/core/dev/scripts/legacy/multi_agent_scheduler.ps1.bak +0 -1978
- package/core/dev/scripts/matrix/CLI_README.md +0 -214
- package/core/dev/scripts/matrix/README.md +0 -310
- package/core/dev/scripts/matrix/package-lock.json +0 -4266
- package/core/dev/scripts/matrix/package.json +0 -55
- package/core/dev/scripts/matrix/src/abbreviations.ts +0 -295
- package/core/dev/scripts/matrix/src/cli/commands/convert.ts +0 -211
- package/core/dev/scripts/matrix/src/cli/commands/create.ts +0 -95
- package/core/dev/scripts/matrix/src/cli/commands/delete.ts +0 -102
- package/core/dev/scripts/matrix/src/cli/commands/list-tags.ts.wip +0 -140
- package/core/dev/scripts/matrix/src/cli/commands/manage-tags.ts.wip +0 -173
- package/core/dev/scripts/matrix/src/cli/commands/query.ts +0 -261
- package/core/dev/scripts/matrix/src/cli/commands/read.ts +0 -77
- package/core/dev/scripts/matrix/src/cli/commands/stats.ts.wip +0 -168
- package/core/dev/scripts/matrix/src/cli/commands/tokens.ts.wip +0 -126
- package/core/dev/scripts/matrix/src/cli/commands/update.ts +0 -147
- package/core/dev/scripts/matrix/src/cli/commands/validate.ts +0 -275
- package/core/dev/scripts/matrix/src/cli/index.ts +0 -350
- package/core/dev/scripts/matrix/src/cli/utils/file-handler.ts +0 -72
- package/core/dev/scripts/matrix/src/cli/utils/logger.ts +0 -65
- package/core/dev/scripts/matrix/src/index.ts +0 -62
- package/core/dev/scripts/matrix/src/indexer/tag-indexer.ts +0 -165
- package/core/dev/scripts/matrix/src/parser.ts +0 -281
- package/core/dev/scripts/matrix/src/serializer.ts +0 -151
- package/core/dev/scripts/matrix/src/validator.ts +0 -301
- package/core/dev/scripts/matrix/tests/abbreviations.test.ts +0 -122
- package/core/dev/scripts/matrix/tests/cli.test.ts +0 -177
- package/core/dev/scripts/matrix/tests/convert.test.ts +0 -143
- package/core/dev/scripts/matrix/tests/parser.test.ts +0 -203
- package/core/dev/scripts/matrix/tests/query.test.ts +0 -85
- package/core/dev/scripts/matrix/tests/serializer.test.ts +0 -197
- package/core/dev/scripts/matrix/tests/validate.test.ts +0 -141
- package/core/dev/scripts/matrix/tests/validator.test.ts +0 -167
- package/core/dev/scripts/matrix/tsconfig.json +0 -20
- package/core/dev/scripts/matrix/types/index.d.ts +0 -111
- package/core/dev/scripts/matrix/types/index.d.ts.map +0 -1
- package/core/dev/scripts/matrix/types/index.js +0 -8
- package/core/dev/scripts/matrix/types/index.js.map +0 -1
- package/core/dev/scripts/matrix/types/index.ts +0 -124
- package/core/dev/scripts/memory-usage-report.js +0 -152
- package/core/dev/scripts/probe/ansuz-scaffold-ablation.js +0 -579
- package/core/dev/scripts/probe/ansuz-scaffold-ablation.test.js +0 -244
- package/core/dev/scripts/probe/hook-latency-probe.js +0 -315
- package/core/dev/scripts/probe/hook-latency-probe.test.js +0 -105
- package/core/dev/scripts/probe/prediction-signal-auc.js +0 -483
- package/core/dev/scripts/probe/prediction-signal-auc.test.js +0 -200
- package/core/dev/scripts/test-pty-ws-tunnel.js +0 -229
- package/core/dev/scripts/validate_pipeline.js +0 -197
- package/core/dev/spikes/task2145-evidence-20261004/backend-log-excerpt.txt +0 -56
- package/core/dev/spikes/task2145-evidence-20261004/graph.shadow +0 -0
- package/core/dev/spikes/task2145-evidence-20261004/graph.wal +0 -0
- package/core/dev/spikes/task2145-evidence-20261004/graph_corrupt_suspect.marker +0 -1
- package/core/plugins/kenaz/commands/strategy.md +0 -141
|
@@ -1,51 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
/**
|
|
3
|
-
* Context Check Hook — UserPromptSubmit (TASK_943 + TASK_947)
|
|
4
|
-
* Two checks in one hook:
|
|
5
|
-
* 1. (removed, TASK_1987) Fatigue Warning.
|
|
6
|
-
* 2. Task Completed Signal: injects task chain notifications from ansuz_inbox.json.
|
|
7
|
-
* Cross-platform. Silent no-op on any missing file or parse error.
|
|
8
|
-
*/
|
|
9
|
-
'use strict';
|
|
10
|
-
const kenazProjectRoot = require('./_project-guard').guard();
|
|
11
|
-
const fs = require('fs');
|
|
12
|
-
const path = require('path');
|
|
13
|
-
const { fenceSignal } = require('./_signal-fence');
|
|
14
|
-
const projectDir = kenazProjectRoot;
|
|
15
|
-
|
|
16
|
-
// ── 1. Fatigue Warning (TASK_943) — REMOVED by TASK_1987 (2026-09-21).
|
|
17
|
-
// The watcher that fed it is retired (TASK_2345) and the user had rejected the
|
|
18
|
-
// fatigue footer (memory: feedback_no_fatigue_footer).
|
|
19
|
-
|
|
20
|
-
// ── 2. Task Completed Signal (TASK_947) ────────────────────────────────────
|
|
21
|
-
try {
|
|
22
|
-
const inboxFile = path.join(projectDir, '.kenaz', 'specialist', 'ansuz_inbox.json');
|
|
23
|
-
if (!fs.existsSync(inboxFile)) process.exit(0);
|
|
24
|
-
|
|
25
|
-
const inbox = JSON.parse(fs.readFileSync(inboxFile, 'utf8'));
|
|
26
|
-
if (!inbox || !Array.isArray(inbox.messages)) process.exit(0);
|
|
27
|
-
|
|
28
|
-
const processedIds = Array.isArray(inbox.processed_ids) ? inbox.processed_ids : [];
|
|
29
|
-
|
|
30
|
-
// Filter: type=task_completed, not yet processed, oldest first, max 3
|
|
31
|
-
const pending = inbox.messages
|
|
32
|
-
.filter(m => m && m.type === 'task_completed' && m.id && !processedIds.includes(m.id))
|
|
33
|
-
.sort((a, b) => (a.completed_at || '').localeCompare(b.completed_at || ''))
|
|
34
|
-
.slice(0, 3);
|
|
35
|
-
|
|
36
|
-
if (pending.length === 0) process.exit(0);
|
|
37
|
-
|
|
38
|
-
// Output one line per completed task
|
|
39
|
-
let rawTaskOutput = '';
|
|
40
|
-
for (const msg of pending) {
|
|
41
|
-
rawTaskOutput += '\n\u26a1 Task completed: ' + msg.task_id + ' \u2014 ' + msg.task_title + '. Chain follow-up may be needed.';
|
|
42
|
-
}
|
|
43
|
-
process.stdout.write(fenceSignal('task', rawTaskOutput.trimStart()) + '\n');
|
|
44
|
-
|
|
45
|
-
// Mark as processed (update processed_ids)
|
|
46
|
-
const newIds = pending.map(m => m.id);
|
|
47
|
-
inbox.processed_ids = [...processedIds, ...newIds];
|
|
48
|
-
try {
|
|
49
|
-
fs.writeFileSync(inboxFile, JSON.stringify(inbox, null, 2), 'utf8');
|
|
50
|
-
} catch { /* best-effort write — silent on error */ }
|
|
51
|
-
} catch { /* graceful no-op */ }
|
|
@@ -1,52 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
/**
|
|
3
|
-
* Context Check Hook — UserPromptSubmit (TASK_943 + TASK_947)
|
|
4
|
-
* Two checks in one hook:
|
|
5
|
-
* 1. (removed, TASK_1987) Fatigue Warning.
|
|
6
|
-
* 2. Task Completed Signal: injects task chain notifications from ansuz_inbox.json.
|
|
7
|
-
* Cross-platform. Silent no-op on any missing file or parse error.
|
|
8
|
-
*/
|
|
9
|
-
'use strict';
|
|
10
|
-
try { require('./_dedupe-guard').guard(typeof __filename === 'string' ? __filename : null); } catch (_) { /* never block a hook on guard load failure (e.g. vm sandbox without __filename) */ }
|
|
11
|
-
const kenazProjectRoot = require('./_project-guard').guard();
|
|
12
|
-
const fs = require('fs');
|
|
13
|
-
const path = require('path');
|
|
14
|
-
const { fenceSignal } = require('./_signal-fence');
|
|
15
|
-
const projectDir = kenazProjectRoot;
|
|
16
|
-
|
|
17
|
-
// ── 1. Fatigue Warning (TASK_943) — REMOVED by TASK_1987 (2026-09-21).
|
|
18
|
-
// The watcher that fed it is retired (TASK_2345) and the user had rejected the
|
|
19
|
-
// fatigue footer (memory: feedback_no_fatigue_footer).
|
|
20
|
-
|
|
21
|
-
// ── 2. Task Completed Signal (TASK_947) ────────────────────────────────────
|
|
22
|
-
try {
|
|
23
|
-
const inboxFile = path.join(projectDir, '.kenaz', 'specialist', 'ansuz_inbox.json');
|
|
24
|
-
if (!fs.existsSync(inboxFile)) process.exit(0);
|
|
25
|
-
|
|
26
|
-
const inbox = JSON.parse(fs.readFileSync(inboxFile, 'utf8'));
|
|
27
|
-
if (!inbox || !Array.isArray(inbox.messages)) process.exit(0);
|
|
28
|
-
|
|
29
|
-
const processedIds = Array.isArray(inbox.processed_ids) ? inbox.processed_ids : [];
|
|
30
|
-
|
|
31
|
-
// Filter: type=task_completed, not yet processed, oldest first, max 3
|
|
32
|
-
const pending = inbox.messages
|
|
33
|
-
.filter(m => m && m.type === 'task_completed' && m.id && !processedIds.includes(m.id))
|
|
34
|
-
.sort((a, b) => (a.completed_at || '').localeCompare(b.completed_at || ''))
|
|
35
|
-
.slice(0, 3);
|
|
36
|
-
|
|
37
|
-
if (pending.length === 0) process.exit(0);
|
|
38
|
-
|
|
39
|
-
// Output one line per completed task
|
|
40
|
-
let rawTaskOutput = '';
|
|
41
|
-
for (const msg of pending) {
|
|
42
|
-
rawTaskOutput += '\n\u26a1 Task completed: ' + msg.task_id + ' \u2014 ' + msg.task_title + '. Chain follow-up may be needed.';
|
|
43
|
-
}
|
|
44
|
-
process.stdout.write(fenceSignal('task', rawTaskOutput.trimStart()) + '\n');
|
|
45
|
-
|
|
46
|
-
// Mark as processed (update processed_ids)
|
|
47
|
-
const newIds = pending.map(m => m.id);
|
|
48
|
-
inbox.processed_ids = [...processedIds, ...newIds];
|
|
49
|
-
try {
|
|
50
|
-
fs.writeFileSync(inboxFile, JSON.stringify(inbox, null, 2), 'utf8');
|
|
51
|
-
} catch { /* best-effort write — silent on error */ }
|
|
52
|
-
} catch { /* graceful no-op */ }
|
|
@@ -1,154 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
/**
|
|
3
|
-
* handoff-snapshot-on-commit.js — PostToolUse hook for Bash (TASK_1400)
|
|
4
|
-
*
|
|
5
|
-
* Fires after any Bash tool call that looks like a git commit.
|
|
6
|
-
* Emits a one-line nudge to run ansuz_handoff_snapshot with the commit hash.
|
|
7
|
-
* 5-minute dedup prevents nudge fatigue under rapid commit sequences.
|
|
8
|
-
*
|
|
9
|
-
* Matcher: Bash (all Bash calls — filtered internally by command pattern)
|
|
10
|
-
*/
|
|
11
|
-
'use strict';
|
|
12
|
-
try { require('./_dedupe-guard').guard(typeof __filename === 'string' ? __filename : null); } catch (_) { /* never block a hook on guard load failure (e.g. vm sandbox without __filename) */ }
|
|
13
|
-
const kenazProjectRoot = require('./_project-guard').guard();
|
|
14
|
-
|
|
15
|
-
const fs = require('fs');
|
|
16
|
-
const path = require('path');
|
|
17
|
-
const { fenceSignal } = require('./_signal-fence');
|
|
18
|
-
|
|
19
|
-
const PROJECT_DIR = kenazProjectRoot;
|
|
20
|
-
const TELEMETRY_PATH = path.join(
|
|
21
|
-
PROJECT_DIR,
|
|
22
|
-
'.kenaz', 'specialist', 'handoff_nudges.jsonl'
|
|
23
|
-
);
|
|
24
|
-
const DEDUP_WINDOW_MS = 5 * 60 * 1000;
|
|
25
|
-
const DEDUP_KEY_PREFIX = 'commit:';
|
|
26
|
-
|
|
27
|
-
function readStdin() {
|
|
28
|
-
try {
|
|
29
|
-
return JSON.parse(fs.readFileSync(0, 'utf8').trim() || '{}');
|
|
30
|
-
} catch {
|
|
31
|
-
return {};
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
function extractCommand(payload) {
|
|
36
|
-
return (
|
|
37
|
-
payload?.tool_input?.command ||
|
|
38
|
-
payload?.toolInput?.command ||
|
|
39
|
-
payload?.command ||
|
|
40
|
-
''
|
|
41
|
-
);
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
function extractShortHash(payload) {
|
|
45
|
-
// Try to extract hash from tool_response output
|
|
46
|
-
try {
|
|
47
|
-
const resp =
|
|
48
|
-
payload?.tool_response?.content?.[0]?.text ||
|
|
49
|
-
payload?.tool_result?.content?.[0]?.text ||
|
|
50
|
-
'';
|
|
51
|
-
const m = resp.match(/\b([a-f0-9]{7,12})\b/);
|
|
52
|
-
return m ? m[1] : null;
|
|
53
|
-
} catch {
|
|
54
|
-
return null;
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
function normalizeKey(cmd) {
|
|
59
|
-
// Normalize to prevent minor variation from defeating dedup
|
|
60
|
-
return cmd.replace(/\s+/g, ' ').trim().substring(0, 80);
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
function isDeduped(key) {
|
|
64
|
-
try {
|
|
65
|
-
if (!fs.existsSync(TELEMETRY_PATH)) return false;
|
|
66
|
-
const lines = fs.readFileSync(TELEMETRY_PATH, 'utf8').split(/\r?\n/).filter(Boolean);
|
|
67
|
-
const cutoff = Date.now() - DEDUP_WINDOW_MS;
|
|
68
|
-
for (let i = lines.length - 1; i >= 0; i--) {
|
|
69
|
-
let entry;
|
|
70
|
-
try { entry = JSON.parse(lines[i]); } catch { continue; }
|
|
71
|
-
const ts = Number(entry.timestamp_ms || 0);
|
|
72
|
-
if (ts && ts < cutoff) break;
|
|
73
|
-
if (entry.dedup_key === key && !entry.suppressed) return true;
|
|
74
|
-
}
|
|
75
|
-
return false;
|
|
76
|
-
} catch {
|
|
77
|
-
return false;
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
function appendTelemetry(record) {
|
|
82
|
-
try {
|
|
83
|
-
fs.mkdirSync(path.dirname(TELEMETRY_PATH), { recursive: true });
|
|
84
|
-
fs.appendFileSync(TELEMETRY_PATH, JSON.stringify(record) + '\n', 'utf8');
|
|
85
|
-
} catch { /* best-effort */ }
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
// Auto-invoke the snapshot CLI as a detached process so the hook returns fast
|
|
89
|
-
// and the snapshot write does not depend on the agent remembering. Errors are
|
|
90
|
-
// swallowed — never block the user turn. Mirrors the discipline applied across
|
|
91
|
-
// all 4 handoff hooks (commit, decision, question, post-dispatch).
|
|
92
|
-
function invokeHandoffSnapshot(trigger, milestone, sessionId, transcriptPath) {
|
|
93
|
-
try {
|
|
94
|
-
const { spawn } = require('child_process');
|
|
95
|
-
const cli = path.join(PROJECT_DIR, '.specialist', 'Ansuz', 'tools', 'ansuz-handoff-snapshot.js');
|
|
96
|
-
if (!fs.existsSync(cli)) return;
|
|
97
|
-
const args = [cli, `--trigger=${trigger}`];
|
|
98
|
-
if (milestone) args.push(`--last-milestone=${milestone}`);
|
|
99
|
-
if (sessionId) args.push(`--session-id=${sessionId}`);
|
|
100
|
-
// Scopes pending/unfinished to tasks this conversation touched.
|
|
101
|
-
if (transcriptPath) args.push(`--transcript-path=${transcriptPath}`);
|
|
102
|
-
const child = spawn('node', args, {
|
|
103
|
-
detached: true,
|
|
104
|
-
stdio: 'ignore',
|
|
105
|
-
cwd: PROJECT_DIR,
|
|
106
|
-
windowsHide: true,
|
|
107
|
-
});
|
|
108
|
-
child.unref();
|
|
109
|
-
} catch { /* best-effort */ }
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
function main() {
|
|
113
|
-
const payload = readStdin();
|
|
114
|
-
const cmd = extractCommand(payload);
|
|
115
|
-
|
|
116
|
-
// Only fire on git commit commands
|
|
117
|
-
if (!/\bgit\s+commit\b/.test(cmd)) {
|
|
118
|
-
process.exit(0);
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
// Ansuz-only: a plain session's commit must not write an Ansuz handoff slot
|
|
122
|
-
// (it did, filling the slot with other sessions' backlog as "pending").
|
|
123
|
-
if (!require('./_ansuz-conversation').isAnsuzConversation(payload.transcript_path, PROJECT_DIR)) {
|
|
124
|
-
process.exit(0);
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
const dedupKey = DEDUP_KEY_PREFIX + normalizeKey(cmd);
|
|
128
|
-
const suppressed = isDeduped(dedupKey);
|
|
129
|
-
|
|
130
|
-
appendTelemetry({
|
|
131
|
-
timestamp: new Date().toISOString(),
|
|
132
|
-
timestamp_ms: Date.now(),
|
|
133
|
-
hook: 'handoff-snapshot-on-commit',
|
|
134
|
-
dedup_key: dedupKey,
|
|
135
|
-
suppressed,
|
|
136
|
-
});
|
|
137
|
-
|
|
138
|
-
if (suppressed) {
|
|
139
|
-
process.exit(0);
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
const shortHash = extractShortHash(payload);
|
|
143
|
-
const hashPart = shortHash ? ` (${shortHash})` : '';
|
|
144
|
-
const milestone = `Commit landed${hashPart}`;
|
|
145
|
-
|
|
146
|
-
// Auto-write the handoff (system-level enforcement, instance-independent)
|
|
147
|
-
invokeHandoffSnapshot('live_decision_point', milestone, payload.session_id, payload.transcript_path);
|
|
148
|
-
|
|
149
|
-
process.stdout.write(
|
|
150
|
-
'\n' + fenceSignal('handoff', `🔁 Commit landed${hashPart}. Handoff auto-snapshot dispatched (live_decision_point).`) + '\n'
|
|
151
|
-
);
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
main();
|
|
@@ -1,158 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
/**
|
|
3
|
-
* handoff-snapshot-on-question.js — PostToolUse hook for AskUserQuestion (TASK_1400)
|
|
4
|
-
*
|
|
5
|
-
* Fires after AskUserQuestion resolves (tool_response present with user's answer).
|
|
6
|
-
* Emits a nudge to run ansuz_handoff_snapshot — question resolution is a natural
|
|
7
|
-
* decision point that should be captured in the live handoff.
|
|
8
|
-
* 5-minute dedup keyed on normalized question text.
|
|
9
|
-
*
|
|
10
|
-
* Matcher: AskUserQuestion
|
|
11
|
-
*/
|
|
12
|
-
'use strict';
|
|
13
|
-
try { require('./_dedupe-guard').guard(typeof __filename === 'string' ? __filename : null); } catch (_) { /* never block a hook on guard load failure (e.g. vm sandbox without __filename) */ }
|
|
14
|
-
const kenazProjectRoot = require('./_project-guard').guard();
|
|
15
|
-
|
|
16
|
-
const fs = require('fs');
|
|
17
|
-
const path = require('path');
|
|
18
|
-
const { fenceSignal } = require('./_signal-fence');
|
|
19
|
-
|
|
20
|
-
const PROJECT_DIR = kenazProjectRoot;
|
|
21
|
-
const TELEMETRY_PATH = path.join(
|
|
22
|
-
PROJECT_DIR,
|
|
23
|
-
'.kenaz', 'specialist', 'handoff_nudges.jsonl'
|
|
24
|
-
);
|
|
25
|
-
const DEDUP_WINDOW_MS = 5 * 60 * 1000;
|
|
26
|
-
const DEDUP_KEY_PREFIX = 'question:';
|
|
27
|
-
|
|
28
|
-
function readStdin() {
|
|
29
|
-
try {
|
|
30
|
-
return JSON.parse(fs.readFileSync(0, 'utf8').trim() || '{}');
|
|
31
|
-
} catch {
|
|
32
|
-
return {};
|
|
33
|
-
}
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
function extractQuestion(payload) {
|
|
37
|
-
// AskUserQuestion schema: tool_input.questions = [{question, ...}];
|
|
38
|
-
// legacy single-question payloads carried tool_input.question.
|
|
39
|
-
const input = payload?.tool_input || payload?.toolInput || {};
|
|
40
|
-
if (typeof input.question === 'string' && input.question.trim()) {
|
|
41
|
-
return input.question;
|
|
42
|
-
}
|
|
43
|
-
const questions = Array.isArray(input.questions) ? input.questions : [];
|
|
44
|
-
return questions.map((q) => q?.question).filter(Boolean).join(' | ');
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
function hasResponse(payload) {
|
|
48
|
-
// Check if the user has responded (tool_response present)
|
|
49
|
-
const resp = payload?.tool_response || payload?.tool_result;
|
|
50
|
-
if (!resp) return false;
|
|
51
|
-
// Verify it's not empty
|
|
52
|
-
if (typeof resp === 'string') return resp.trim().length > 0;
|
|
53
|
-
if (typeof resp === 'object') {
|
|
54
|
-
if (resp.content && Array.isArray(resp.content)) {
|
|
55
|
-
return resp.content.some(c => c?.text && c.text.trim().length > 0);
|
|
56
|
-
}
|
|
57
|
-
return true;
|
|
58
|
-
}
|
|
59
|
-
return false;
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
function normalizeQuestion(q) {
|
|
63
|
-
return q.replace(/\s+/g, ' ').trim().substring(0, 80);
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
function isDeduped(key) {
|
|
67
|
-
try {
|
|
68
|
-
if (!fs.existsSync(TELEMETRY_PATH)) return false;
|
|
69
|
-
const lines = fs.readFileSync(TELEMETRY_PATH, 'utf8').split(/\r?\n/).filter(Boolean);
|
|
70
|
-
const cutoff = Date.now() - DEDUP_WINDOW_MS;
|
|
71
|
-
for (let i = lines.length - 1; i >= 0; i--) {
|
|
72
|
-
let entry;
|
|
73
|
-
try { entry = JSON.parse(lines[i]); } catch { continue; }
|
|
74
|
-
const ts = Number(entry.timestamp_ms || 0);
|
|
75
|
-
if (ts && ts < cutoff) break;
|
|
76
|
-
if (entry.dedup_key === key && !entry.suppressed) return true;
|
|
77
|
-
}
|
|
78
|
-
return false;
|
|
79
|
-
} catch {
|
|
80
|
-
return false;
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
function appendTelemetry(record) {
|
|
85
|
-
try {
|
|
86
|
-
fs.mkdirSync(path.dirname(TELEMETRY_PATH), { recursive: true });
|
|
87
|
-
fs.appendFileSync(TELEMETRY_PATH, JSON.stringify(record) + '\n', 'utf8');
|
|
88
|
-
} catch { /* best-effort */ }
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
// Auto-invoke the snapshot CLI as a detached process so the hook returns fast
|
|
92
|
-
// and the snapshot write does not depend on the agent remembering. Errors are
|
|
93
|
-
// swallowed — never block the user turn.
|
|
94
|
-
function invokeHandoffSnapshot(trigger, milestone, sessionId, transcriptPath) {
|
|
95
|
-
try {
|
|
96
|
-
const { spawn } = require('child_process');
|
|
97
|
-
const cli = path.join(PROJECT_DIR, '.specialist', 'Ansuz', 'tools', 'ansuz-handoff-snapshot.js');
|
|
98
|
-
if (!fs.existsSync(cli)) return;
|
|
99
|
-
const args = [cli, `--trigger=${trigger}`];
|
|
100
|
-
if (milestone) args.push(`--last-milestone=${milestone}`);
|
|
101
|
-
if (sessionId) args.push(`--session-id=${sessionId}`);
|
|
102
|
-
// Scopes pending/unfinished to tasks this conversation touched.
|
|
103
|
-
if (transcriptPath) args.push(`--transcript-path=${transcriptPath}`);
|
|
104
|
-
const child = spawn('node', args, {
|
|
105
|
-
detached: true,
|
|
106
|
-
stdio: 'ignore',
|
|
107
|
-
cwd: PROJECT_DIR,
|
|
108
|
-
windowsHide: true,
|
|
109
|
-
});
|
|
110
|
-
child.unref();
|
|
111
|
-
} catch { /* best-effort */ }
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
function main() {
|
|
115
|
-
const payload = readStdin();
|
|
116
|
-
|
|
117
|
-
// Only fire if there is an actual response
|
|
118
|
-
if (!hasResponse(payload)) {
|
|
119
|
-
process.exit(0);
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
// Ansuz-only: see handoff-snapshot-on-commit.js.
|
|
123
|
-
if (!require('./_ansuz-conversation').isAnsuzConversation(payload.transcript_path, PROJECT_DIR)) {
|
|
124
|
-
process.exit(0);
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
const question = extractQuestion(payload);
|
|
128
|
-
const normalizedQ = normalizeQuestion(question);
|
|
129
|
-
const dedupKey = DEDUP_KEY_PREFIX + normalizedQ;
|
|
130
|
-
const suppressed = isDeduped(dedupKey);
|
|
131
|
-
|
|
132
|
-
appendTelemetry({
|
|
133
|
-
timestamp: new Date().toISOString(),
|
|
134
|
-
timestamp_ms: Date.now(),
|
|
135
|
-
hook: 'handoff-snapshot-on-question',
|
|
136
|
-
dedup_key: dedupKey,
|
|
137
|
-
question: normalizedQ,
|
|
138
|
-
suppressed,
|
|
139
|
-
});
|
|
140
|
-
|
|
141
|
-
if (suppressed) {
|
|
142
|
-
process.exit(0);
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
const questionSummary = normalizedQ.substring(0, 50) + (normalizedQ.length > 50 ? '…' : '');
|
|
146
|
-
const milestone = questionSummary
|
|
147
|
-
? `AskUserQuestion resolved: ${questionSummary}`
|
|
148
|
-
: 'AskUserQuestion resolved';
|
|
149
|
-
|
|
150
|
-
// Auto-write the handoff (system-level enforcement, instance-independent)
|
|
151
|
-
invokeHandoffSnapshot('live_decision_point', milestone, payload.session_id, payload.transcript_path);
|
|
152
|
-
|
|
153
|
-
process.stdout.write(
|
|
154
|
-
'\n' + fenceSignal('handoff', `🔁 AskUserQuestion resolved ("${questionSummary}"). Handoff auto-snapshot dispatched (live_decision_point).`) + '\n'
|
|
155
|
-
);
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
main();
|
|
@@ -1,197 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
/**
|
|
3
|
-
* Handoff Staleness Hook — UserPromptSubmit (TASK_1390, reworked TASK_1830)
|
|
4
|
-
* Nudges Ansuz to refresh THIS session's per-session ISBAR handoff slot
|
|
5
|
-
* (.kenaz/specialist/handoffs/<sessionId>.json) when it drifts stale.
|
|
6
|
-
*
|
|
7
|
-
* Fires if THIS CONVERSATION has dispatched work AND:
|
|
8
|
-
* (a) no per-session handoff slot exists for this session ('missing'), or
|
|
9
|
-
* (b) the slot's generated_at is older than HANDOFF_STALE_AFTER_MS ('stale').
|
|
10
|
-
* Silent when the slot is fresh, when it is the Phase 11 cleared sentinel,
|
|
11
|
-
* or when the session has not dispatched anything (idle sessions are never
|
|
12
|
-
* nagged). Graceful no-op on any read error.
|
|
13
|
-
*
|
|
14
|
-
* Anchor contract (TASK_1837): the conversation start anchor is the transcript
|
|
15
|
-
* file's birthtime ONLY. If the harness provides no readable transcript_path,
|
|
16
|
-
* the anchor is UNKNOWN and the hook stays silent — it must never fall back to
|
|
17
|
-
* ansuz_session.json started_at (a long-lived SDK-daemon timestamp that
|
|
18
|
-
* attributes days of historical dispatch briefs to this conversation).
|
|
19
|
-
* Staleness threshold: single-sourced from ansuz-handoff-snapshot.js
|
|
20
|
-
* (HANDOFF_STALE_AFTER_MS) — see TASK_1837/TASK_1838.
|
|
21
|
-
*
|
|
22
|
-
* Dispatch detection (TASK_1830 measurement — two routes, two trace families):
|
|
23
|
-
* 1. Formation dispatch route → .formation/.active-dispatches.json
|
|
24
|
-
* (written by Rust, kenaz.rs). Kept for that route.
|
|
25
|
-
* 2. sub-agent route (Agent tool — the DEFAULT since 2026-06-09) never touches
|
|
26
|
-
* that file. Its in-project trace is the dispatch brief
|
|
27
|
-
* .kenaz/specialist/dispatch_prompt_<task>.(md|txt) written at dispatch
|
|
28
|
-
* time. Gating only on route 1 made this hook permanently silent.
|
|
29
|
-
*
|
|
30
|
-
* Session identity (TASK_1829/TASK_1830): slots are read via handoff-resolver
|
|
31
|
-
* readOwnHandoff — NEVER readLatestHandoff (another session's fresh slot would
|
|
32
|
-
* false-pass our staleness check). Candidate order: hook stdin session_id
|
|
33
|
-
* (snapshot hooks name slots with it) → transcript basename (stable across
|
|
34
|
-
* compact/resume) → resolver ambient candidates (ansuz_session.json / env).
|
|
35
|
-
*
|
|
36
|
-
* Entry contract: settings.json invokes this via
|
|
37
|
-
* node -e "...require(d+'/.claude/hooks/handoff-staleness.js')"
|
|
38
|
-
* so the body MUST run at module top level. Do NOT add a
|
|
39
|
-
* `require.main === module` guard — under node -e require.main is undefined
|
|
40
|
-
* and such a guard silently disables the hook (TASK_1827/TASK_1829 regressions).
|
|
41
|
-
*/
|
|
42
|
-
'use strict';
|
|
43
|
-
const kenazProjectRoot = require('./_project-guard').guard();
|
|
44
|
-
const fs = require('fs');
|
|
45
|
-
const path = require('path');
|
|
46
|
-
const { fenceSignal } = require('./_signal-fence');
|
|
47
|
-
|
|
48
|
-
// Staleness threshold single source (TASK_1837, pattern from TASK_1838's
|
|
49
|
-
// dispatchLockStaleness): the snapshot WRITER owns HANDOFF_STALE_AFTER_MS and
|
|
50
|
-
// derives its own freshness window from it, so "hook says stale" always implies
|
|
51
|
-
// "writer will rewrite". Holding a local copy here is what produced the
|
|
52
|
-
// 5min-vs-30min dead zone. Required lazily below (stale branch only).
|
|
53
|
-
|
|
54
|
-
const projectDir = kenazProjectRoot;
|
|
55
|
-
const sessionFile = path.join(projectDir, '.kenaz', 'specialist', 'ansuz_session.json');
|
|
56
|
-
const specialistDir = path.join(projectDir, '.kenaz', 'specialist');
|
|
57
|
-
const dispatchFile = path.join(projectDir, '.formation', '.active-dispatches.json');
|
|
58
|
-
|
|
59
|
-
try {
|
|
60
|
-
// Read current session
|
|
61
|
-
if (!fs.existsSync(sessionFile)) process.exit(0);
|
|
62
|
-
const session = JSON.parse(fs.readFileSync(sessionFile, 'utf8'));
|
|
63
|
-
const currentSessionId = session && session.session_id;
|
|
64
|
-
if (!currentSessionId) process.exit(0);
|
|
65
|
-
// Hook stdin payload — Claude Code injects { session_id, transcript_path, ... }.
|
|
66
|
-
let payload = {};
|
|
67
|
-
try {
|
|
68
|
-
const raw = fs.readFileSync(0, 'utf8').trim();
|
|
69
|
-
if (raw) payload = JSON.parse(raw) || {};
|
|
70
|
-
} catch { /* stdin optional — probes and degraded harnesses */ }
|
|
71
|
-
|
|
72
|
-
// Anchor "this conversation started". The ONLY valid anchor is the transcript
|
|
73
|
-
// file's birthtime — it is per-conversation. ansuz_session.json started_at
|
|
74
|
-
// belongs to the long-lived boot-registered session (the SDK daemon can live
|
|
75
|
-
// for days); using it as a fallback attributed every historical dispatch brief
|
|
76
|
-
// to the current conversation (TASK_1837 measurement: 11 stale briefs matched
|
|
77
|
-
// the 6-day-old daemon anchor vs 0 with the transcript anchor), so every first
|
|
78
|
-
// prompt false-alarmed 'missing'. Unknown-is-null (same family as TASK_1832/
|
|
79
|
-
// TASK_1833): when the anchor cannot be established, convStartedAt stays null
|
|
80
|
-
// and hasDispatched stays false — never guess from an optimistic default.
|
|
81
|
-
let convStartedAt = null;
|
|
82
|
-
if (payload.transcript_path) {
|
|
83
|
-
try {
|
|
84
|
-
const birth = fs.statSync(String(payload.transcript_path)).birthtimeMs;
|
|
85
|
-
if (Number.isFinite(birth) && birth > 0) convStartedAt = birth;
|
|
86
|
-
} catch { /* transcript unreadable — anchor stays unknown */ }
|
|
87
|
-
}
|
|
88
|
-
// Anchor unknown → cannot attribute any dispatch to this conversation → silent.
|
|
89
|
-
if (!convStartedAt) process.exit(0);
|
|
90
|
-
|
|
91
|
-
// Ansuz-only nudge: a plain Claude Code session sharing this project must not
|
|
92
|
-
// be told to refresh an Ansuz handoff just because some other session
|
|
93
|
-
// dispatched. Decided from this conversation's own user messages.
|
|
94
|
-
const { isAnsuzConversation } = require('./_ansuz-conversation');
|
|
95
|
-
if (!isAnsuzConversation(payload.transcript_path, projectDir)) process.exit(0);
|
|
96
|
-
|
|
97
|
-
// Determine if any dispatch occurred during this session.
|
|
98
|
-
// Note: dispatch.originSessionId is the MCP server's session ID (auto-injected
|
|
99
|
-
// at mcp.rs:get_session_id), NOT Claude's session_id. The two identity spaces
|
|
100
|
-
// never match. Use time-based comparison instead.
|
|
101
|
-
// A dispatch counts only if it started after this conversation did AND this
|
|
102
|
-
// conversation's own tool calls name its task id. Timestamps alone let a
|
|
103
|
-
// parallel session's dispatch nag every other open conversation.
|
|
104
|
-
const { toolUseMentionsAny, taskIdNeedles } = require('./_ansuz-conversation');
|
|
105
|
-
const candidateNeedles = [];
|
|
106
|
-
try {
|
|
107
|
-
// Route 1: Formation registry (entries carry taskId).
|
|
108
|
-
if (fs.existsSync(dispatchFile)) {
|
|
109
|
-
const dispatches = JSON.parse(fs.readFileSync(dispatchFile, 'utf8'));
|
|
110
|
-
if (Array.isArray(dispatches)) {
|
|
111
|
-
for (const d of dispatches) {
|
|
112
|
-
if (!d || !d.startedAt || !d.taskId) continue; // unattributable → ignore
|
|
113
|
-
const dispatchStartedAt = new Date(d.startedAt).getTime();
|
|
114
|
-
if (Number.isFinite(dispatchStartedAt) && dispatchStartedAt >= convStartedAt) {
|
|
115
|
-
candidateNeedles.push(...taskIdNeedles(d.taskId));
|
|
116
|
-
}
|
|
117
|
-
}
|
|
118
|
-
}
|
|
119
|
-
}
|
|
120
|
-
// Route 2: sub-agent dispatch briefs dispatch_prompt_<task>.(md|txt); mtime is
|
|
121
|
-
// the dispatch time. dispatch_prompt_stats.jsonl shares the prefix and is
|
|
122
|
-
// appended by every dispatch in every session — never a brief.
|
|
123
|
-
for (const name of fs.readdirSync(specialistDir)) {
|
|
124
|
-
const m = /^dispatch_prompt_(.+)\.(md|txt)$/.exec(name);
|
|
125
|
-
if (!m) continue;
|
|
126
|
-
try {
|
|
127
|
-
if (fs.statSync(path.join(specialistDir, name)).mtimeMs >= convStartedAt) {
|
|
128
|
-
candidateNeedles.push(...taskIdNeedles(m[1]));
|
|
129
|
-
}
|
|
130
|
-
} catch { /* file raced away — skip */ }
|
|
131
|
-
}
|
|
132
|
-
} catch { /* graceful — dispatch traces optional */ }
|
|
133
|
-
const hasDispatched = candidateNeedles.length > 0 &&
|
|
134
|
-
toolUseMentionsAny(payload.transcript_path, [...new Set(candidateNeedles)]);
|
|
135
|
-
|
|
136
|
-
// No dispatches this session → nothing to nudge about (idle sessions stay quiet).
|
|
137
|
-
if (!hasDispatched) process.exit(0);
|
|
138
|
-
|
|
139
|
-
// Read THIS session's per-session handoff slot via the resolver (TASK_1829:
|
|
140
|
-
// readOwnHandoff only — readLatestHandoff would let another session's fresh
|
|
141
|
-
// slot masquerade as ours and turn a true warning into a false pass).
|
|
142
|
-
let handoff = null;
|
|
143
|
-
let slotSessionId = null;
|
|
144
|
-
try {
|
|
145
|
-
const resolver = require(path.join(projectDir, '.specialist', 'Ansuz', 'tools', 'handoff-resolver.js'));
|
|
146
|
-
const candidates = [];
|
|
147
|
-
if (payload.session_id) candidates.push(String(payload.session_id));
|
|
148
|
-
if (payload.transcript_path) {
|
|
149
|
-
candidates.push(path.basename(String(payload.transcript_path), '.jsonl'));
|
|
150
|
-
}
|
|
151
|
-
candidates.push(String(currentSessionId));
|
|
152
|
-
for (const cand of resolver.resolveOwnSessionCandidates(projectDir)) {
|
|
153
|
-
if (cand && cand.id) candidates.push(String(cand.id));
|
|
154
|
-
}
|
|
155
|
-
for (const id of [...new Set(candidates)]) {
|
|
156
|
-
handoff = resolver.readOwnHandoff(projectDir, id);
|
|
157
|
-
if (handoff) { slotSessionId = id; break; }
|
|
158
|
-
}
|
|
159
|
-
} catch { /* resolver unavailable — treat as no slot */ }
|
|
160
|
-
|
|
161
|
-
// Skip if handoff is already the cleared sentinel (Phase 11 done)
|
|
162
|
-
if (handoff && handoff.cleared === true) process.exit(0);
|
|
163
|
-
|
|
164
|
-
const now = Date.now();
|
|
165
|
-
let staleReason = null;
|
|
166
|
-
let lastUpdateAgo = null;
|
|
167
|
-
|
|
168
|
-
if (!handoff) {
|
|
169
|
-
// (a) Dispatched this session but no per-session slot yet
|
|
170
|
-
staleReason = 'missing';
|
|
171
|
-
} else {
|
|
172
|
-
// (b) Slot exists but generated_at is too old (writeHandoff/snapshot CLI
|
|
173
|
-
// always stamp generated_at; a missing stamp reads as stale).
|
|
174
|
-
// Threshold comes from the writer module — if it cannot be loaded the outer
|
|
175
|
-
// catch silences the hook, which is correct: the nudge would point at a
|
|
176
|
-
// snapshot CLI that cannot run anyway.
|
|
177
|
-
const { HANDOFF_STALE_AFTER_MS } = require(
|
|
178
|
-
path.join(projectDir, '.specialist', 'Ansuz', 'tools', 'ansuz-handoff-snapshot.js')
|
|
179
|
-
);
|
|
180
|
-
const generatedAt = handoff.generated_at ? new Date(handoff.generated_at).getTime() : null;
|
|
181
|
-
const ageMs = Number.isFinite(generatedAt) && generatedAt ? now - generatedAt : Infinity;
|
|
182
|
-
if (ageMs > HANDOFF_STALE_AFTER_MS) {
|
|
183
|
-
staleReason = 'stale';
|
|
184
|
-
lastUpdateAgo = Number.isFinite(ageMs) ? Math.round(ageMs / 60000) : null;
|
|
185
|
-
}
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
if (!staleReason) process.exit(0);
|
|
189
|
-
|
|
190
|
-
// Emit one-line nudge
|
|
191
|
-
const nudgeSessionId = slotSessionId || (payload.session_id ? String(payload.session_id) : String(currentSessionId));
|
|
192
|
-
const sessionShort = nudgeSessionId.slice(0, 8);
|
|
193
|
-
const agoStr = lastUpdateAgo != null ? `, last_update: ${lastUpdateAgo}min` : '';
|
|
194
|
-
process.stdout.write(
|
|
195
|
-
'\n' + fenceSignal('handoff', `⚠️ ISBAR handoff ${staleReason} (session: ${sessionShort}${agoStr}). Refresh this session's per-session slot .kenaz/specialist/handoffs/${nudgeSessionId}.json — call the ansuz_handoff_snapshot MCP tool, or run: node .specialist/Ansuz/tools/ansuz-handoff-snapshot.js --trigger=manual --session-id=${nudgeSessionId}. Never hand-write the retired singular slot.`) + '\n'
|
|
196
|
-
);
|
|
197
|
-
} catch { /* graceful no-op — never block user turn */ }
|
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
// TASK_2341: the thalamus ranking endpoint (POST /api/synapse/jev-thalamus-rank) was removed with the
|
|
3
|
-
// graph/thalamus subsystem, so postRank no longer makes any HTTP call. It resolves null, which
|
|
4
|
-
// .specialist/tools/jev-optional-context.js already treats as "rank unavailable" -> baseline (no optional
|
|
5
|
-
// context). The file name and `enabled`/`postRank` exports stay because jev-optional-context.js requires them.
|
|
6
|
-
const fs = require('fs');
|
|
7
|
-
const path = require('path');
|
|
8
|
-
const MAX_INPUT = 32768;
|
|
9
|
-
function enabled(projectDir) {
|
|
10
|
-
try {
|
|
11
|
-
if (!path.isAbsolute(projectDir)) return false;
|
|
12
|
-
const file = path.join(projectDir, '.kenaz', 'dashboard-models.json');
|
|
13
|
-
return fs.statSync(file).size <= MAX_INPUT && JSON.parse(fs.readFileSync(file, 'utf8')).jevThalamusEnabled === true;
|
|
14
|
-
} catch { return false; }
|
|
15
|
-
}
|
|
16
|
-
async function postRank() { return null; }
|
|
17
|
-
module.exports = { enabled, postRank };
|
|
@@ -1,41 +0,0 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
const { test } = require('node:test');
|
|
3
|
-
const assert = require('node:assert/strict');
|
|
4
|
-
const fs = require('fs');
|
|
5
|
-
const path = require('path');
|
|
6
|
-
const os = require('os');
|
|
7
|
-
const helper = require('./jev-thalamus-context');
|
|
8
|
-
const { loadOptionalContext } = require('../../.specialist/tools/jev-optional-context');
|
|
9
|
-
function project(t, flag = true) {
|
|
10
|
-
const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'jev-thalamus-hook-'));
|
|
11
|
-
t.after(() => fs.rmSync(dir, { recursive: true, force: true }));
|
|
12
|
-
fs.mkdirSync(path.join(dir, '.kenaz'));
|
|
13
|
-
const toggle = value => fs.writeFileSync(path.join(dir, '.kenaz/dashboard-models.json'), JSON.stringify({ jevThalamusEnabled: value }));
|
|
14
|
-
toggle(flag); return { dir, toggle };
|
|
15
|
-
}
|
|
16
|
-
test('enabled is true only for exact boolean true in an absolute project dir', t => {
|
|
17
|
-
const ctx = project(t);
|
|
18
|
-
assert.equal(helper.enabled(ctx.dir), true);
|
|
19
|
-
for (const flag of [false, 'true', 1, null]) { ctx.toggle(flag); assert.equal(helper.enabled(ctx.dir), false); }
|
|
20
|
-
assert.equal(helper.enabled('relative/dir'), false);
|
|
21
|
-
assert.equal(helper.enabled(path.join(ctx.dir, 'missing')), false);
|
|
22
|
-
});
|
|
23
|
-
test('postRank makes no HTTP call (endpoint removed) and resolves null', async () => {
|
|
24
|
-
const http = require('http');
|
|
25
|
-
const original = http.request; let calls = 0;
|
|
26
|
-
http.request = () => { calls++; throw new Error('must not be called'); };
|
|
27
|
-
try { assert.equal(await helper.postRank({ projectPath: 'x', taskContext: 't', candidates: [] }), null); }
|
|
28
|
-
finally { http.request = original; }
|
|
29
|
-
assert.equal(calls, 0);
|
|
30
|
-
assert.equal(helper.ENDPOINT, undefined);
|
|
31
|
-
});
|
|
32
|
-
test('jev-optional-context stays on baseline (rank_unavailable) with the real postRank', async t => {
|
|
33
|
-
const ctx = project(t);
|
|
34
|
-
const out = await loadOptionalContext({ projectPath: ctx.dir, prompt: 'task', specialist: 'ansuz' });
|
|
35
|
-
assert.equal(out.status, 'baseline');
|
|
36
|
-
assert.equal(out.context, '');
|
|
37
|
-
});
|
|
38
|
-
test('hook source has no calls to removed /api/synapse routes', () => {
|
|
39
|
-
const src = fs.readFileSync(path.join(__dirname, 'jev-thalamus-context.js'), 'utf8').replace(/^\s*\/\/.*$/gm, '');
|
|
40
|
-
assert.doesNotMatch(src, /http\.request|\/api\/synapse\/jev/);
|
|
41
|
-
});
|