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,85 +0,0 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
// phase-gate gate-shape tests (restored TASK_2340 after the Kuzu removal dropped the pulse/predicted_symbols/thalamus gates).
|
|
3
|
-
// Everything runs against a temp --project-path; charts for the temp project land in the git-ignored .specialist/sessions/projects/.
|
|
4
|
-
const test = require('node:test');
|
|
5
|
-
const assert = require('node:assert/strict');
|
|
6
|
-
const fs = require('node:fs');
|
|
7
|
-
const os = require('node:os');
|
|
8
|
-
const path = require('node:path');
|
|
9
|
-
|
|
10
|
-
const proj = fs.mkdtempSync(path.join(os.tmpdir(), 'pg2340-'));
|
|
11
|
-
fs.mkdirSync(path.join(proj, '.kenaz', 'specialist'), { recursive: true });
|
|
12
|
-
process.argv.push('--project-path=' + proj); // read at module load by phase-gate
|
|
13
|
-
const pg = require('./phase-gate');
|
|
14
|
-
|
|
15
|
-
const coreRoot = path.resolve(__dirname, '../..');
|
|
16
|
-
const taskId = 'TASK_PG2340_' + process.pid;
|
|
17
|
-
const taskDir = path.join(coreRoot, '.kenaz', 'tasks', 'active');
|
|
18
|
-
const taskFile = path.join(taskDir, taskId + '.yaml');
|
|
19
|
-
|
|
20
|
-
// enterPhase/exitPhase print JSON; keep test output clean.
|
|
21
|
-
function quiet(fn) {
|
|
22
|
-
const log = console.log;
|
|
23
|
-
console.log = () => {};
|
|
24
|
-
try { return fn(); } finally { console.log = log; }
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
// enterPhase touches lesson_guards.yaml (injection bookkeeping); restore it so the suite leaves the tree clean.
|
|
28
|
-
const guardsFile = path.join(coreRoot, '.specialist', 'Ansuz', 'guards', 'lesson_guards.yaml');
|
|
29
|
-
const guardsBefore = fs.existsSync(guardsFile) ? fs.readFileSync(guardsFile) : null;
|
|
30
|
-
|
|
31
|
-
test.before(() => {
|
|
32
|
-
fs.mkdirSync(taskDir, { recursive: true });
|
|
33
|
-
fs.writeFileSync(taskFile, `id: ${taskId}\ntitle: pg test\nscope_paths:\n - .specialist/tools/phase-gate.js\n`);
|
|
34
|
-
});
|
|
35
|
-
test.after(() => {
|
|
36
|
-
if (guardsBefore) fs.writeFileSync(guardsFile, guardsBefore);
|
|
37
|
-
fs.rmSync(taskFile, { force: true });
|
|
38
|
-
try { fs.rmdirSync(taskDir); } catch { /* not empty or absent: leave tracked dirs alone */ }
|
|
39
|
-
fs.rmSync(proj, { recursive: true, force: true });
|
|
40
|
-
const sessions = path.join(coreRoot, '.specialist', 'sessions', 'projects');
|
|
41
|
-
if (fs.existsSync(sessions)) {
|
|
42
|
-
for (const d of fs.readdirSync(sessions)) {
|
|
43
|
-
if (d.startsWith(path.basename(proj).toLowerCase())) fs.rmSync(path.join(sessions, d), { recursive: true, force: true });
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
|
-
});
|
|
47
|
-
|
|
48
|
-
test('the task fixture really declares scope_paths', () => {
|
|
49
|
-
assert.deepEqual(pg.readTaskScopePaths(taskId), ['.specialist/tools/phase-gate.js']);
|
|
50
|
-
});
|
|
51
|
-
|
|
52
|
-
test('checklists no longer ask for removed tools (preamble, thalamus, pulse, search)', () => {
|
|
53
|
-
const all = JSON.stringify(pg.PHASE_CHECKLISTS);
|
|
54
|
-
for (const gone of ['synapse_preamble', 'preamble_loaded', 'Tier 2b', 'thalamus', 'synapse_pulse', 'synapse_search', 'synapse_impact']) {
|
|
55
|
-
assert.equal(all.includes(gone), false, gone);
|
|
56
|
-
}
|
|
57
|
-
assert.ok(!pg.PHASE_CHECKLISTS[0].some(i => i.includes('cortex_recall') || i.includes('synapse_')));
|
|
58
|
-
assert.ok(pg.PHASE_CHECKLISTS[2].some(i => i.includes('kenaz_lsp_callers')));
|
|
59
|
-
assert.equal(JSON.stringify(pg.PHASE_CHECKLISTS).toLowerCase().includes('muninn'), false);
|
|
60
|
-
});
|
|
61
|
-
|
|
62
|
-
test('Phase 8 exit rule no longer requires predicted_symbols', () => {
|
|
63
|
-
assert.equal(pg.PHASE_EXIT_RULES[8], undefined);
|
|
64
|
-
assert.deepEqual(pg.PHASE_EXIT_RULES[1].required, ['ddx_hypotheses']);
|
|
65
|
-
assert.deepEqual(pg.PHASE_EXIT_RULES[9].required, ['cognitive_checkpoint']);
|
|
66
|
-
});
|
|
67
|
-
|
|
68
|
-
test('Phase 3 enter is not blocked by a missing pulse log even when the task declares scope_paths', () => {
|
|
69
|
-
const r = quiet(() => pg.enterPhase(3, taskId));
|
|
70
|
-
assert.equal(r.blocked, false);
|
|
71
|
-
assert.equal(r.block_reason, null);
|
|
72
|
-
assert.equal(JSON.stringify(r.warnings).includes('pulse'), false);
|
|
73
|
-
});
|
|
74
|
-
|
|
75
|
-
test('Phase 8 enter no longer reports muninn / valence output', () => {
|
|
76
|
-
const r = quiet(() => pg.enterPhase(8, taskId));
|
|
77
|
-
assert.equal(r.blocked, false);
|
|
78
|
-
assert.equal('valence' in r, false);
|
|
79
|
-
assert.equal('muninn' in r, false);
|
|
80
|
-
});
|
|
81
|
-
|
|
82
|
-
test('Phase 8 exit passes with scope_paths and no predicted_symbols', () => {
|
|
83
|
-
const r = quiet(() => pg.exitPhase(8, taskId, {}));
|
|
84
|
-
assert.notEqual(r.blocked, true);
|
|
85
|
-
});
|
|
@@ -1,444 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
'use strict';
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* Precompute Runner — TASK_1162
|
|
6
|
-
*
|
|
7
|
-
* Executes safe, read-only named functions before phase entry and embeds
|
|
8
|
-
* their output into blind-spot guard text. Eliminates "remember to check X"
|
|
9
|
-
* compliance friction by making the result already present in instructions.
|
|
10
|
-
*
|
|
11
|
-
* Security model:
|
|
12
|
-
* - execFileSync with argv array (never execSync + shell) — prevents shell injection
|
|
13
|
-
* - env: SAFE_ENV (PATH-only) — drops GIT_EXEC_PATH, LD_PRELOAD, etc.
|
|
14
|
-
* - Per-parameter regex validation BEFORE any execFileSync call
|
|
15
|
-
* - File paths resolved within project root and rejected on escape
|
|
16
|
-
* - Whitelist: 5 named functions only — gitShowStat, gitDiffStat, grepCount, wcLines, fileExists
|
|
17
|
-
*
|
|
18
|
-
* Budget:
|
|
19
|
-
* - 3s total per phase enter (TOTAL_BUDGET_MS)
|
|
20
|
-
* - 1s per command (PER_CMD_TIMEOUT_MS, enforced by execFileSync timeout option)
|
|
21
|
-
* - Per-guard output capped at 200 chars (PER_GUARD_CAP)
|
|
22
|
-
* - Total output capped at 4096 bytes (TOTAL_CAP)
|
|
23
|
-
*
|
|
24
|
-
* Kill switch:
|
|
25
|
-
* KENAZ_PRECOMPUTE_DISABLE=1 → skip all precompute, fall back to text-only guard
|
|
26
|
-
*
|
|
27
|
-
* Guard YAML precompute field format:
|
|
28
|
-
* precompute:
|
|
29
|
-
* cmd: "gitShowStat" # one of 5 named functions
|
|
30
|
-
* sha: "HEAD" # gitShowStat / gitDiffStat param
|
|
31
|
-
* sha1: "HEAD~1" # gitDiffStat param
|
|
32
|
-
* sha2: "HEAD" # gitDiffStat param
|
|
33
|
-
* pattern: "TODO" # grepCount param (JS regex pattern)
|
|
34
|
-
* file: "path/to/file" # grepCount / wcLines param (project-relative)
|
|
35
|
-
* path: "path/to/check" # fileExists param (project-relative)
|
|
36
|
-
* when: "always" # "always" | "never" | taskData key name
|
|
37
|
-
* template: "Result: {{result}}" # {{result}} replaced with command output
|
|
38
|
-
*/
|
|
39
|
-
|
|
40
|
-
const fs = require('fs');
|
|
41
|
-
const path = require('path');
|
|
42
|
-
const { execFileSync } = require('child_process');
|
|
43
|
-
|
|
44
|
-
// ── Constants ───────────────────────────────────────────────
|
|
45
|
-
|
|
46
|
-
const TOTAL_BUDGET_MS = 3000; // total per phase enter
|
|
47
|
-
const PER_CMD_TIMEOUT_MS = 1000; // execFileSync timeout per command
|
|
48
|
-
const PER_GUARD_CAP = 200; // max chars per guard output (truncated with …)
|
|
49
|
-
const TOTAL_CAP = 4096; // max total chars across all guards per phase
|
|
50
|
-
const MAX_BUFFER = 4096; // execFileSync maxBuffer
|
|
51
|
-
|
|
52
|
-
// SHA validation: 7–40 lowercase hex chars (git short or full SHA)
|
|
53
|
-
const SHA_RE = /^[0-9a-f]{7,40}$/;
|
|
54
|
-
|
|
55
|
-
// Minimal safe environment — only PATH so git can be located.
|
|
56
|
-
// Drops GIT_EXEC_PATH, GIT_SSH, GIT_TEMPLATE_DIR, LD_PRELOAD, etc.
|
|
57
|
-
const SAFE_ENV = { PATH: process.env.PATH || process.env.Path || '' };
|
|
58
|
-
|
|
59
|
-
// ── Kill switch ─────────────────────────────────────────────
|
|
60
|
-
|
|
61
|
-
/**
|
|
62
|
-
* Returns true if precompute is globally disabled via env var.
|
|
63
|
-
* Evaluated lazily so tests can set the env var before calling runPrecompute.
|
|
64
|
-
*/
|
|
65
|
-
function isDisabled() {
|
|
66
|
-
return process.env.KENAZ_PRECOMPUTE_DISABLE === '1';
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
// ── Parameter validators ────────────────────────────────────
|
|
70
|
-
|
|
71
|
-
/**
|
|
72
|
-
* Validate a git SHA parameter.
|
|
73
|
-
* @param {string} sha
|
|
74
|
-
* @returns {string} validated sha
|
|
75
|
-
* @throws {Error} if invalid
|
|
76
|
-
*/
|
|
77
|
-
function validateSha(sha) {
|
|
78
|
-
if (typeof sha !== 'string' || !SHA_RE.test(sha)) {
|
|
79
|
-
throw new Error(`Invalid SHA "${sha}": must match /^[0-9a-f]{7,40}$/`);
|
|
80
|
-
}
|
|
81
|
-
return sha;
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
/**
|
|
85
|
-
* Resolve and validate a file path within project root.
|
|
86
|
-
* Rejects any path that escapes the project root (../ traversal).
|
|
87
|
-
* @param {string} filePath - relative or absolute path
|
|
88
|
-
* @param {string} projectRoot - absolute project root
|
|
89
|
-
* @returns {string} validated absolute path
|
|
90
|
-
* @throws {Error} if path escapes project root
|
|
91
|
-
*/
|
|
92
|
-
function resolveFilePath(filePath, projectRoot) {
|
|
93
|
-
if (typeof filePath !== 'string' || !filePath.trim()) {
|
|
94
|
-
throw new Error('File path must be a non-empty string');
|
|
95
|
-
}
|
|
96
|
-
const abs = path.isAbsolute(filePath)
|
|
97
|
-
? filePath
|
|
98
|
-
: path.resolve(projectRoot, filePath);
|
|
99
|
-
const normalized = path.normalize(abs);
|
|
100
|
-
const rootNormalized = path.normalize(projectRoot);
|
|
101
|
-
// Must be within project root (starts with root + sep, or equals root)
|
|
102
|
-
if (normalized !== rootNormalized &&
|
|
103
|
-
!normalized.startsWith(rootNormalized + path.sep)) {
|
|
104
|
-
throw new Error(`Path escape rejected: "${filePath}" resolves outside project root`);
|
|
105
|
-
}
|
|
106
|
-
return normalized;
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
// ── Named Function Library ──────────────────────────────────
|
|
110
|
-
//
|
|
111
|
-
// Each function validates its parameters, then either executes a read-only
|
|
112
|
-
// git command via execFileSync (argv array, never shell) or reads files
|
|
113
|
-
// using Node's fs module (grepCount, wcLines, fileExists — cross-platform,
|
|
114
|
-
// no shell dependency).
|
|
115
|
-
|
|
116
|
-
/**
|
|
117
|
-
* Show stat for a single commit (files changed + insertions/deletions summary).
|
|
118
|
-
* @param {string} sha - 7–40 hex git SHA
|
|
119
|
-
* @param {string} projectRoot - absolute project root (cwd for git)
|
|
120
|
-
* @returns {string} trimmed stdout
|
|
121
|
-
*/
|
|
122
|
-
function gitShowStat(sha, projectRoot) {
|
|
123
|
-
validateSha(sha);
|
|
124
|
-
const result = execFileSync('git', ['show', '--stat', '--no-patch', sha], {
|
|
125
|
-
env: SAFE_ENV,
|
|
126
|
-
cwd: projectRoot,
|
|
127
|
-
timeout: PER_CMD_TIMEOUT_MS,
|
|
128
|
-
maxBuffer: MAX_BUFFER,
|
|
129
|
-
encoding: 'utf8',
|
|
130
|
-
windowsHide: true,
|
|
131
|
-
});
|
|
132
|
-
return result.trim();
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
/**
|
|
136
|
-
* Diff stat between two commits (files changed summary).
|
|
137
|
-
* @param {string} sha1 - base commit SHA
|
|
138
|
-
* @param {string} sha2 - head commit SHA
|
|
139
|
-
* @param {string} projectRoot - absolute project root
|
|
140
|
-
* @returns {string} trimmed stdout
|
|
141
|
-
*/
|
|
142
|
-
function gitDiffStat(sha1, sha2, projectRoot) {
|
|
143
|
-
validateSha(sha1);
|
|
144
|
-
validateSha(sha2);
|
|
145
|
-
const result = execFileSync('git', ['diff', '--stat', sha1, sha2], {
|
|
146
|
-
env: SAFE_ENV,
|
|
147
|
-
cwd: projectRoot,
|
|
148
|
-
timeout: PER_CMD_TIMEOUT_MS,
|
|
149
|
-
maxBuffer: MAX_BUFFER,
|
|
150
|
-
encoding: 'utf8',
|
|
151
|
-
windowsHide: true,
|
|
152
|
-
});
|
|
153
|
-
return result.trim();
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
/**
|
|
157
|
-
* Count lines matching a pattern in a file (pure Node — cross-platform).
|
|
158
|
-
* Uses JavaScript RegExp — no grep binary required on Windows.
|
|
159
|
-
* @param {string} pattern - JavaScript regex pattern string
|
|
160
|
-
* @param {string} filePath - project-relative or absolute path
|
|
161
|
-
* @param {string} projectRoot - absolute project root
|
|
162
|
-
* @returns {string} match count as string
|
|
163
|
-
*/
|
|
164
|
-
function grepCount(pattern, filePath, projectRoot) {
|
|
165
|
-
if (typeof pattern !== 'string') throw new Error('pattern must be a string');
|
|
166
|
-
const absPath = resolveFilePath(filePath, projectRoot);
|
|
167
|
-
// Validate pattern compiles (throws SyntaxError on invalid regex)
|
|
168
|
-
const re = new RegExp(pattern);
|
|
169
|
-
const content = fs.readFileSync(absPath, 'utf8');
|
|
170
|
-
const count = content.split('\n').filter(line => re.test(line)).length;
|
|
171
|
-
return String(count);
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
/**
|
|
175
|
-
* Count lines in a file (pure Node — cross-platform).
|
|
176
|
-
* @param {string} filePath - project-relative or absolute path
|
|
177
|
-
* @param {string} projectRoot - absolute project root
|
|
178
|
-
* @returns {string} line count as string
|
|
179
|
-
*/
|
|
180
|
-
function wcLines(filePath, projectRoot) {
|
|
181
|
-
const absPath = resolveFilePath(filePath, projectRoot);
|
|
182
|
-
const content = fs.readFileSync(absPath, 'utf8');
|
|
183
|
-
// Split on \n — consistent with wc -l semantics (count \n occurrences)
|
|
184
|
-
const count = content.split('\n').length - (content.endsWith('\n') ? 1 : 0);
|
|
185
|
-
return String(count);
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
/**
|
|
189
|
-
* Count commits between two refs: HEAD ahead of the given base ref.
|
|
190
|
-
* Used by L-004 to detect stale worktree base (e.g. ahead origin/main by N commits).
|
|
191
|
-
* @param {string} baseRef - base ref name (e.g. "origin/main", "main", "HEAD~10")
|
|
192
|
-
* @param {string} projectRoot - absolute project root
|
|
193
|
-
* @returns {string} "ahead {N} commits of {baseRef}" or "in sync with {baseRef}"
|
|
194
|
-
*/
|
|
195
|
-
function gitCommitsAhead(baseRef, projectRoot) {
|
|
196
|
-
// Only allow safe ref patterns: refs/, origin/x, local branch names, HEAD~N
|
|
197
|
-
if (!/^[a-zA-Z0-9_./~-]{1,80}$/.test(baseRef)) {
|
|
198
|
-
throw new Error(`Invalid baseRef "${baseRef}": must match /^[a-zA-Z0-9_./~-]{1,80}$/`);
|
|
199
|
-
}
|
|
200
|
-
let result;
|
|
201
|
-
try {
|
|
202
|
-
result = execFileSync('git', ['rev-list', '--count', `${baseRef}..HEAD`], {
|
|
203
|
-
env: SAFE_ENV,
|
|
204
|
-
cwd: projectRoot,
|
|
205
|
-
timeout: PER_CMD_TIMEOUT_MS,
|
|
206
|
-
maxBuffer: MAX_BUFFER,
|
|
207
|
-
encoding: 'utf8',
|
|
208
|
-
windowsHide: true,
|
|
209
|
-
});
|
|
210
|
-
} catch (e) {
|
|
211
|
-
return `(unknown — ${baseRef} unreachable)`;
|
|
212
|
-
}
|
|
213
|
-
const n = parseInt(result.trim(), 10);
|
|
214
|
-
if (!Number.isFinite(n)) return `(unknown count for ${baseRef})`;
|
|
215
|
-
if (n === 0) return `in sync with ${baseRef}`;
|
|
216
|
-
if (n > 50) return `⚠️ ahead ${n} commits of ${baseRef} — very stale base risk`;
|
|
217
|
-
if (n > 10) return `ahead ${n} commits of ${baseRef} — stale base possible`;
|
|
218
|
-
return `ahead ${n} commits of ${baseRef}`;
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
/**
|
|
222
|
-
* Show recent commits touching a file (last 5).
|
|
223
|
-
* Used by L-002 to surface what's recently changed in the file Ansuz is about to modify.
|
|
224
|
-
* @param {string} filePath - project-relative path
|
|
225
|
-
* @param {string} projectRoot - absolute project root
|
|
226
|
-
* @returns {string} compact log lines
|
|
227
|
-
*/
|
|
228
|
-
function gitLogShort(filePath, projectRoot) {
|
|
229
|
-
const absPath = resolveFilePath(filePath, projectRoot);
|
|
230
|
-
const relPath = path.relative(projectRoot, absPath);
|
|
231
|
-
// Reject path traversal — relPath must not start with ..
|
|
232
|
-
if (relPath.startsWith('..') || path.isAbsolute(relPath)) {
|
|
233
|
-
throw new Error(`Path escape detected: ${filePath}`);
|
|
234
|
-
}
|
|
235
|
-
const result = execFileSync('git', ['log', '--oneline', '-5', '--', relPath], {
|
|
236
|
-
env: SAFE_ENV,
|
|
237
|
-
cwd: projectRoot,
|
|
238
|
-
timeout: PER_CMD_TIMEOUT_MS,
|
|
239
|
-
maxBuffer: MAX_BUFFER,
|
|
240
|
-
encoding: 'utf8',
|
|
241
|
-
windowsHide: true,
|
|
242
|
-
});
|
|
243
|
-
const lines = result.trim().split('\n').filter(Boolean);
|
|
244
|
-
if (lines.length === 0) return `(no git history for ${relPath})`;
|
|
245
|
-
return `${relPath}: last ${lines.length} commits — ${lines[0].substring(0, 60)}`;
|
|
246
|
-
}
|
|
247
|
-
|
|
248
|
-
/**
|
|
249
|
-
* Check if a file exists within project root (pure Node — cross-platform).
|
|
250
|
-
* @param {string} filePath - project-relative or absolute path
|
|
251
|
-
* @param {string} projectRoot - absolute project root
|
|
252
|
-
* @returns {string} "true" or "false"
|
|
253
|
-
*/
|
|
254
|
-
function fileExists(filePath, projectRoot) {
|
|
255
|
-
let absPath;
|
|
256
|
-
try {
|
|
257
|
-
absPath = resolveFilePath(filePath, projectRoot);
|
|
258
|
-
} catch {
|
|
259
|
-
return 'false'; // invalid/escaped path → treat as not-found
|
|
260
|
-
}
|
|
261
|
-
return fs.existsSync(absPath) ? 'true' : 'false';
|
|
262
|
-
}
|
|
263
|
-
|
|
264
|
-
// ── Template Rendering ──────────────────────────────────────
|
|
265
|
-
|
|
266
|
-
/**
|
|
267
|
-
* Render a template string, replacing {{result}} with the command output.
|
|
268
|
-
* Truncates to PER_GUARD_CAP chars with ellipsis if needed.
|
|
269
|
-
* @param {string} template
|
|
270
|
-
* @param {string} result - raw command output
|
|
271
|
-
* @returns {string} rendered and capped output
|
|
272
|
-
*/
|
|
273
|
-
function renderTemplate(template, result) {
|
|
274
|
-
const rendered = (template || '{{result}}').replace(/\{\{result\}\}/g, result);
|
|
275
|
-
if (rendered.length > PER_GUARD_CAP) {
|
|
276
|
-
return rendered.slice(0, PER_GUARD_CAP - 1) + '…';
|
|
277
|
-
}
|
|
278
|
-
return rendered;
|
|
279
|
-
}
|
|
280
|
-
|
|
281
|
-
// ── Single Guard Evaluator ──────────────────────────────────
|
|
282
|
-
|
|
283
|
-
/**
|
|
284
|
-
* Evaluate a single guard's precompute field.
|
|
285
|
-
* Returns rendered string, or null if skipped (when=never or condition unmet).
|
|
286
|
-
*
|
|
287
|
-
* @param {object} guard - guard object with .precompute field
|
|
288
|
-
* @param {object} taskData - phase data for when-condition evaluation
|
|
289
|
-
* @param {string} projectRoot - absolute project root
|
|
290
|
-
* @returns {string|null} rendered output or null
|
|
291
|
-
*/
|
|
292
|
-
function evaluateGuard(guard, taskData, projectRoot) {
|
|
293
|
-
const pc = guard.precompute;
|
|
294
|
-
if (!pc || !pc.cmd) return null;
|
|
295
|
-
|
|
296
|
-
// Evaluate when condition
|
|
297
|
-
const when = (pc.when || 'always').trim();
|
|
298
|
-
if (when === 'never') return null;
|
|
299
|
-
if (when !== 'always') {
|
|
300
|
-
// Treat as a taskData key name — run only if taskData[when] is truthy
|
|
301
|
-
const safeData = taskData || {};
|
|
302
|
-
if (!safeData[when]) return null;
|
|
303
|
-
}
|
|
304
|
-
|
|
305
|
-
let rawResult;
|
|
306
|
-
try {
|
|
307
|
-
const cmd = pc.cmd;
|
|
308
|
-
switch (cmd) {
|
|
309
|
-
case 'gitShowStat':
|
|
310
|
-
rawResult = gitShowStat(pc.sha || 'HEAD', projectRoot);
|
|
311
|
-
break;
|
|
312
|
-
|
|
313
|
-
case 'gitDiffStat':
|
|
314
|
-
rawResult = gitDiffStat(pc.sha1 || 'HEAD~1', pc.sha2 || 'HEAD', projectRoot);
|
|
315
|
-
break;
|
|
316
|
-
|
|
317
|
-
case 'grepCount':
|
|
318
|
-
rawResult = grepCount(pc.pattern || '.', pc.file || '', projectRoot);
|
|
319
|
-
break;
|
|
320
|
-
|
|
321
|
-
case 'wcLines':
|
|
322
|
-
rawResult = wcLines(pc.file || '', projectRoot);
|
|
323
|
-
break;
|
|
324
|
-
|
|
325
|
-
case 'fileExists':
|
|
326
|
-
rawResult = fileExists(pc.path || pc.file || '', projectRoot);
|
|
327
|
-
break;
|
|
328
|
-
|
|
329
|
-
case 'gitCommitsAhead':
|
|
330
|
-
rawResult = gitCommitsAhead(pc.baseRef || pc.ref || 'origin/main', projectRoot);
|
|
331
|
-
break;
|
|
332
|
-
|
|
333
|
-
case 'gitLogShort':
|
|
334
|
-
rawResult = gitLogShort(pc.file || pc.path || '', projectRoot);
|
|
335
|
-
break;
|
|
336
|
-
|
|
337
|
-
default:
|
|
338
|
-
return `[precompute: unknown cmd "${String(cmd).slice(0, 40)}"]`;
|
|
339
|
-
}
|
|
340
|
-
} catch (e) {
|
|
341
|
-
// Surface error as result — guard still renders, just shows what went wrong
|
|
342
|
-
return `[precompute error: ${String(e.message).slice(0, 150)}]`;
|
|
343
|
-
}
|
|
344
|
-
|
|
345
|
-
return renderTemplate(pc.template || '{{result}}', rawResult);
|
|
346
|
-
}
|
|
347
|
-
|
|
348
|
-
// ── Batch Runner ────────────────────────────────────────────
|
|
349
|
-
|
|
350
|
-
/**
|
|
351
|
-
* Run precompute for all guards that have a precompute field.
|
|
352
|
-
* Mutates each matching guard object by adding a `precompute_result` field.
|
|
353
|
-
*
|
|
354
|
-
* Budget enforcement:
|
|
355
|
-
* - Skips remaining guards if TOTAL_BUDGET_MS exceeded
|
|
356
|
-
* - Skips remaining guards if TOTAL_CAP bytes exceeded
|
|
357
|
-
* - Per-command timeout enforced by execFileSync timeout option (1s)
|
|
358
|
-
*
|
|
359
|
-
* Note: Uses synchronous execution (execFileSync). Promise.all is used
|
|
360
|
-
* structurally for forward compatibility; true parallelism requires async
|
|
361
|
-
* fs/child_process APIs which can be introduced in a follow-up.
|
|
362
|
-
*
|
|
363
|
-
* @param {Array<object>} guards - guard objects from readBlindSpotGuards
|
|
364
|
-
* @param {object} taskData - current phase data (for when-condition evaluation)
|
|
365
|
-
* @param {string} projectRoot - absolute project root path
|
|
366
|
-
*/
|
|
367
|
-
function runPrecompute(guards, taskData, projectRoot) {
|
|
368
|
-
// Kill switch: skip all precompute
|
|
369
|
-
if (isDisabled()) {
|
|
370
|
-
return;
|
|
371
|
-
}
|
|
372
|
-
|
|
373
|
-
if (!Array.isArray(guards) || guards.length === 0) return;
|
|
374
|
-
|
|
375
|
-
const startMs = Date.now();
|
|
376
|
-
let totalOutput = 0;
|
|
377
|
-
let budgetExceeded = false;
|
|
378
|
-
|
|
379
|
-
for (const guard of guards) {
|
|
380
|
-
if (!guard.precompute) continue; // backward compat: guards without precompute unchanged
|
|
381
|
-
|
|
382
|
-
// Budget gate: check elapsed time
|
|
383
|
-
if (!budgetExceeded && (Date.now() - startMs) >= TOTAL_BUDGET_MS) {
|
|
384
|
-
budgetExceeded = true;
|
|
385
|
-
// Log warning to stderr (non-blocking — caller can inspect guard objects)
|
|
386
|
-
process.stderr.write('[precompute] WARN: total budget (3s) exceeded — remaining guards skipped\n');
|
|
387
|
-
}
|
|
388
|
-
|
|
389
|
-
if (budgetExceeded) {
|
|
390
|
-
guard.precompute_result = '[precompute: budget exceeded — skipped]';
|
|
391
|
-
continue;
|
|
392
|
-
}
|
|
393
|
-
|
|
394
|
-
// Total output cap gate
|
|
395
|
-
if (totalOutput >= TOTAL_CAP) {
|
|
396
|
-
guard.precompute_result = '[precompute: total output cap (4096 bytes) reached — skipped]';
|
|
397
|
-
continue;
|
|
398
|
-
}
|
|
399
|
-
|
|
400
|
-
const result = evaluateGuard(guard, taskData, projectRoot);
|
|
401
|
-
|
|
402
|
-
if (result !== null && result !== undefined) {
|
|
403
|
-
// Enforce total cap: trim result to fit remaining budget
|
|
404
|
-
const remaining = TOTAL_CAP - totalOutput;
|
|
405
|
-
if (result.length > remaining) {
|
|
406
|
-
guard.precompute_result = result.slice(0, remaining - 1) + '…';
|
|
407
|
-
totalOutput = TOTAL_CAP; // cap reached
|
|
408
|
-
} else {
|
|
409
|
-
guard.precompute_result = result;
|
|
410
|
-
totalOutput += result.length;
|
|
411
|
-
}
|
|
412
|
-
}
|
|
413
|
-
}
|
|
414
|
-
}
|
|
415
|
-
|
|
416
|
-
// ── Exports ─────────────────────────────────────────────────
|
|
417
|
-
|
|
418
|
-
module.exports = {
|
|
419
|
-
// Primary API
|
|
420
|
-
runPrecompute,
|
|
421
|
-
|
|
422
|
-
// Named functions (exported for unit testing)
|
|
423
|
-
gitShowStat,
|
|
424
|
-
gitDiffStat,
|
|
425
|
-
grepCount,
|
|
426
|
-
wcLines,
|
|
427
|
-
fileExists,
|
|
428
|
-
gitCommitsAhead,
|
|
429
|
-
gitLogShort,
|
|
430
|
-
|
|
431
|
-
// Validators (exported for unit testing)
|
|
432
|
-
validateSha,
|
|
433
|
-
resolveFilePath,
|
|
434
|
-
|
|
435
|
-
// Helpers (exported for unit testing)
|
|
436
|
-
renderTemplate,
|
|
437
|
-
evaluateGuard,
|
|
438
|
-
|
|
439
|
-
// Constants (exported for unit testing)
|
|
440
|
-
TOTAL_BUDGET_MS,
|
|
441
|
-
PER_CMD_TIMEOUT_MS,
|
|
442
|
-
PER_GUARD_CAP,
|
|
443
|
-
TOTAL_CAP,
|
|
444
|
-
};
|