kenaz 0.3.18 → 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/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/launch.js +1 -6
- package/core/.formation/bifrost/launch.test.js +1 -1
- 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__/m1-hmap-convid.test.js +1 -124
- package/core/.formation/tools/dispatch.js +1 -1
- 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 +9 -45
- 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/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,621 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* rule-engine.js — Self-Evolution Rule Runtime
|
|
5
|
-
*
|
|
6
|
-
* Loads ansuz_rules.yaml, tracks session observations for proposed rules,
|
|
7
|
-
* auto-promotes proposed → active after sessions_required threshold,
|
|
8
|
-
* and validates immutable rules are unchanged.
|
|
9
|
-
*
|
|
10
|
-
* CLI:
|
|
11
|
-
* node rule-engine.js load # Load and display all rules
|
|
12
|
-
* node rule-engine.js track <ruleId> # Increment sessions_observed for a proposed rule
|
|
13
|
-
* node rule-engine.js promote # Auto-promote eligible proposed rules
|
|
14
|
-
* node rule-engine.js validate # Validate immutable rules unchanged
|
|
15
|
-
* node rule-engine.js record-apply <ruleId> <sessionId> # Record rule application in session
|
|
16
|
-
* node rule-engine.js record-outcome <ruleId> <sessionId> <good|bad> # Record outcome
|
|
17
|
-
* node rule-engine.js validate-hash # Validate immutable rules via SHA256
|
|
18
|
-
* node rule-engine.js mark-review <ruleId> # Mark rule for Amelia review
|
|
19
|
-
*
|
|
20
|
-
* Module:
|
|
21
|
-
* const engine = require('.specialist/tools/rule-engine');
|
|
22
|
-
* engine.loadRules()
|
|
23
|
-
* engine.trackSession('R-200')
|
|
24
|
-
* engine.promoteRules()
|
|
25
|
-
* engine.validateImmutable(baselineRules)
|
|
26
|
-
* engine.recordApplication('R-001', 'session-xyz')
|
|
27
|
-
* engine.recordOutcome('R-001', 'session-xyz', 'good')
|
|
28
|
-
* engine.validateImmutableHash()
|
|
29
|
-
* engine.markPendingReview('R-200')
|
|
30
|
-
*/
|
|
31
|
-
|
|
32
|
-
const fs = require('fs');
|
|
33
|
-
const path = require('path');
|
|
34
|
-
const crypto = require('crypto');
|
|
35
|
-
|
|
36
|
-
// ═══════════════════════════════════════
|
|
37
|
-
// YAML loading (project-standard pattern)
|
|
38
|
-
// ═══════════════════════════════════════
|
|
39
|
-
|
|
40
|
-
const yaml = (() => {
|
|
41
|
-
try { return require('js-yaml'); } catch {
|
|
42
|
-
const candidates = [
|
|
43
|
-
path.resolve(__dirname, '../../.vendor/js-yaml.cjs'),
|
|
44
|
-
path.resolve(__dirname, '.vendor/js-yaml.cjs'),
|
|
45
|
-
];
|
|
46
|
-
if (process.env.KENAZ_SOURCE_DIR) {
|
|
47
|
-
candidates.unshift(path.join(process.env.KENAZ_SOURCE_DIR, '.vendor', 'js-yaml.cjs'));
|
|
48
|
-
}
|
|
49
|
-
for (const c of candidates) {
|
|
50
|
-
try { return require(c); } catch { /* continue */ }
|
|
51
|
-
}
|
|
52
|
-
throw new Error('js-yaml not found. Install it or place .vendor/js-yaml.cjs');
|
|
53
|
-
}
|
|
54
|
-
})();
|
|
55
|
-
|
|
56
|
-
// ═══════════════════════════════════════
|
|
57
|
-
// Path resolution
|
|
58
|
-
// ═══════════════════════════════════════
|
|
59
|
-
|
|
60
|
-
function findProjectRoot() {
|
|
61
|
-
let dir = __dirname;
|
|
62
|
-
while (dir !== path.dirname(dir)) {
|
|
63
|
-
const parent = path.dirname(dir);
|
|
64
|
-
if (fs.existsSync(path.join(parent, '.specialist'))) return parent;
|
|
65
|
-
dir = parent;
|
|
66
|
-
}
|
|
67
|
-
return process.cwd();
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
const BASE_DIR = findProjectRoot();
|
|
71
|
-
|
|
72
|
-
function getRulesPath(projectPath) {
|
|
73
|
-
const root = projectPath || BASE_DIR;
|
|
74
|
-
// Sub-projects store rules in .kenaz/specialist/
|
|
75
|
-
const kenazPath = path.join(root, '.kenaz', 'specialist', 'ansuz_rules.yaml');
|
|
76
|
-
if (fs.existsSync(kenazPath)) return kenazPath;
|
|
77
|
-
// Core project
|
|
78
|
-
const corePath = path.join(root, '.specialist', 'Ansuz', 'ansuz_rules.yaml');
|
|
79
|
-
if (fs.existsSync(corePath)) return corePath;
|
|
80
|
-
return kenazPath; // default to .kenaz path
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
// ═══════════════════════════════════════
|
|
84
|
-
// Ephemeral stats sidecar
|
|
85
|
-
// These fields mutate every session (autoTrackSession increments
|
|
86
|
-
// sessions_observed for all rules), so tracking them inside the
|
|
87
|
-
// git-tracked ansuz_rules.yaml causes a merge conflict on every pull.
|
|
88
|
-
// They live in an ignored sibling JSON instead; the YAML keeps only
|
|
89
|
-
// rule definitions + meaningful shared state (status/version/hash).
|
|
90
|
-
// ═══════════════════════════════════════
|
|
91
|
-
|
|
92
|
-
const STAT_FIELDS = ['sessions_observed', 'sessions_applied', 'good_outcomes', 'bad_outcomes'];
|
|
93
|
-
|
|
94
|
-
function getStatsPath(rulesPath) {
|
|
95
|
-
return rulesPath.replace(/ansuz_rules\.yaml$/, 'ansuz_rules_stats.json');
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
function loadStats(statsPath) {
|
|
99
|
-
try {
|
|
100
|
-
if (fs.existsSync(statsPath)) {
|
|
101
|
-
return JSON.parse(fs.readFileSync(statsPath, 'utf8')) || {};
|
|
102
|
-
}
|
|
103
|
-
} catch { /* corrupt sidecar → start fresh, definitions are the source of truth */ }
|
|
104
|
-
return {};
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
function saveStats(statsPath, rules) {
|
|
108
|
-
const stats = {};
|
|
109
|
-
for (const r of rules) {
|
|
110
|
-
const entry = {};
|
|
111
|
-
for (const f of STAT_FIELDS) {
|
|
112
|
-
if (r[f] !== undefined) entry[f] = r[f];
|
|
113
|
-
}
|
|
114
|
-
if (Object.keys(entry).length > 0) stats[r.id] = entry;
|
|
115
|
-
}
|
|
116
|
-
fs.writeFileSync(statsPath, JSON.stringify(stats, null, 2) + '\n', 'utf8');
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
/**
|
|
120
|
-
* Serialize rules to YAML with ephemeral stat fields stripped out.
|
|
121
|
-
* Deterministic given the definitions, so counter increments never churn the file.
|
|
122
|
-
*/
|
|
123
|
-
function dumpRulesYaml(_raw) {
|
|
124
|
-
const clone = JSON.parse(JSON.stringify(_raw));
|
|
125
|
-
if (Array.isArray(clone.rules)) {
|
|
126
|
-
for (const r of clone.rules) {
|
|
127
|
-
for (const f of STAT_FIELDS) delete r[f];
|
|
128
|
-
}
|
|
129
|
-
}
|
|
130
|
-
return yaml.dump(clone, { lineWidth: -1, noRefs: true, quotingType: '"' });
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
/** Persist both the (stat-stripped) YAML definitions and the ignored stats sidecar. */
|
|
134
|
-
function persist(filePath, statsPath, _raw, rules) {
|
|
135
|
-
fs.writeFileSync(filePath, dumpRulesYaml(_raw), 'utf8');
|
|
136
|
-
saveStats(statsPath, rules);
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
// ═══════════════════════════════════════
|
|
140
|
-
// Core functions
|
|
141
|
-
// ═══════════════════════════════════════
|
|
142
|
-
|
|
143
|
-
/**
|
|
144
|
-
* Load all rules from ansuz_rules.yaml.
|
|
145
|
-
* @param {string} [projectPath] - Optional project root override
|
|
146
|
-
* @returns {{ version: number, rules: object[], filePath: string }}
|
|
147
|
-
*/
|
|
148
|
-
function loadRules(projectPath) {
|
|
149
|
-
const filePath = getRulesPath(projectPath);
|
|
150
|
-
if (!fs.existsSync(filePath)) {
|
|
151
|
-
throw new Error(`ansuz_rules.yaml not found at: ${filePath}`);
|
|
152
|
-
}
|
|
153
|
-
const content = fs.readFileSync(filePath, 'utf8');
|
|
154
|
-
const data = yaml.load(content);
|
|
155
|
-
if (!data || !Array.isArray(data.rules)) {
|
|
156
|
-
throw new Error('Invalid ansuz_rules.yaml: missing rules array');
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
// Merge ephemeral stats from the ignored sidecar over any legacy values
|
|
160
|
-
// still present in the YAML (backward-compat: pre-migration YAMLs carry
|
|
161
|
-
// sessions_observed inline; the sidecar wins once it exists).
|
|
162
|
-
const statsPath = getStatsPath(filePath);
|
|
163
|
-
const stats = loadStats(statsPath);
|
|
164
|
-
for (const rule of data.rules) {
|
|
165
|
-
const s = stats[rule.id];
|
|
166
|
-
if (!s) continue;
|
|
167
|
-
for (const f of STAT_FIELDS) {
|
|
168
|
-
if (s[f] !== undefined) rule[f] = s[f];
|
|
169
|
-
}
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
return {
|
|
173
|
-
version: data.version || 1,
|
|
174
|
-
rules: data.rules,
|
|
175
|
-
filePath,
|
|
176
|
-
statsPath,
|
|
177
|
-
_raw: data,
|
|
178
|
-
};
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
/**
|
|
182
|
-
* Increment sessions_observed for a proposed rule and write back.
|
|
183
|
-
* @param {string} ruleId - Rule ID (e.g. 'R-200')
|
|
184
|
-
* @param {string} [projectPath] - Optional project root override
|
|
185
|
-
* @returns {{ ruleId: string, sessions_observed: number, sessions_required: number }}
|
|
186
|
-
*/
|
|
187
|
-
function trackSession(ruleId, projectPath) {
|
|
188
|
-
const { rules, filePath, statsPath, _raw } = loadRules(projectPath);
|
|
189
|
-
|
|
190
|
-
const rule = rules.find(r => r.id === ruleId);
|
|
191
|
-
if (!rule) {
|
|
192
|
-
throw new Error(`Rule not found: ${ruleId}`);
|
|
193
|
-
}
|
|
194
|
-
if (rule.status !== 'proposed') {
|
|
195
|
-
throw new Error(`Rule ${ruleId} is '${rule.status}', not 'proposed'. Only proposed rules can be tracked.`);
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
// Increment
|
|
199
|
-
rule.sessions_observed = (rule.sessions_observed || 0) + 1;
|
|
200
|
-
|
|
201
|
-
// Write back
|
|
202
|
-
persist(filePath, statsPath, _raw, rules);
|
|
203
|
-
|
|
204
|
-
return {
|
|
205
|
-
ruleId: rule.id,
|
|
206
|
-
sessions_observed: rule.sessions_observed,
|
|
207
|
-
sessions_required: rule.sessions_required || 3,
|
|
208
|
-
};
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
/**
|
|
212
|
-
* Auto-promote proposed rules where sessions_observed >= sessions_required.
|
|
213
|
-
* @param {string} [projectPath] - Optional project root override
|
|
214
|
-
* @returns {{ promoted: string[], unchanged: string[] }}
|
|
215
|
-
*/
|
|
216
|
-
function promoteRules(projectPath) {
|
|
217
|
-
const { rules, filePath, statsPath, _raw } = loadRules(projectPath);
|
|
218
|
-
|
|
219
|
-
const promoted = [];
|
|
220
|
-
const unchanged = [];
|
|
221
|
-
|
|
222
|
-
for (const rule of rules) {
|
|
223
|
-
if (rule.status !== 'proposed') continue;
|
|
224
|
-
|
|
225
|
-
const observed = rule.sessions_observed || 0;
|
|
226
|
-
const required = rule.sessions_required || 3;
|
|
227
|
-
|
|
228
|
-
if (observed >= required) {
|
|
229
|
-
// Outcome gate: refuse promotion if outcome_rate < 30%
|
|
230
|
-
const applied = Array.isArray(rule.sessions_applied) ? rule.sessions_applied.length : (rule.sessions_applied || 0);
|
|
231
|
-
const good = rule.good_outcomes || 0;
|
|
232
|
-
const outcomeRate = applied > 0 ? good / applied : 1.0; // no data = benefit of doubt
|
|
233
|
-
|
|
234
|
-
if (outcomeRate < 0.3) {
|
|
235
|
-
rule.status = 'deprecated';
|
|
236
|
-
rule.deprecated_reason = `Auto-deprecated: outcome_rate ${(outcomeRate * 100).toFixed(0)}% < 30% threshold after ${observed} sessions`;
|
|
237
|
-
promoted.push(`${rule.id} (DEPRECATED — outcome_rate ${(outcomeRate * 100).toFixed(0)}%)`);
|
|
238
|
-
} else {
|
|
239
|
-
rule.status = 'active';
|
|
240
|
-
promoted.push(rule.id);
|
|
241
|
-
}
|
|
242
|
-
} else {
|
|
243
|
-
unchanged.push(rule.id);
|
|
244
|
-
}
|
|
245
|
-
}
|
|
246
|
-
|
|
247
|
-
if (promoted.length > 0) {
|
|
248
|
-
// Bump file version
|
|
249
|
-
_raw.version = (_raw.version || 1) + 1;
|
|
250
|
-
_raw.timestamp = new Date().toISOString();
|
|
251
|
-
|
|
252
|
-
persist(filePath, statsPath, _raw, rules);
|
|
253
|
-
}
|
|
254
|
-
|
|
255
|
-
return { promoted, unchanged };
|
|
256
|
-
}
|
|
257
|
-
|
|
258
|
-
/**
|
|
259
|
-
* Validate that immutable rules have not been modified.
|
|
260
|
-
* Compares current immutable rules against a baseline snapshot.
|
|
261
|
-
* @param {object[]} baselineRules - Array of immutable rules from a known-good state
|
|
262
|
-
* @param {string} [projectPath] - Optional project root override
|
|
263
|
-
* @returns {{ valid: boolean, violations: string[] }}
|
|
264
|
-
*/
|
|
265
|
-
function validateImmutable(baselineRules, projectPath) {
|
|
266
|
-
const { rules } = loadRules(projectPath);
|
|
267
|
-
const violations = [];
|
|
268
|
-
|
|
269
|
-
const currentImmutable = rules.filter(r => r.status === 'immutable');
|
|
270
|
-
const baselineMap = new Map(baselineRules.map(r => [r.id, r]));
|
|
271
|
-
|
|
272
|
-
for (const rule of currentImmutable) {
|
|
273
|
-
const baseline = baselineMap.get(rule.id);
|
|
274
|
-
if (!baseline) continue; // new immutable rule, no baseline to compare
|
|
275
|
-
|
|
276
|
-
if (rule.rule !== baseline.rule) {
|
|
277
|
-
violations.push(`${rule.id}: rule text changed from "${baseline.rule}" to "${rule.rule}"`);
|
|
278
|
-
}
|
|
279
|
-
if (rule.version !== baseline.version) {
|
|
280
|
-
violations.push(`${rule.id}: version changed from ${baseline.version} to ${rule.version}`);
|
|
281
|
-
}
|
|
282
|
-
}
|
|
283
|
-
|
|
284
|
-
// Check for removed immutable rules
|
|
285
|
-
for (const [id, baseline] of baselineMap) {
|
|
286
|
-
if (!currentImmutable.find(r => r.id === id)) {
|
|
287
|
-
violations.push(`${id}: immutable rule removed`);
|
|
288
|
-
}
|
|
289
|
-
}
|
|
290
|
-
|
|
291
|
-
return { valid: violations.length === 0, violations };
|
|
292
|
-
}
|
|
293
|
-
|
|
294
|
-
// ═══════════════════════════════════════
|
|
295
|
-
// Performance metrics & review
|
|
296
|
-
// ═══════════════════════════════════════
|
|
297
|
-
|
|
298
|
-
/**
|
|
299
|
-
* Record that a rule was applied in a session.
|
|
300
|
-
* Appends sessionId to the rule's sessions_applied array.
|
|
301
|
-
* @param {string} ruleId - Rule ID (e.g. 'R-001')
|
|
302
|
-
* @param {string} sessionId - Session identifier
|
|
303
|
-
* @param {string} [projectPath] - Optional project root override
|
|
304
|
-
* @returns {{ ruleId: string, sessions_applied: string[] }}
|
|
305
|
-
*/
|
|
306
|
-
function recordApplication(ruleId, sessionId, projectPath) {
|
|
307
|
-
const { rules, filePath, statsPath, _raw } = loadRules(projectPath);
|
|
308
|
-
|
|
309
|
-
const rule = rules.find(r => r.id === ruleId);
|
|
310
|
-
if (!rule) {
|
|
311
|
-
throw new Error(`Rule not found: ${ruleId}`);
|
|
312
|
-
}
|
|
313
|
-
|
|
314
|
-
if (!Array.isArray(rule.sessions_applied)) {
|
|
315
|
-
rule.sessions_applied = [];
|
|
316
|
-
}
|
|
317
|
-
if (!rule.sessions_applied.includes(sessionId)) {
|
|
318
|
-
rule.sessions_applied.push(sessionId);
|
|
319
|
-
}
|
|
320
|
-
|
|
321
|
-
persist(filePath, statsPath, _raw, rules);
|
|
322
|
-
|
|
323
|
-
return { ruleId: rule.id, sessions_applied: rule.sessions_applied };
|
|
324
|
-
}
|
|
325
|
-
|
|
326
|
-
/**
|
|
327
|
-
* Record an outcome (good/bad) for a rule in a session.
|
|
328
|
-
* Increments good_outcomes or bad_outcomes counters.
|
|
329
|
-
* @param {string} ruleId - Rule ID
|
|
330
|
-
* @param {string} sessionId - Session identifier
|
|
331
|
-
* @param {'good'|'bad'} outcome - Outcome type
|
|
332
|
-
* @param {string} [projectPath] - Optional project root override
|
|
333
|
-
* @returns {{ ruleId: string, good_outcomes: number, bad_outcomes: number }}
|
|
334
|
-
*/
|
|
335
|
-
function recordOutcome(ruleId, sessionId, outcome, projectPath) {
|
|
336
|
-
if (outcome !== 'good' && outcome !== 'bad') {
|
|
337
|
-
throw new Error(`Invalid outcome: '${outcome}'. Must be 'good' or 'bad'.`);
|
|
338
|
-
}
|
|
339
|
-
|
|
340
|
-
const { rules, filePath, statsPath, _raw } = loadRules(projectPath);
|
|
341
|
-
|
|
342
|
-
const rule = rules.find(r => r.id === ruleId);
|
|
343
|
-
if (!rule) {
|
|
344
|
-
throw new Error(`Rule not found: ${ruleId}`);
|
|
345
|
-
}
|
|
346
|
-
|
|
347
|
-
// Ensure sessions_applied includes this session
|
|
348
|
-
if (!Array.isArray(rule.sessions_applied)) {
|
|
349
|
-
rule.sessions_applied = [];
|
|
350
|
-
}
|
|
351
|
-
if (!rule.sessions_applied.includes(sessionId)) {
|
|
352
|
-
rule.sessions_applied.push(sessionId);
|
|
353
|
-
}
|
|
354
|
-
|
|
355
|
-
// Increment outcome counter
|
|
356
|
-
if (outcome === 'good') {
|
|
357
|
-
rule.good_outcomes = (rule.good_outcomes || 0) + 1;
|
|
358
|
-
} else {
|
|
359
|
-
rule.bad_outcomes = (rule.bad_outcomes || 0) + 1;
|
|
360
|
-
}
|
|
361
|
-
|
|
362
|
-
persist(filePath, statsPath, _raw, rules);
|
|
363
|
-
|
|
364
|
-
return {
|
|
365
|
-
ruleId: rule.id,
|
|
366
|
-
good_outcomes: rule.good_outcomes || 0,
|
|
367
|
-
bad_outcomes: rule.bad_outcomes || 0,
|
|
368
|
-
};
|
|
369
|
-
}
|
|
370
|
-
|
|
371
|
-
/**
|
|
372
|
-
* Compute SHA256 hash of a rule's content (rule text + version).
|
|
373
|
-
* @param {object} rule - Rule object
|
|
374
|
-
* @returns {string} hex hash
|
|
375
|
-
*/
|
|
376
|
-
function computeRuleHash(rule) {
|
|
377
|
-
const content = `${rule.id}:${rule.rule}:${rule.version || 1}`;
|
|
378
|
-
return crypto.createHash('sha256').update(content, 'utf8').digest('hex');
|
|
379
|
-
}
|
|
380
|
-
|
|
381
|
-
/**
|
|
382
|
-
* Validate immutable rules by SHA256 hash comparison.
|
|
383
|
-
* Each immutable rule should have an immutable_hash field. If missing, the hash
|
|
384
|
-
* is computed and stored (first-run seeding). If present, the current hash is
|
|
385
|
-
* compared against the stored hash to detect unauthorized changes.
|
|
386
|
-
* @param {string} [projectPath] - Optional project root override
|
|
387
|
-
* @returns {{ valid: boolean, violations: string[], seeded: string[] }}
|
|
388
|
-
*/
|
|
389
|
-
function validateImmutableHash(projectPath) {
|
|
390
|
-
const { rules, filePath, statsPath, _raw } = loadRules(projectPath);
|
|
391
|
-
const violations = [];
|
|
392
|
-
const seeded = [];
|
|
393
|
-
|
|
394
|
-
const immutableRules = rules.filter(r => r.status === 'immutable');
|
|
395
|
-
let needsWrite = false;
|
|
396
|
-
|
|
397
|
-
for (const rule of immutableRules) {
|
|
398
|
-
const currentHash = computeRuleHash(rule);
|
|
399
|
-
|
|
400
|
-
if (!rule.immutable_hash) {
|
|
401
|
-
// First run: seed the hash
|
|
402
|
-
rule.immutable_hash = currentHash;
|
|
403
|
-
seeded.push(rule.id);
|
|
404
|
-
needsWrite = true;
|
|
405
|
-
} else if (rule.immutable_hash !== currentHash) {
|
|
406
|
-
violations.push(`${rule.id}: content drift detected (stored hash: ${rule.immutable_hash.slice(0, 12)}..., current: ${currentHash.slice(0, 12)}...)`);
|
|
407
|
-
}
|
|
408
|
-
}
|
|
409
|
-
|
|
410
|
-
if (needsWrite) {
|
|
411
|
-
persist(filePath, statsPath, _raw, rules);
|
|
412
|
-
}
|
|
413
|
-
|
|
414
|
-
return { valid: violations.length === 0, violations, seeded };
|
|
415
|
-
}
|
|
416
|
-
|
|
417
|
-
/**
|
|
418
|
-
* Auto-increment sessions_observed for ALL non-immutable, non-deprecated rules.
|
|
419
|
-
* Called at Phase 0 exit so every session counts toward observation thresholds.
|
|
420
|
-
* @param {string} [projectPath] - Optional project root override
|
|
421
|
-
* @returns {{ tracked: string[], skipped: string[], total: number }}
|
|
422
|
-
*/
|
|
423
|
-
function autoTrackSession(projectPath) {
|
|
424
|
-
const { rules, filePath, statsPath, _raw } = loadRules(projectPath);
|
|
425
|
-
|
|
426
|
-
const tracked = [];
|
|
427
|
-
const skipped = [];
|
|
428
|
-
|
|
429
|
-
for (const rule of rules) {
|
|
430
|
-
if (rule.status === 'immutable' || rule.status === 'deprecated') {
|
|
431
|
-
skipped.push(rule.id);
|
|
432
|
-
continue;
|
|
433
|
-
}
|
|
434
|
-
rule.sessions_observed = (rule.sessions_observed || 0) + 1;
|
|
435
|
-
tracked.push(rule.id);
|
|
436
|
-
}
|
|
437
|
-
|
|
438
|
-
if (tracked.length > 0) {
|
|
439
|
-
persist(filePath, statsPath, _raw, rules);
|
|
440
|
-
}
|
|
441
|
-
|
|
442
|
-
return { tracked, skipped, total: tracked.length };
|
|
443
|
-
}
|
|
444
|
-
|
|
445
|
-
/**
|
|
446
|
-
* Migrate any inline stat fields out of the YAML into the ignored sidecar.
|
|
447
|
-
* Idempotent: safe to run repeatedly. On first run it strips sessions_observed
|
|
448
|
-
* (etc.) from the tracked YAML and seeds ansuz_rules_stats.json.
|
|
449
|
-
* @param {string} [projectPath] - Optional project root override
|
|
450
|
-
* @returns {{ filePath: string, statsPath: string, ruleCount: number }}
|
|
451
|
-
*/
|
|
452
|
-
function migrateStats(projectPath) {
|
|
453
|
-
const { rules, filePath, statsPath, _raw } = loadRules(projectPath);
|
|
454
|
-
persist(filePath, statsPath, _raw, rules);
|
|
455
|
-
return { filePath, statsPath, ruleCount: rules.length };
|
|
456
|
-
}
|
|
457
|
-
|
|
458
|
-
/**
|
|
459
|
-
* Mark a rule as pending Amelia review.
|
|
460
|
-
* Sets pending_amelia_review = true and pending_review_since timestamp.
|
|
461
|
-
* @param {string} ruleId - Rule ID
|
|
462
|
-
* @param {string} [projectPath] - Optional project root override
|
|
463
|
-
* @returns {{ ruleId: string, pending_amelia_review: boolean, pending_review_since: string }}
|
|
464
|
-
*/
|
|
465
|
-
function markPendingReview(ruleId, projectPath) {
|
|
466
|
-
const { rules, filePath, statsPath, _raw } = loadRules(projectPath);
|
|
467
|
-
|
|
468
|
-
const rule = rules.find(r => r.id === ruleId);
|
|
469
|
-
if (!rule) {
|
|
470
|
-
throw new Error(`Rule not found: ${ruleId}`);
|
|
471
|
-
}
|
|
472
|
-
|
|
473
|
-
rule.pending_amelia_review = true;
|
|
474
|
-
rule.pending_review_since = new Date().toISOString();
|
|
475
|
-
|
|
476
|
-
persist(filePath, statsPath, _raw, rules);
|
|
477
|
-
|
|
478
|
-
return {
|
|
479
|
-
ruleId: rule.id,
|
|
480
|
-
pending_amelia_review: rule.pending_amelia_review,
|
|
481
|
-
pending_review_since: rule.pending_review_since,
|
|
482
|
-
};
|
|
483
|
-
}
|
|
484
|
-
|
|
485
|
-
// ═══════════════════════════════════════
|
|
486
|
-
// Module exports
|
|
487
|
-
// ═══════════════════════════════════════
|
|
488
|
-
|
|
489
|
-
module.exports = {
|
|
490
|
-
loadRules,
|
|
491
|
-
trackSession,
|
|
492
|
-
autoTrackSession,
|
|
493
|
-
promoteRules,
|
|
494
|
-
validateImmutable,
|
|
495
|
-
recordApplication,
|
|
496
|
-
recordOutcome,
|
|
497
|
-
validateImmutableHash,
|
|
498
|
-
markPendingReview,
|
|
499
|
-
migrateStats,
|
|
500
|
-
computeRuleHash,
|
|
501
|
-
};
|
|
502
|
-
|
|
503
|
-
// ═══════════════════════════════════════
|
|
504
|
-
// CLI
|
|
505
|
-
// ═══════════════════════════════════════
|
|
506
|
-
|
|
507
|
-
if (require.main === module) {
|
|
508
|
-
const args = process.argv.slice(2);
|
|
509
|
-
const command = args[0];
|
|
510
|
-
|
|
511
|
-
try {
|
|
512
|
-
switch (command) {
|
|
513
|
-
case 'load': {
|
|
514
|
-
const { version, rules } = loadRules();
|
|
515
|
-
console.log(`ansuz_rules v${version}: ${rules.length} rules`);
|
|
516
|
-
const byStatus = {};
|
|
517
|
-
for (const r of rules) {
|
|
518
|
-
byStatus[r.status] = (byStatus[r.status] || 0) + 1;
|
|
519
|
-
}
|
|
520
|
-
console.log('Status breakdown:', JSON.stringify(byStatus));
|
|
521
|
-
for (const r of rules) {
|
|
522
|
-
const extra = r.status === 'proposed'
|
|
523
|
-
? ` [${r.sessions_observed || 0}/${r.sessions_required || 3} sessions]`
|
|
524
|
-
: '';
|
|
525
|
-
console.log(` ${r.id} [${r.status}]${extra} ${r.rule}`);
|
|
526
|
-
}
|
|
527
|
-
break;
|
|
528
|
-
}
|
|
529
|
-
case 'track': {
|
|
530
|
-
const ruleId = args[1];
|
|
531
|
-
if (!ruleId) { console.error('Usage: rule-engine.js track <ruleId>'); process.exit(1); }
|
|
532
|
-
const result = trackSession(ruleId);
|
|
533
|
-
console.log(`Tracked ${result.ruleId}: ${result.sessions_observed}/${result.sessions_required} sessions`);
|
|
534
|
-
break;
|
|
535
|
-
}
|
|
536
|
-
case 'promote': {
|
|
537
|
-
const result = promoteRules();
|
|
538
|
-
if (result.promoted.length > 0) {
|
|
539
|
-
console.log(`Promoted: ${result.promoted.join(', ')}`);
|
|
540
|
-
} else {
|
|
541
|
-
console.log('No rules ready for promotion.');
|
|
542
|
-
}
|
|
543
|
-
if (result.unchanged.length > 0) {
|
|
544
|
-
console.log(`Still pending: ${result.unchanged.join(', ')}`);
|
|
545
|
-
}
|
|
546
|
-
break;
|
|
547
|
-
}
|
|
548
|
-
case 'validate': {
|
|
549
|
-
const { rules } = loadRules();
|
|
550
|
-
const immutableBaseline = rules.filter(r => r.status === 'immutable');
|
|
551
|
-
const result = validateImmutable(immutableBaseline);
|
|
552
|
-
if (result.valid) {
|
|
553
|
-
console.log('All immutable rules intact.');
|
|
554
|
-
} else {
|
|
555
|
-
console.error('VIOLATIONS:');
|
|
556
|
-
result.violations.forEach(v => console.error(` - ${v}`));
|
|
557
|
-
process.exit(1);
|
|
558
|
-
}
|
|
559
|
-
break;
|
|
560
|
-
}
|
|
561
|
-
case 'record-apply': {
|
|
562
|
-
const ruleId = args[1];
|
|
563
|
-
const sessionId = args[2];
|
|
564
|
-
if (!ruleId || !sessionId) { console.error('Usage: rule-engine.js record-apply <ruleId> <sessionId>'); process.exit(1); }
|
|
565
|
-
const result = recordApplication(ruleId, sessionId);
|
|
566
|
-
console.log(`Recorded application of ${result.ruleId} in session. Total sessions: ${result.sessions_applied.length}`);
|
|
567
|
-
break;
|
|
568
|
-
}
|
|
569
|
-
case 'record-outcome': {
|
|
570
|
-
const ruleId = args[1];
|
|
571
|
-
const sessionId = args[2];
|
|
572
|
-
const outcome = args[3];
|
|
573
|
-
if (!ruleId || !sessionId || !outcome) { console.error('Usage: rule-engine.js record-outcome <ruleId> <sessionId> <good|bad>'); process.exit(1); }
|
|
574
|
-
const result = recordOutcome(ruleId, sessionId, outcome);
|
|
575
|
-
console.log(`Recorded ${outcome} outcome for ${result.ruleId}. Good: ${result.good_outcomes}, Bad: ${result.bad_outcomes}`);
|
|
576
|
-
break;
|
|
577
|
-
}
|
|
578
|
-
case 'validate-hash': {
|
|
579
|
-
const result = validateImmutableHash();
|
|
580
|
-
if (result.seeded.length > 0) {
|
|
581
|
-
console.log(`Seeded hashes for: ${result.seeded.join(', ')}`);
|
|
582
|
-
}
|
|
583
|
-
if (result.valid) {
|
|
584
|
-
console.log('All immutable rule hashes valid.');
|
|
585
|
-
} else {
|
|
586
|
-
console.error('HASH VIOLATIONS:');
|
|
587
|
-
result.violations.forEach(v => console.error(` - ${v}`));
|
|
588
|
-
process.exit(1);
|
|
589
|
-
}
|
|
590
|
-
break;
|
|
591
|
-
}
|
|
592
|
-
case 'auto-track': {
|
|
593
|
-
const result = autoTrackSession();
|
|
594
|
-
console.log(`Auto-tracked ${result.total} rules: ${result.tracked.join(', ')}`);
|
|
595
|
-
if (result.skipped.length > 0) {
|
|
596
|
-
console.log(`Skipped (immutable/deprecated): ${result.skipped.join(', ')}`);
|
|
597
|
-
}
|
|
598
|
-
break;
|
|
599
|
-
}
|
|
600
|
-
case 'mark-review': {
|
|
601
|
-
const ruleId = args[1];
|
|
602
|
-
if (!ruleId) { console.error('Usage: rule-engine.js mark-review <ruleId>'); process.exit(1); }
|
|
603
|
-
const result = markPendingReview(ruleId);
|
|
604
|
-
console.log(`Marked ${result.ruleId} for Amelia review (since ${result.pending_review_since})`);
|
|
605
|
-
break;
|
|
606
|
-
}
|
|
607
|
-
case 'migrate-stats': {
|
|
608
|
-
const result = migrateStats();
|
|
609
|
-
console.log(`Migrated stats for ${result.ruleCount} rules → ${result.statsPath}`);
|
|
610
|
-
console.log(`YAML now definition-only: ${result.filePath}`);
|
|
611
|
-
break;
|
|
612
|
-
}
|
|
613
|
-
default:
|
|
614
|
-
console.log('Usage: rule-engine.js <load|track|promote|validate|record-apply|record-outcome|validate-hash|mark-review|migrate-stats> [args]');
|
|
615
|
-
process.exit(1);
|
|
616
|
-
}
|
|
617
|
-
} catch (err) {
|
|
618
|
-
console.error(`Error: ${err.message}`);
|
|
619
|
-
process.exit(1);
|
|
620
|
-
}
|
|
621
|
-
}
|