kenaz 0.3.18 → 0.3.20
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__/arms.test.js +41 -0
- package/core/.formation/tools/__tests__/diagnose-bug.test.js +10 -0
- package/core/.formation/tools/__tests__/m1-hmap-convid.test.js +1 -124
- package/core/.formation/tools/arms.js +48 -0
- package/core/.formation/tools/dispatch.js +24 -15
- package/core/.formation/tools/kenaz-stats.js +32 -8
- package/core/.formation/tools/kenaz-stats.test.js +15 -2
- package/core/.formation/tools/loader.js +17 -140
- package/core/.formation/tools/post-complete.js +0 -31
- package/core/.formation/tools/prompt.js +16 -1
- package/core/.formation/tools/prompt.test.js +20 -0
- 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/ansuz-boot-fast.js +7 -4
- package/core/.specialist/tools/ansuz-boot-fast.test.js +10 -3
- 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/peer-identity-e2e.js +402 -0
- package/core/dev/scripts/peer-identity-e2e.md +47 -0
- package/core/dev/scripts/relay-admin.js +94 -0
- package/core/dev/scripts/relay-admin.test.js +131 -0
- 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/README.md +10 -0
- 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/hooks/kenaz-hook.js +2 -1
- package/core/plugins/kenaz/hooks/sdk-gate.js +9 -0
- package/core/plugins/kenaz/scripts/kenaz.test.js +15 -3
- package/core/plugins/kenaz/scripts/sync.js +1 -1
- package/core/plugins/kenaz/scripts/ygg.js +8 -0
- package/core/plugins/kenaz/scripts/ygg.test.js +72 -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/core/plugins/kenaz-peer/hooks/register.test.ts +32 -0
- package/core/plugins/kenaz-peer/hooks/register.ts +9 -0
- 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
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
// TASK_2440: the one registry of prompt-level A/B arms (dev/research/self-improvement/DESIGN.md, "New prompt-level changes").
|
|
3
|
+
// A new prompt change = one entry here + its prompt branch in dispatch.js. Not a framework: a table and a hash.
|
|
4
|
+
const MS_DAY = 86400000;
|
|
5
|
+
|
|
6
|
+
// applies: which cards are eligible ('bug' | 'criteria'). salt '' is the TASK_2429 hash kept as it was; new arms use `${name}:`.
|
|
7
|
+
// minSample counts merged cards per arm; metric is the arm's own named metric besides first-pass merge.
|
|
8
|
+
const ARMS = [
|
|
9
|
+
{ name: 'diagnose', field: 'diagnose_arm', on: 'with', off: 'without', salt: '', flag: '--diagnose', applies: 'bug',
|
|
10
|
+
start: '2026-10-11', end: null, decide: '2026-11-11', minSample: 10, metric: 'first-pass', change: 'Bug diagnosis section appended to the prompt' },
|
|
11
|
+
{ name: 'contract', field: 'contract_arm', on: 'contract', off: 'generic', salt: 'contract:', flag: '--contract', applies: 'criteria',
|
|
12
|
+
start: '2026-10-12', end: null, decide: '2026-11-11', minSample: 20, metric: 'rework commits per merged card', change: 'numbered acceptance criteria answered one line each in the report' },
|
|
13
|
+
];
|
|
14
|
+
|
|
15
|
+
/** Stable FNV-1a bit of `${salt}${taskId}`: true -> on arm. */
|
|
16
|
+
function hashOn(taskId, salt) {
|
|
17
|
+
let h = 2166136261;
|
|
18
|
+
for (const ch of salt + String(taskId || '')) { h ^= ch.charCodeAt(0); h = Math.imul(h, 16777619) >>> 0; }
|
|
19
|
+
return (h >>> 7) % 2 === 0;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function active(arm, now) {
|
|
23
|
+
const t = +now;
|
|
24
|
+
return t >= Date.parse(arm.start) && (!arm.end || t < Date.parse(arm.end) + MS_DAY);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/** Label for one arm ('on'/'off' override forces it). Does not check eligibility or dates. */
|
|
28
|
+
function armLabel(arm, taskId, override) {
|
|
29
|
+
if (override === 'on') return arm.on;
|
|
30
|
+
if (override === 'off') return arm.off;
|
|
31
|
+
return hashOn(taskId, arm.salt) ? arm.on : arm.off;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* All arms for one card: { [arm.name]: label }. `eligible` = { bug: bool, criteria: bool }; `overrides` = { [name]: 'on'|'off' }.
|
|
36
|
+
* Arms outside [start, end], or not applying to the card, are absent. Each arm hashes on its own salt, so arms are independent.
|
|
37
|
+
*/
|
|
38
|
+
function assignArms(taskId, eligible, { now = Date.now(), overrides = {}, arms = ARMS } = {}) {
|
|
39
|
+
const out = {};
|
|
40
|
+
for (const arm of arms) {
|
|
41
|
+
if (!eligible[arm.applies]) continue;
|
|
42
|
+
if (!overrides[arm.name] && !active(arm, now)) continue;
|
|
43
|
+
out[arm.name] = armLabel(arm, taskId, overrides[arm.name]);
|
|
44
|
+
}
|
|
45
|
+
return out;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
module.exports = { ARMS, assignArms, armLabel, hashOn, active };
|
|
@@ -14,10 +14,11 @@ const path = require('path');
|
|
|
14
14
|
const fs = require('fs');
|
|
15
15
|
const { execSync } = require('child_process');
|
|
16
16
|
const { loadSkill, loadTask } = require('./loader');
|
|
17
|
+
const { ARMS, assignArms, armLabel } = require('./arms');
|
|
17
18
|
const {
|
|
18
19
|
buildPrompt, SUBAGENT_PREFIX, SUBAGENT_PREFIX_DIRECT, buildSubagentWorktreePrefix,
|
|
19
20
|
promptProfile, extractRigor, extractComplexity, splitPromptSections,
|
|
20
|
-
applyRustCheckToPrefix,
|
|
21
|
+
applyRustCheckToPrefix, cardCriteria,
|
|
21
22
|
} = require('./prompt');
|
|
22
23
|
const {
|
|
23
24
|
ROOT, loadDashboardModels,
|
|
@@ -648,14 +649,12 @@ function isBugCard(taskData) {
|
|
|
648
649
|
return /^\s*((fix(es|ed)?|bug)\b|修)/i.test(String(t.title || ''));
|
|
649
650
|
}
|
|
650
651
|
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
return (h >>> 7) % 2 === 0 ? 'with' : 'without';
|
|
658
|
-
}
|
|
652
|
+
// TASK_2440: arms live in arms.js (one registry); these keep the old names and the exact same hashes.
|
|
653
|
+
const armNamed = (n) => ARMS.find((a) => a.name === n);
|
|
654
|
+
/** 'with' | 'without': stable 50/50 split by task id, unless forced by --diagnose on|off. */
|
|
655
|
+
function diagnoseArmFor(taskId, override) { return armLabel(armNamed('diagnose'), taskId, override); }
|
|
656
|
+
/** 'contract' | 'generic' report arm (TASK_2439), salted so it is independent of the diagnosis arm; --contract on|off forces it. */
|
|
657
|
+
function contractArmFor(taskId, override) { return armLabel(armNamed('contract'), taskId, override); }
|
|
659
658
|
|
|
660
659
|
const BUG_DIAGNOSIS_SECTION = [
|
|
661
660
|
'## Bug diagnosis',
|
|
@@ -682,7 +681,7 @@ const BUG_DIAGNOSIS_SECTION = [
|
|
|
682
681
|
* complexity (TASK_1998 two-axis policy): the normalized 'simple'|'medium'|'complex'
|
|
683
682
|
* CONTEXT-volume key (extractComplexity() default). Optional for older readers.
|
|
684
683
|
*/
|
|
685
|
-
function appendDispatchPromptStats({ taskId, rigor, complexity, formation, promptText, model, modelSource, projectPath, diagnoseArm }) {
|
|
684
|
+
function appendDispatchPromptStats({ taskId, rigor, complexity, formation, promptText, model, modelSource, projectPath, diagnoseArm, contractArm, arms }) {
|
|
686
685
|
const root = projectPath || ROOT;
|
|
687
686
|
const dir = path.join(root, '.kenaz', 'specialist');
|
|
688
687
|
try {
|
|
@@ -698,6 +697,8 @@ function appendDispatchPromptStats({ taskId, rigor, complexity, formation, promp
|
|
|
698
697
|
model,
|
|
699
698
|
model_source: modelSource || null,
|
|
700
699
|
diagnose_arm: diagnoseArm || null,
|
|
700
|
+
contract_arm: contractArm || null,
|
|
701
|
+
arms: arms || null, // TASK_2440: every arm assigned to the card, {name: label}
|
|
701
702
|
});
|
|
702
703
|
fs.appendFileSync(path.join(dir, 'dispatch_prompt_stats.jsonl'), line + '\n', 'utf8');
|
|
703
704
|
} catch { /* best-effort — telemetry must never block dispatch */ }
|
|
@@ -785,6 +786,7 @@ Options:
|
|
|
785
786
|
--formation <name> Skill folder: unit (default) or solo
|
|
786
787
|
--skill <name> Skill to use (YAML filename without extension)
|
|
787
788
|
--task <value> Task ID (TASK_XXX) or free-text description
|
|
789
|
+
--contract on|off Force the report-contract A/B arm for a card with acceptance criteria (default: stable hash of the task id)
|
|
788
790
|
--diagnose on|off Force the bug-diagnosis A/B arm for a bug card (default: stable hash of the task id)
|
|
789
791
|
--dry-run Build and print the payload with no side effects
|
|
790
792
|
--dispatch-mode <mode> Required: subagent (JSON params for the Agent tool)
|
|
@@ -813,7 +815,7 @@ Options:
|
|
|
813
815
|
(with --release-lock: release a lock this session
|
|
814
816
|
cannot prove it owns)
|
|
815
817
|
|
|
816
|
-
Skills: unit: do
|
|
818
|
+
Skills: unit: do.
|
|
817
819
|
|
|
818
820
|
Sub-agent dispatch (Ansuz billing-aware path):
|
|
819
821
|
node .formation/tools/dispatch.js --skill=do --task=TASK_172 --dispatch-mode=subagent
|
|
@@ -915,7 +917,7 @@ Sub-agent dispatch (Ansuz billing-aware path):
|
|
|
915
917
|
console.error('❌ --dispatch-mode=subagent is required (Formation headless/squad/legion dispatch is retired)');
|
|
916
918
|
process.exit(1);
|
|
917
919
|
}
|
|
918
|
-
getSubagentDispatchParams({ formation, skill, task, projectPath, force, useWorktree: useWorktreeCli, dispatchModelOverride: dispatchModelCli, dryRun, harness: harnessCli, diagnose: getArg('--diagnose') }).then(result => {
|
|
920
|
+
getSubagentDispatchParams({ formation, skill, task, projectPath, force, useWorktree: useWorktreeCli, dispatchModelOverride: dispatchModelCli, dryRun, harness: harnessCli, diagnose: getArg('--diagnose'), contract: getArg('--contract') }).then(result => {
|
|
919
921
|
if (!result) { process.exitCode = 1; return; }
|
|
920
922
|
process.stdout.write(JSON.stringify(result, null, 2) + '\n');
|
|
921
923
|
}).catch(e => {
|
|
@@ -989,6 +991,7 @@ async function getSubagentDispatchParams(options) {
|
|
|
989
991
|
dryRun = false,
|
|
990
992
|
// TASK_2315: 'claude' (Agent tool) | 'codex' (Codex-native spawn_agent). Selects the payload shape.
|
|
991
993
|
harness = 'claude',
|
|
994
|
+
contract = null, // TASK_2439: --contract on|off forces the report-contract arm
|
|
992
995
|
diagnose = null, // TASK_2429: --diagnose on|off forces the bug-diagnosis arm
|
|
993
996
|
} = options;
|
|
994
997
|
if (harness !== 'claude' && harness !== 'codex') {
|
|
@@ -1246,9 +1249,13 @@ async function getSubagentDispatchParams(options) {
|
|
|
1246
1249
|
if (projectPath) promptOptions.projectPath = projectPath;
|
|
1247
1250
|
if (taskInput && taskInput.startsWith('TASK_')) promptOptions.taskId = taskInput;
|
|
1248
1251
|
|
|
1252
|
+
// TASK_2439: only cards with acceptance criteria get a contract arm; others keep today's generic line.
|
|
1253
|
+
const armSet = assignArms(taskInput, { bug: isBugCard(taskData), criteria: cardCriteria(taskData).length > 0 }, { overrides: { diagnose, contract } });
|
|
1254
|
+
const contractArm = armSet.contract || null;
|
|
1255
|
+
if (contractArm === 'contract') promptOptions.contract = true;
|
|
1249
1256
|
let rawPrompt = buildPrompt(skillDef, taskInput, taskData, promptOptions);
|
|
1250
1257
|
// TASK_2429: only bug cards get an arm; any other prompt stays byte-for-byte unchanged.
|
|
1251
|
-
const diagnoseArm =
|
|
1258
|
+
const diagnoseArm = armSet.diagnose || null;
|
|
1252
1259
|
if (diagnoseArm === 'with' && typeof rawPrompt === 'string') rawPrompt = rawPrompt + '\n\n' + BUG_DIAGNOSIS_SECTION + '\n';
|
|
1253
1260
|
|
|
1254
1261
|
// 4. Cortex context (same as Formation dispatch)
|
|
@@ -1394,7 +1401,7 @@ async function getSubagentDispatchParams(options) {
|
|
|
1394
1401
|
// by dashboard vs dashboard-by-rigor vs cli without re-deriving it.
|
|
1395
1402
|
// Two-axis policy: complexity rides along too, so dispatch-stats.js can
|
|
1396
1403
|
// group by (rigor, complexity) instead of rigor alone.
|
|
1397
|
-
if (!dryRun) appendDispatchPromptStats({ taskId: taskInput, rigor: resolution.rigorKey, complexity, formation, promptText, model: dispatchModel || rawModel, modelSource: resolution.source, projectPath, diagnoseArm });
|
|
1404
|
+
if (!dryRun) appendDispatchPromptStats({ taskId: taskInput, rigor: resolution.rigorKey, complexity, formation, promptText, model: dispatchModel || rawModel, modelSource: resolution.source, projectPath, diagnoseArm, contractArm, arms: Object.keys(armSet).length ? armSet : null });
|
|
1398
1405
|
|
|
1399
1406
|
// TASK_2032: mirror headless.js's formation-path auto-activation (~L272-290)
|
|
1400
1407
|
// on the subagent path — every gate/guard above has passed and this function
|
|
@@ -1455,6 +1462,8 @@ async function getSubagentDispatchParams(options) {
|
|
|
1455
1462
|
...(isCodex ? { harness: 'codex', cwd: crossWorktree ? crossWorktree.worktreePath : null, worktreePath: crossWorktree ? crossWorktree.worktreePath : null } : {}),
|
|
1456
1463
|
modelTranslation, isolation, ...(agentParams ? { agentParams } : {}), ...(executor ? { executor } : {}), rigor: resolution.rigorKey, complexity, modelSource: resolution.source,
|
|
1457
1464
|
...(diagnoseArm ? { diagnoseArm } : {}),
|
|
1465
|
+
...(contractArm ? { contractArm } : {}),
|
|
1466
|
+
...(Object.keys(armSet).length ? { arms: armSet } : {}),
|
|
1458
1467
|
...(dryRun ? { dryRun: true } : {}),
|
|
1459
1468
|
};
|
|
1460
1469
|
}
|
|
@@ -1505,4 +1514,4 @@ function setupSubagentCrossProjectWorktree(projectPath, taskInput) {
|
|
|
1505
1514
|
};
|
|
1506
1515
|
}
|
|
1507
1516
|
|
|
1508
|
-
module.exports = { isBugCard, diagnoseArmFor, BUG_DIAGNOSIS_SECTION, appendDispatchPromptStats, resolveDispatchModelForRigor, translateModelForAgentTool, DEFAULT_DISPATCH_MODEL, getSubagentDispatchParams, checkDispatchScopeGate, releaseDispatchLock, resolveDispatchIsolation, _getProvenSessionId, _getOwnConversationId, _isOwnClaimMarker, _buildCortexContext: buildCortexContext };
|
|
1517
|
+
module.exports = { isBugCard, diagnoseArmFor, contractArmFor, BUG_DIAGNOSIS_SECTION, appendDispatchPromptStats, resolveDispatchModelForRigor, translateModelForAgentTool, DEFAULT_DISPATCH_MODEL, getSubagentDispatchParams, checkDispatchScopeGate, releaseDispatchLock, resolveDispatchIsolation, _getProvenSessionId, _getOwnConversationId, _isOwnClaimMarker, _buildCortexContext: buildCortexContext };
|
|
@@ -13,6 +13,7 @@ const fs = require('fs');
|
|
|
13
13
|
const os = require('os');
|
|
14
14
|
const path = require('path');
|
|
15
15
|
const { execFileSync } = require('child_process');
|
|
16
|
+
const { ARMS } = require('./arms');
|
|
16
17
|
|
|
17
18
|
const DAY = 86400000;
|
|
18
19
|
const NO_DATA = 'no data';
|
|
@@ -211,21 +212,40 @@ function lessonStats({ home, cwd, now, days }) {
|
|
|
211
212
|
|
|
212
213
|
/** TASK_2429: bug cards per diagnosis arm (dispatch_prompt_stats.jsonl diagnose_arm) joined with the git first-pass verdicts of dispatchStats. */
|
|
213
214
|
function diagnoseAbStats({ cwd, now, days, dispatch }) {
|
|
215
|
+
return armStats({ cwd, now, days, dispatch, field: 'diagnose_arm', names: ['with', 'without'] });
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
/** TASK_2439: cards with acceptance criteria per report-contract arm, plus rework commits per arm. */
|
|
219
|
+
function contractAbStats({ cwd, now, days, dispatch }) {
|
|
220
|
+
return armStats({ cwd, now, days, dispatch, field: 'contract_arm', names: ['contract', 'generic'], commits: true });
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
/** TASK_2440: one row per registered arm (arms.js): cards, merged, first-pass and rework commits on/off, minimum sample, decision date. */
|
|
224
|
+
function armsStats({ cwd, now, days, dispatch }) {
|
|
225
|
+
return ARMS.map((arm) => {
|
|
226
|
+
const r = armStats({ cwd, now, days, dispatch, field: arm.field, names: [arm.on, arm.off], commits: true });
|
|
227
|
+
return { name: arm.name, metric: arm.metric, minSample: arm.minSample, decide: arm.decide, decisionPassed: now >= Date.parse(arm.decide), on: { label: arm.on, ...r[arm.on] }, off: { label: arm.off, ...r[arm.off] } };
|
|
228
|
+
});
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
function armStats({ cwd, now, days, dispatch, field, names, commits }) {
|
|
214
232
|
const arms = {}; // card number -> arm (latest dispatch record wins)
|
|
215
233
|
try {
|
|
216
234
|
for (const l of fs.readFileSync(path.join(cwd, '.kenaz', 'specialist', 'dispatch_prompt_stats.jsonl'), 'utf8').split('\n')) {
|
|
217
235
|
let r; try { r = JSON.parse(l); } catch (_) { continue; }
|
|
218
|
-
const m = r && r
|
|
219
|
-
if (m && (ms(r.ts) || 0) >= now - days * DAY) arms[m[1]] = r
|
|
236
|
+
const m = r && r[field] && /^TASK_(\d+)$/i.exec(r.task_id || '');
|
|
237
|
+
if (m && (ms(r.ts) || 0) >= now - days * DAY) arms[m[1]] = r[field];
|
|
220
238
|
}
|
|
221
239
|
} catch (_) { /* no records */ }
|
|
222
240
|
const rework = new Set(((dispatch && dispatch.rework) || []).map((x) => x.card));
|
|
223
241
|
const merged = new Set((dispatch && dispatch.mergedCards) || []);
|
|
242
|
+
const shas = new Map(((dispatch && dispatch.rework) || []).map((x) => [x.card, (x.shas || []).length]));
|
|
224
243
|
const out = {};
|
|
225
|
-
for (const arm of
|
|
244
|
+
for (const arm of names) {
|
|
226
245
|
const cards = Object.keys(arms).filter((n) => arms[n] === arm).map((n) => 'TASK_' + n);
|
|
227
246
|
const m = cards.filter((c) => merged.has(c));
|
|
228
247
|
out[arm] = { cards: cards.length, merged: m.length, firstPass: m.filter((c) => !rework.has(c)).length };
|
|
248
|
+
if (commits) out[arm].reworkCommits = m.reduce((n, c) => n + (shas.get(c) || 0), 0);
|
|
229
249
|
}
|
|
230
250
|
return out;
|
|
231
251
|
}
|
|
@@ -247,7 +267,7 @@ function selfImprovementStats({ home, cwd, now, days }) {
|
|
|
247
267
|
const prev = dispatchStats({ repo: cwd, now: now - days * DAY, days, until: now - days * DAY });
|
|
248
268
|
const pick = (d) => (d.firstPassRate == null ? null : { rate: d.firstPassRate, merged: d.merged });
|
|
249
269
|
return { source: src, ...require('../../.specialist/tools/lessons-harvest.js').selfStats({ projectPath: cwd, home, now, days }),
|
|
250
|
-
firstPass: { window: pick(cur), previous: pick(prev) },
|
|
270
|
+
firstPass: { window: pick(cur), previous: pick(prev) }, arms: armsStats({ cwd, now, days, dispatch: cur }),
|
|
251
271
|
glossary: glossaryStats({ cwd, now, days, cur }) };
|
|
252
272
|
} catch (e) { return { source: src, note: `${NO_DATA} (${e.message})` }; }
|
|
253
273
|
}
|
|
@@ -314,9 +334,13 @@ function render(s) {
|
|
|
314
334
|
` lessons by source: ${Object.entries(si.bySource).map(([k, v]) => `${k}=${v.lessons} (fired ${v.fired}, recurred before first fire ${v.recurBefore}, after ${v.recurAfter})`).join('; ') || 'none'}`,
|
|
315
335
|
` graduated ${si.graduated}; harvest items open ${si.harvestOpen}, recorded ${si.harvestRecorded}`,
|
|
316
336
|
` first-pass merge rate: this window ${r(si.firstPass.window)}, previous window ${r(si.firstPass.previous)}`);
|
|
317
|
-
if (si.
|
|
318
|
-
const
|
|
319
|
-
L.push(
|
|
337
|
+
if (si.arms && si.arms.length) {
|
|
338
|
+
const c = (x) => `${x.label} ${x.cards} cards, ${x.merged} merged, first-pass ${x.firstPass}/${x.merged}${x.merged ? ` (${(100 * x.firstPass / x.merged).toFixed(0)}%)` : ''}, ${x.reworkCommits} rework commits`;
|
|
339
|
+
L.push(' Prompt arms (.formation/tools/arms.js):');
|
|
340
|
+
for (const a of si.arms) {
|
|
341
|
+
const low = Math.min(a.on.merged, a.off.merged) < a.minSample;
|
|
342
|
+
L.push(` ${a.name}: ${c(a.on)} | ${c(a.off)}; own metric: ${a.metric}; decision date ${a.decide} ${a.decisionPassed ? 'passed' : 'not yet'}${low ? ` (below ${a.minSample} merged per arm: keep collecting, do not decide)` : ''}`);
|
|
343
|
+
}
|
|
320
344
|
}
|
|
321
345
|
const g = si.glossary;
|
|
322
346
|
if (g) L.push(` glossary: ${g.terms} terms, ${g.added} added in window; ${g.injections} injections in ${g.cards.length} cards`,
|
|
@@ -336,4 +360,4 @@ function main(argv, out = console.log, opts = {}) {
|
|
|
336
360
|
}
|
|
337
361
|
|
|
338
362
|
if (require.main === module) process.exit(main(process.argv.slice(2)));
|
|
339
|
-
module.exports = { diagnoseAbStats, main, collect, peerStats, dispatchStats, git, fieldStats, alayaStats, render };
|
|
363
|
+
module.exports = { armsStats, diagnoseAbStats, contractAbStats, main, collect, peerStats, dispatchStats, git, fieldStats, alayaStats, render };
|
|
@@ -174,8 +174,21 @@ test('diagnose A/B: bug cards and first-pass per arm from fake records (TASK_242
|
|
|
174
174
|
assert.deepStrictEqual(r.without, { cards: 3, merged: 2, firstPass: 1 });
|
|
175
175
|
assert.deepStrictEqual(S.diagnoseAbStats({ cwd: path.join(tmp, 'none'), now: NOW, days: 14, dispatch }).with, { cards: 0, merged: 0, firstPass: 0 });
|
|
176
176
|
const text = S.render({ days: 14, peer: { source: '-', note: 'no data' }, dispatch: { source: '-', note: 'no data' }, fieldReports: { source: '-', note: 'no data' }, alaya: { source: '-', note: 'no data' },
|
|
177
|
-
selfImprovement: { source: '-', nudges: 0, converted: 0, bySource: {}, graduated: 0, harvestOpen: 0, harvestRecorded: 0, firstPass: {},
|
|
178
|
-
assert.ok(text.includes('
|
|
177
|
+
selfImprovement: { source: '-', nudges: 0, converted: 0, bySource: {}, graduated: 0, harvestOpen: 0, harvestRecorded: 0, firstPass: {}, arms: S.armsStats({ cwd, now: NOW, days: 14, dispatch }) } });
|
|
178
|
+
assert.ok(text.includes('Prompt arms') && text.includes('diagnose: with 2 cards, 2 merged, first-pass 1/2 (50%), 0 rework commits | without 3 cards, 2 merged, first-pass 1/2 (50%), 0 rework commits; own metric: first-pass; decision date 2026-11-11'), text);
|
|
179
|
+
assert.ok(text.includes('(below 10 merged per arm: keep collecting, do not decide)') && text.includes('contract: contract 0 cards'), text);
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
test('contract A/B: cards, first-pass and rework commits per arm (TASK_2439)', () => {
|
|
183
|
+
const cwd = path.join(tmp, 'ct-proj');
|
|
184
|
+
const f = path.join(cwd, '.kenaz', 'specialist', 'dispatch_prompt_stats.jsonl');
|
|
185
|
+
fs.mkdirSync(path.dirname(f), { recursive: true });
|
|
186
|
+
const rec = (id, arm) => JSON.stringify({ ts: iso(1), task_id: id, contract_arm: arm, diagnose_arm: 'with' });
|
|
187
|
+
fs.writeFileSync(f, [rec('TASK_1', 'contract'), rec('TASK_2', 'contract'), rec('TASK_3', 'generic'), rec('TASK_4', 'generic'), rec('TASK_5', 'generic')].join('\n') + '\n');
|
|
188
|
+
const dispatch = { mergedCards: ['TASK_1', 'TASK_2', 'TASK_3', 'TASK_4'], rework: [{ card: 'TASK_2', shas: ['a'] }, { card: 'TASK_3', shas: ['b', 'c'] }, { card: 'TASK_4', shas: ['d'] }] };
|
|
189
|
+
const r = S.contractAbStats({ cwd, now: NOW, days: 14, dispatch });
|
|
190
|
+
assert.deepStrictEqual(r.contract, { cards: 2, merged: 2, firstPass: 1, reworkCommits: 1 });
|
|
191
|
+
assert.deepStrictEqual(r.generic, { cards: 3, merged: 2, firstPass: 0, reworkCommits: 3 });
|
|
179
192
|
});
|
|
180
193
|
|
|
181
194
|
test('main: --json parses and all four sections print; bad --days exits 2', () => {
|
|
@@ -1,11 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Formation Loader
|
|
3
|
-
* YAML
|
|
4
|
-
*
|
|
5
|
-
* 任務系統:
|
|
6
|
-
* 新系統 (.collaboration/tasks/) — TASK_151+ 目錄即狀態,YAML 格式
|
|
7
|
-
* 舊系統 (dev/pipeline/) — TASK_134~187 JSON 索引 + YAML 檔
|
|
8
|
-
* 查詢順序:新系統 → 舊系統 YAML → 舊系統 TASK_QUEUE.json
|
|
3
|
+
* YAML skill definitions + task loading (via task-api.js, which resolves
|
|
4
|
+
* .kenaz/tasks/ first and .collaboration/tasks/ as the legacy fallback).
|
|
9
5
|
*/
|
|
10
6
|
|
|
11
7
|
const { execSync } = require('child_process');
|
|
@@ -14,14 +10,10 @@ const fs = require('fs');
|
|
|
14
10
|
const yaml = (() => { try { return require('js-yaml'); } catch { return require('../../.vendor/js-yaml.cjs'); } })();
|
|
15
11
|
const { FORMATION_DIR, COLLABORATION_API, ROOT } = require('./utils');
|
|
16
12
|
|
|
17
|
-
// 舊系統路徑
|
|
18
|
-
const LEGACY_PIPELINE_DIR = path.join(ROOT, 'dev', 'pipeline');
|
|
19
|
-
const LEGACY_TASK_QUEUE = path.join(LEGACY_PIPELINE_DIR, 'TASK_QUEUE.json');
|
|
20
|
-
|
|
21
13
|
/**
|
|
22
|
-
*
|
|
23
|
-
* @param {string} formation -
|
|
24
|
-
* @param {string} skill -
|
|
14
|
+
* Load a skill YAML definition (.formation/<formation>/<skill>.yaml).
|
|
15
|
+
* @param {string} formation - formation name (unit)
|
|
16
|
+
* @param {string} skill - skill name
|
|
25
17
|
* @returns {object} parsed YAML
|
|
26
18
|
*/
|
|
27
19
|
function loadSkill(formation, skill) {
|
|
@@ -34,48 +26,33 @@ function loadSkill(formation, skill) {
|
|
|
34
26
|
}
|
|
35
27
|
|
|
36
28
|
/**
|
|
37
|
-
*
|
|
38
|
-
*
|
|
39
|
-
* 查詢順序:
|
|
40
|
-
* 1. .collaboration/tasks/ 或 .kenaz/tasks/ (新系統 — task-api.js get)
|
|
41
|
-
* 2. dev/pipeline/completed/ + tasks/ (舊系統 YAML 檔)
|
|
42
|
-
* 3. dev/pipeline/TASK_QUEUE.json (舊系統 JSON 索引)
|
|
29
|
+
* Load task data through task-api.js.
|
|
43
30
|
*
|
|
44
|
-
* @param {string} taskId - TASK_XXX
|
|
45
|
-
* @param {object} [options]
|
|
46
|
-
* @param {string} [options.projectPath] -
|
|
47
|
-
* @returns {string|null}
|
|
31
|
+
* @param {string} taskId - TASK_XXX
|
|
32
|
+
* @param {object} [options]
|
|
33
|
+
* @param {string} [options.projectPath] - sub-project path (looked up first)
|
|
34
|
+
* @returns {string|null} task YAML text, or null
|
|
48
35
|
*/
|
|
49
36
|
function loadTask(taskId, options) {
|
|
50
37
|
if (!taskId || !taskId.startsWith('TASK_')) return null;
|
|
51
38
|
|
|
52
39
|
const projectRoot = options?.projectPath || ROOT;
|
|
53
40
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
if (fromCollab) return fromCollab;
|
|
57
|
-
|
|
58
|
-
// 2. 舊系統 YAML 檔 (dev/pipeline/completed/ + tasks/)
|
|
59
|
-
const fromYaml = loadTaskFromLegacyYaml(taskId, projectRoot);
|
|
60
|
-
if (fromYaml) return fromYaml;
|
|
61
|
-
|
|
62
|
-
// 3. 舊系統 JSON 索引 (TASK_QUEUE.json)
|
|
63
|
-
const fromQueue = loadTaskFromLegacyQueue(taskId, projectRoot);
|
|
64
|
-
if (fromQueue) return fromQueue;
|
|
41
|
+
const fromApi = loadTaskFromApi(taskId, projectRoot);
|
|
42
|
+
if (fromApi) return fromApi;
|
|
65
43
|
|
|
66
|
-
console.warn(`⚠️ Task ${taskId} not found
|
|
44
|
+
console.warn(`⚠️ Task ${taskId} not found (root: ${projectRoot})`);
|
|
67
45
|
return null;
|
|
68
46
|
}
|
|
69
47
|
|
|
70
48
|
/**
|
|
71
|
-
* 從新系統 .collaboration 載入
|
|
72
49
|
* @param {string} taskId
|
|
73
|
-
* @param {string} projectRoot
|
|
50
|
+
* @param {string} projectRoot
|
|
74
51
|
*/
|
|
75
|
-
function
|
|
52
|
+
function loadTaskFromApi(taskId, projectRoot) {
|
|
76
53
|
if (!fs.existsSync(COLLABORATION_API)) return null;
|
|
77
54
|
try {
|
|
78
|
-
//
|
|
55
|
+
// When projectRoot differs from the core ROOT, pass --project-path
|
|
79
56
|
const projectArg = projectRoot !== ROOT ? ` --project-path "${projectRoot}"` : '';
|
|
80
57
|
const result = execSync(
|
|
81
58
|
`node "${COLLABORATION_API}"${projectArg} get ${taskId}`,
|
|
@@ -87,104 +64,4 @@ function loadTaskFromCollaboration(taskId, projectRoot) {
|
|
|
87
64
|
return null;
|
|
88
65
|
}
|
|
89
66
|
|
|
90
|
-
|
|
91
|
-
* 從舊系統 YAML 檔載入 (dev/pipeline/completed/ + tasks/)
|
|
92
|
-
* @param {string} taskId
|
|
93
|
-
* @param {string} projectRoot - 專案根目錄
|
|
94
|
-
*/
|
|
95
|
-
function loadTaskFromLegacyYaml(taskId, projectRoot) {
|
|
96
|
-
const pipelineDir = path.join(projectRoot, 'dev', 'pipeline');
|
|
97
|
-
const dirs = ['completed', 'tasks'];
|
|
98
|
-
for (const dir of dirs) {
|
|
99
|
-
const dirPath = path.join(pipelineDir, dir);
|
|
100
|
-
if (!fs.existsSync(dirPath)) continue;
|
|
101
|
-
try {
|
|
102
|
-
const files = fs.readdirSync(dirPath);
|
|
103
|
-
const match = files.find(f => f.startsWith(taskId) && f.endsWith('.yaml'));
|
|
104
|
-
if (match) {
|
|
105
|
-
return fs.readFileSync(path.join(dirPath, match), 'utf8').trim();
|
|
106
|
-
}
|
|
107
|
-
} catch (e) {}
|
|
108
|
-
}
|
|
109
|
-
return null;
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
/**
|
|
113
|
-
* 從舊系統 TASK_QUEUE.json 載入
|
|
114
|
-
* @param {string} taskId
|
|
115
|
-
* @param {string} projectRoot - 專案根目錄
|
|
116
|
-
*/
|
|
117
|
-
function loadTaskFromLegacyQueue(taskId, projectRoot) {
|
|
118
|
-
const queuePath = path.join(projectRoot, 'dev', 'pipeline', 'TASK_QUEUE.json');
|
|
119
|
-
if (!fs.existsSync(queuePath)) return null;
|
|
120
|
-
try {
|
|
121
|
-
const queue = JSON.parse(fs.readFileSync(queuePath, 'utf8'));
|
|
122
|
-
const task = (queue.tasks || []).find(t => t.id === taskId);
|
|
123
|
-
if (task) {
|
|
124
|
-
return yaml.dump(task, { lineWidth: -1 });
|
|
125
|
-
}
|
|
126
|
-
} catch (e) {}
|
|
127
|
-
return null;
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
/**
|
|
131
|
-
* 列出某個 formation 下所有可用的 skill
|
|
132
|
-
* @param {string} formation - 陣型名稱
|
|
133
|
-
* @returns {string[]} skill 名稱列表
|
|
134
|
-
*/
|
|
135
|
-
function listSkills(formation) {
|
|
136
|
-
const dir = path.join(FORMATION_DIR, formation);
|
|
137
|
-
if (!fs.existsSync(dir)) return [];
|
|
138
|
-
return fs.readdirSync(dir)
|
|
139
|
-
.filter(f => f.endsWith('.yaml'))
|
|
140
|
-
.map(f => f.replace('.yaml', ''));
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
/**
|
|
144
|
-
* 載入 specialist 定義(5 個 YAML 檔案)
|
|
145
|
-
* @param {string} name - specialist 名稱 (e.g., "Ansuz", "Amelia")
|
|
146
|
-
* @returns {object} { principles, workflow, knowledge, rule, capability }
|
|
147
|
-
*/
|
|
148
|
-
function loadSpecialist(name) {
|
|
149
|
-
const specialistDir = path.join(ROOT, '.specialist', name);
|
|
150
|
-
if (!fs.existsSync(specialistDir)) {
|
|
151
|
-
console.error(`❌ Specialist not found: ${specialistDir}`);
|
|
152
|
-
process.exit(1);
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
const files = ['principles', 'workflow', 'knowledge', 'rule', 'capability'];
|
|
156
|
-
const specialist = { name };
|
|
157
|
-
|
|
158
|
-
for (const file of files) {
|
|
159
|
-
const filePath = path.join(specialistDir, `${file}.yaml`);
|
|
160
|
-
if (!fs.existsSync(filePath)) {
|
|
161
|
-
console.warn(`⚠️ Missing ${file}.yaml for specialist ${name}`);
|
|
162
|
-
specialist[file] = null;
|
|
163
|
-
continue;
|
|
164
|
-
}
|
|
165
|
-
try {
|
|
166
|
-
specialist[file] = yaml.load(fs.readFileSync(filePath, 'utf8'));
|
|
167
|
-
} catch (e) {
|
|
168
|
-
console.error(`❌ Failed to parse ${filePath}: ${e.message}`);
|
|
169
|
-
process.exit(1);
|
|
170
|
-
}
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
return specialist;
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
/**
|
|
177
|
-
* 列出所有可用的 specialist
|
|
178
|
-
* @returns {string[]} specialist 名稱列表
|
|
179
|
-
*/
|
|
180
|
-
function listSpecialists() {
|
|
181
|
-
const specialistDir = path.join(ROOT, '.specialist');
|
|
182
|
-
if (!fs.existsSync(specialistDir)) return [];
|
|
183
|
-
return fs.readdirSync(specialistDir)
|
|
184
|
-
.filter(f => {
|
|
185
|
-
const stat = fs.statSync(path.join(specialistDir, f));
|
|
186
|
-
return stat.isDirectory();
|
|
187
|
-
});
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
module.exports = { loadSkill, loadTask, listSkills, loadSpecialist, listSpecialists };
|
|
67
|
+
module.exports = { loadSkill, loadTask };
|
|
@@ -54,7 +54,6 @@
|
|
|
54
54
|
* | 2.5d | Safety net: force-move to done | merge succeeded but complete() failed | spawnSync | task stays in active |
|
|
55
55
|
* | 2.6b | Ansuz inbox signal | result.taskCompleted && !dryRun | catch (silent) | Ansuz not notified |
|
|
56
56
|
* | 2.6c | Relay task_completed event (TASK_1409) | result.taskCompleted && !dryRun && relay | catch (silent) | PM inbox not notified cross-node |
|
|
57
|
-
* | 3 | Memory agent onTaskCompleted/onError | memoryAgent && taskId | catch (log warning) | memory event not queued |
|
|
58
57
|
*
|
|
59
58
|
* A.2 gitBranchAndMerge() — serial mode (L2344)
|
|
60
59
|
*
|
|
@@ -301,12 +300,6 @@ const { verify: verifyAcceptance } = require('./verify-acceptance');
|
|
|
301
300
|
// LLM-based acceptance verification (./verify-acceptance.js) remains and is the
|
|
302
301
|
// one whose result (result.git.acceptance) is actually read.
|
|
303
302
|
|
|
304
|
-
// Memory Agent(記憶系統整合)
|
|
305
|
-
let memoryAgent = null;
|
|
306
|
-
try {
|
|
307
|
-
memoryAgent = require('../../.memory/tools/memory-agent');
|
|
308
|
-
} catch { /* memory-agent not available — graceful degradation */ }
|
|
309
|
-
|
|
310
303
|
// Design Reasoning Attribution (TASK_1074)
|
|
311
304
|
let designReasoning = null;
|
|
312
305
|
try {
|
|
@@ -587,7 +580,6 @@ function listDaemonManagedPaths(projectRoot) {
|
|
|
587
580
|
|
|
588
581
|
scanDir('.synapse');
|
|
589
582
|
scanDir('.prefrontal');
|
|
590
|
-
scanDir('.memory');
|
|
591
583
|
if (fs.existsSync(path.join(projectRoot, '.tmp'))) discovered.add('.tmp/');
|
|
592
584
|
|
|
593
585
|
return Array.from(discovered);
|
|
@@ -3003,29 +2995,6 @@ function postComplete(opts = {}) {
|
|
|
3003
2995
|
} catch { /* fire-and-forget — never blocks post-complete */ }
|
|
3004
2996
|
}
|
|
3005
2997
|
|
|
3006
|
-
// Step 3: Memory Agent(記憶系統整合)
|
|
3007
|
-
if (memoryAgent && taskId) {
|
|
3008
|
-
try {
|
|
3009
|
-
const testsFailed = result.git?.regression && !result.git.regression.passed && !result.git.regression.skipped;
|
|
3010
|
-
if (testsFailed) {
|
|
3011
|
-
// 回歸測試失敗 → onError(fire-and-forget)
|
|
3012
|
-
memoryAgent.onError({
|
|
3013
|
-
event: 'regression_test_failure',
|
|
3014
|
-
taskId,
|
|
3015
|
-
error: `Regression tests failed after ${taskId} completion`,
|
|
3016
|
-
summary: result.git.regression.output?.slice(-500),
|
|
3017
|
-
}).catch(() => {});
|
|
3018
|
-
console.log(' 🧠 Memory: error event queued (regression failure)');
|
|
3019
|
-
} else {
|
|
3020
|
-
// 任務成功完成 → onTaskCompleted(fire-and-forget)
|
|
3021
|
-
memoryAgent.onTaskCompleted(taskId).catch(() => {});
|
|
3022
|
-
console.log(' 🧠 Memory: task completion event queued');
|
|
3023
|
-
}
|
|
3024
|
-
} catch (e) {
|
|
3025
|
-
console.log(` ⚠️ Memory agent skipped: ${e.message}`);
|
|
3026
|
-
}
|
|
3027
|
-
}
|
|
3028
|
-
|
|
3029
2998
|
// Step 4.6: Guard Failure Detector (TASK_1653)
|
|
3030
2999
|
// Runs shadow-mode failure detectors for active lesson guards that carry failure_signature.
|
|
3031
3000
|
// Reads guard snapshots directly from lesson_guards.yaml (not the task chart) because
|
|
@@ -145,6 +145,14 @@ function buildPrompt(skillDef, taskInput, taskData, options = {}) {
|
|
|
145
145
|
return buildSubagentPrompt(skillDef, taskInput, taskData, options);
|
|
146
146
|
}
|
|
147
147
|
|
|
148
|
+
/** Acceptance criteria of a JSON card (empty for YAML/raw or none). */
|
|
149
|
+
function cardCriteria(taskData) {
|
|
150
|
+
try {
|
|
151
|
+
const t = typeof taskData === 'string' ? JSON.parse(taskData) : taskData;
|
|
152
|
+
return t && Array.isArray(t.acceptance_criteria) ? t.acceptance_criteria.map(String) : [];
|
|
153
|
+
} catch { return []; }
|
|
154
|
+
}
|
|
155
|
+
|
|
148
156
|
/**
|
|
149
157
|
* Short prompt for Agent-tool subagent dispatch (both harnesses): task card,
|
|
150
158
|
* commit command, verification expectation, report format. The worktree hard
|
|
@@ -187,7 +195,13 @@ function buildSubagentPrompt(skillDef, taskInput, taskData, options = {}) {
|
|
|
187
195
|
lines.push(`Blocked: \`${buildTaskApiRewriter(options)(onBlocked.run).replace(/\{task_id\}/g, taskId)}\` and explain.`);
|
|
188
196
|
}
|
|
189
197
|
for (const c of learned.checks) lines.push(`Project check: ${c.slice(2)}`);
|
|
190
|
-
|
|
198
|
+
const criteria = options.contract ? cardCriteria(taskData) : [];
|
|
199
|
+
if (criteria.length) {
|
|
200
|
+
// TASK_2439: the report answers each acceptance criterion with evidence (A/B arm, DESIGN.md).
|
|
201
|
+
lines.push('Final report <= 20 lines: files touched, tests run, commit sha, unverified items, and one line per criterion:');
|
|
202
|
+
criteria.forEach((ac, i) => lines.push(`${i + 1}. ${ac}`));
|
|
203
|
+
lines.push('Answer each as met / not met / not verified + evidence (test name and result, file:line, or command output).');
|
|
204
|
+
} else lines.push('Final report <= 20 lines: files touched, tests run, commit sha, unverified items.');
|
|
191
205
|
lines.push('');
|
|
192
206
|
appendDispatchOverrides(lines, taskData);
|
|
193
207
|
return lines.join('\n');
|
|
@@ -666,6 +680,7 @@ Details: .formation/tools/dispatch-detail/cross-project-worktree.md (Read only i
|
|
|
666
680
|
}
|
|
667
681
|
|
|
668
682
|
module.exports = {
|
|
683
|
+
cardCriteria,
|
|
669
684
|
recordSkillUsage,
|
|
670
685
|
buildPrompt,
|
|
671
686
|
SUBAGENT_PREFIX,
|
|
@@ -328,6 +328,26 @@ test('TASK_2391: subagent prompt keeps card + commit rule + report, drops boiler
|
|
|
328
328
|
assert.ok(body.length < 2500, `body too long: ${body.length}`);
|
|
329
329
|
});
|
|
330
330
|
|
|
331
|
+
test('TASK_2439: contract arm lists numbered criteria; off arm and cards without criteria keep the generic line', () => {
|
|
332
|
+
const card = { id: 'TASK_9002', title: 'T', description: 'D', scope_paths: ['src/a.js'], acceptance_criteria: ['AC-A', 'AC-B', 'AC-C'] };
|
|
333
|
+
const was = process.cwd();
|
|
334
|
+
const build = (c, contract) => buildPrompt(loadDoSkill(), 'TASK_9002', JSON.stringify(c), { isSubagent: true, taskId: 'TASK_9002', ...(contract ? { contract: true } : {}) });
|
|
335
|
+
let on, off, plain, noAcOn, noAcOff;
|
|
336
|
+
try {
|
|
337
|
+
process.chdir(fs.mkdtempSync(path.join(os.tmpdir(), 'p2439-')));
|
|
338
|
+
on = build(card, true); off = build(card, false); plain = build(card);
|
|
339
|
+
const bare = { ...card }; delete bare.acceptance_criteria;
|
|
340
|
+
noAcOn = build(bare, true); noAcOff = build(bare, false);
|
|
341
|
+
} finally { process.chdir(was); }
|
|
342
|
+
const generic = 'Final report <= 20 lines: files touched, tests run, commit sha, unverified items.\n';
|
|
343
|
+
assert.ok(on.includes('1. AC-A\n2. AC-B\n3. AC-C\n'));
|
|
344
|
+
assert.ok(on.includes('met / not met / not verified + evidence'));
|
|
345
|
+
assert.ok(!on.includes(generic));
|
|
346
|
+
assert.strictEqual(off, plain, "off arm = today's prompt");
|
|
347
|
+
assert.ok(off.includes(generic) && !off.includes('not verified +'));
|
|
348
|
+
assert.ok(noAcOn.includes(generic) && noAcOn === noAcOff);
|
|
349
|
+
});
|
|
350
|
+
|
|
331
351
|
test('TASK_2391: subagent prompt gets the Rust check only for a Rust-scoped card', () => {
|
|
332
352
|
const rustCard = JSON.stringify({ id: 'TASK_9002', title: 'R', scope_paths: ['dashboard/src-tauri/src/x.rs'] });
|
|
333
353
|
const withRust = buildPrompt(loadDoSkill(), 'TASK_9002', rustCard, { isSubagent: true, taskId: 'TASK_9002', rustCoreCheck: () => true });
|
package/core/.rule/INDEX.yaml
CHANGED
|
@@ -24,8 +24,6 @@ load_order:
|
|
|
24
24
|
description: "GEN-009: 跨平台相容原則 - Windows/macOS/Linux 三平台相容性要求"
|
|
25
25
|
- path: "general/agent-workflow.yaml"
|
|
26
26
|
description: "Agent 工作流程 - 記憶點優先 + 邊界意識 + Context Integrity Canary(AGT-001 至 AGT-009)"
|
|
27
|
-
- path: "general/sunny-access-control.yaml"
|
|
28
|
-
description: "SEC-010: Sunny 系統存取控制 - 禁止其他 Agent 存取 .system/sunny/"
|
|
29
27
|
- path: "general/synapse-code-intelligence.yaml"
|
|
30
28
|
description: "SYN-001~007: 代碼導航 — kenaz_lsp_* 代碼導航、Grep/Glob 文字與概念搜尋。記憶層 synapse_* 工具已於 2026-10-11 移除(TASK_2411)"
|
|
31
29
|
# 2026-10-11 (TASK_2411): synapse-cognitive-fusion.yaml, cortex-tools.yaml, memory-source-priority.yaml, memory-language.yaml deleted - memory system removed.
|
|
@@ -41,14 +39,6 @@ load_order:
|
|
|
41
39
|
description: "VERIFY-TGT-001~002: Rust 驗證正典 CARGO_TARGET_DIR — 單一共用目錄(dev/scripts/verify-target-dir.js),禁止自創目錄;並行驗證被 cargo 檔案鎖串行化屬正常等待"
|
|
42
40
|
- path: "general/task-pool-routing.yaml"
|
|
43
41
|
description: "POOL-001~003: 本地任務 vs 任務池 路由規則 — 詞彙驅動路由、術語一致性、Pool 操作安全(publish/claim 破壞性語義)"
|
|
44
|
-
- path: "general/pipeline.yaml"
|
|
45
|
-
description: "Pipeline 系統規則 - 任務管理規範"
|
|
46
|
-
- path: "pipeline/PPL-002-completed-task-archival.yaml"
|
|
47
|
-
description: "已完成任務封存規則 - 防止佇列污染"
|
|
48
|
-
- path: "pipeline/auto-update.yaml"
|
|
49
|
-
description: "PPL-003 - 任務完成後必須更新 Pipeline 系統(Critical)"
|
|
50
|
-
- path: "pipeline/task-execution.yaml"
|
|
51
|
-
description: "PPL-101 至 PPL-113 - Pipeline 任務執行規範(拆分、依賴、並行、回歸保護等)"
|
|
52
42
|
|
|
53
43
|
# Code 規則組 - 從核心原則到具體實踐
|
|
54
44
|
- path: "code/principles.yaml"
|
|
@@ -70,14 +60,6 @@ load_order:
|
|
|
70
60
|
|
|
71
61
|
- path: "design/ui.yaml"
|
|
72
62
|
description: "UI 設計規範"
|
|
73
|
-
- path: "design/ux.yaml"
|
|
74
|
-
description: "UX 設計原則"
|
|
75
|
-
- path: "design/colors.yaml"
|
|
76
|
-
description: "色彩系統"
|
|
77
|
-
- path: "architecture/decisions.yaml"
|
|
78
|
-
description: "架構決策記錄"
|
|
79
|
-
- path: "business/constraints.yaml"
|
|
80
|
-
description: "業務邏輯約束"
|
|
81
63
|
|
|
82
64
|
# 嚴重程度定義
|
|
83
65
|
severity_levels:
|