@soleri/core 9.2.0 → 9.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/data/flows/build.flow.yaml +8 -9
- package/data/flows/deliver.flow.yaml +9 -10
- package/data/flows/design.flow.yaml +3 -4
- package/data/flows/enhance.flow.yaml +5 -6
- package/data/flows/explore.flow.yaml +3 -4
- package/data/flows/fix.flow.yaml +5 -6
- package/data/flows/plan.flow.yaml +4 -5
- package/data/flows/review.flow.yaml +3 -4
- package/dist/curator/curator.d.ts.map +1 -1
- package/dist/curator/curator.js +98 -22
- package/dist/curator/curator.js.map +1 -1
- package/dist/engine/bin/soleri-engine.js.map +1 -1
- package/dist/engine/module-manifest.d.ts +2 -0
- package/dist/engine/module-manifest.d.ts.map +1 -1
- package/dist/engine/module-manifest.js +136 -1
- package/dist/engine/module-manifest.js.map +1 -1
- package/dist/engine/register-engine.d.ts.map +1 -1
- package/dist/engine/register-engine.js +25 -1
- package/dist/engine/register-engine.js.map +1 -1
- package/dist/flows/gate-evaluator.js.map +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/dist/operator/operator-profile.d.ts.map +1 -1
- package/dist/operator/operator-profile.js +11 -5
- package/dist/operator/operator-profile.js.map +1 -1
- package/dist/operator/operator-signals.d.ts.map +1 -1
- package/dist/operator/operator-signals.js.map +1 -1
- package/dist/planning/evidence-collector.js.map +1 -1
- package/dist/planning/gap-passes.d.ts.map +1 -1
- package/dist/planning/gap-passes.js +23 -6
- package/dist/planning/gap-passes.js.map +1 -1
- package/dist/planning/gap-patterns.d.ts.map +1 -1
- package/dist/planning/gap-patterns.js +57 -11
- package/dist/planning/gap-patterns.js.map +1 -1
- package/dist/planning/github-projection.d.ts.map +1 -1
- package/dist/planning/github-projection.js +39 -20
- package/dist/planning/github-projection.js.map +1 -1
- package/dist/planning/impact-analyzer.d.ts.map +1 -1
- package/dist/planning/impact-analyzer.js +20 -18
- package/dist/planning/impact-analyzer.js.map +1 -1
- package/dist/planning/plan-lifecycle.d.ts.map +1 -1
- package/dist/planning/plan-lifecycle.js +22 -9
- package/dist/planning/plan-lifecycle.js.map +1 -1
- package/dist/planning/planner.d.ts.map +1 -1
- package/dist/planning/planner.js +60 -17
- package/dist/planning/planner.js.map +1 -1
- package/dist/planning/rationalization-detector.d.ts.map +1 -1
- package/dist/planning/rationalization-detector.js.map +1 -1
- package/dist/planning/reconciliation-engine.d.ts.map +1 -1
- package/dist/planning/reconciliation-engine.js.map +1 -1
- package/dist/planning/task-complexity-assessor.d.ts +42 -0
- package/dist/planning/task-complexity-assessor.d.ts.map +1 -0
- package/dist/planning/task-complexity-assessor.js +132 -0
- package/dist/planning/task-complexity-assessor.js.map +1 -0
- package/dist/planning/task-verifier.d.ts.map +1 -1
- package/dist/planning/task-verifier.js +14 -6
- package/dist/planning/task-verifier.js.map +1 -1
- package/dist/runtime/admin-ops.d.ts.map +1 -1
- package/dist/runtime/admin-ops.js +18 -0
- package/dist/runtime/admin-ops.js.map +1 -1
- package/dist/runtime/admin-setup-ops.d.ts.map +1 -1
- package/dist/runtime/admin-setup-ops.js +2 -1
- package/dist/runtime/admin-setup-ops.js.map +1 -1
- package/dist/runtime/branching-ops.d.ts +12 -0
- package/dist/runtime/branching-ops.d.ts.map +1 -0
- package/dist/runtime/branching-ops.js +100 -0
- package/dist/runtime/branching-ops.js.map +1 -0
- package/dist/runtime/context-health.d.ts.map +1 -1
- package/dist/runtime/context-health.js.map +1 -1
- package/dist/runtime/facades/branching-facade.d.ts +7 -0
- package/dist/runtime/facades/branching-facade.d.ts.map +1 -0
- package/dist/runtime/facades/branching-facade.js +8 -0
- package/dist/runtime/facades/branching-facade.js.map +1 -0
- package/dist/runtime/facades/chat-service-ops.d.ts.map +1 -1
- package/dist/runtime/facades/chat-service-ops.js +3 -1
- package/dist/runtime/facades/chat-service-ops.js.map +1 -1
- package/dist/runtime/facades/chat-transport-ops.d.ts.map +1 -1
- package/dist/runtime/facades/chat-transport-ops.js.map +1 -1
- package/dist/runtime/facades/index.d.ts.map +1 -1
- package/dist/runtime/facades/index.js +42 -0
- package/dist/runtime/facades/index.js.map +1 -1
- package/dist/runtime/facades/intake-facade.d.ts +9 -0
- package/dist/runtime/facades/intake-facade.d.ts.map +1 -0
- package/dist/runtime/facades/intake-facade.js +11 -0
- package/dist/runtime/facades/intake-facade.js.map +1 -0
- package/dist/runtime/facades/links-facade.d.ts +9 -0
- package/dist/runtime/facades/links-facade.d.ts.map +1 -0
- package/dist/runtime/facades/links-facade.js +10 -0
- package/dist/runtime/facades/links-facade.js.map +1 -0
- package/dist/runtime/facades/operator-facade.d.ts.map +1 -1
- package/dist/runtime/facades/operator-facade.js.map +1 -1
- package/dist/runtime/facades/plan-facade.d.ts.map +1 -1
- package/dist/runtime/facades/plan-facade.js +4 -1
- package/dist/runtime/facades/plan-facade.js.map +1 -1
- package/dist/runtime/facades/tier-facade.d.ts +7 -0
- package/dist/runtime/facades/tier-facade.d.ts.map +1 -0
- package/dist/runtime/facades/tier-facade.js +8 -0
- package/dist/runtime/facades/tier-facade.js.map +1 -0
- package/dist/runtime/facades/vault-facade.d.ts +9 -1
- package/dist/runtime/facades/vault-facade.d.ts.map +1 -1
- package/dist/runtime/facades/vault-facade.js +44 -187
- package/dist/runtime/facades/vault-facade.js.map +1 -1
- package/dist/runtime/github-integration.d.ts.map +1 -1
- package/dist/runtime/github-integration.js +11 -4
- package/dist/runtime/github-integration.js.map +1 -1
- package/dist/runtime/orchestrate-ops.d.ts.map +1 -1
- package/dist/runtime/orchestrate-ops.js +75 -42
- package/dist/runtime/orchestrate-ops.js.map +1 -1
- package/dist/runtime/planning-extra-ops.d.ts.map +1 -1
- package/dist/runtime/planning-extra-ops.js.map +1 -1
- package/dist/runtime/runtime.d.ts.map +1 -1
- package/dist/runtime/runtime.js +3 -1
- package/dist/runtime/runtime.js.map +1 -1
- package/dist/runtime/session-briefing.d.ts.map +1 -1
- package/dist/runtime/session-briefing.js +5 -1
- package/dist/runtime/session-briefing.js.map +1 -1
- package/dist/runtime/tier-ops.d.ts +13 -0
- package/dist/runtime/tier-ops.d.ts.map +1 -0
- package/dist/runtime/tier-ops.js +110 -0
- package/dist/runtime/tier-ops.js.map +1 -0
- package/dist/skills/sync-skills.d.ts.map +1 -1
- package/dist/skills/sync-skills.js +1 -1
- package/dist/skills/sync-skills.js.map +1 -1
- package/dist/vault/linking.d.ts.map +1 -1
- package/dist/vault/linking.js +41 -5
- package/dist/vault/linking.js.map +1 -1
- package/dist/vault/vault-entries.d.ts.map +1 -1
- package/dist/vault/vault-entries.js +68 -26
- package/dist/vault/vault-entries.js.map +1 -1
- package/dist/vault/vault-maintenance.d.ts.map +1 -1
- package/dist/vault/vault-maintenance.js +6 -2
- package/dist/vault/vault-maintenance.js.map +1 -1
- package/dist/vault/vault-markdown-sync.d.ts.map +1 -1
- package/dist/vault/vault-markdown-sync.js.map +1 -1
- package/dist/vault/vault-memories.d.ts.map +1 -1
- package/dist/vault/vault-memories.js +3 -1
- package/dist/vault/vault-memories.js.map +1 -1
- package/dist/vault/vault-schema.js +36 -10
- package/dist/vault/vault-schema.js.map +1 -1
- package/dist/vault/vault.d.ts.map +1 -1
- package/dist/vault/vault.js +5 -1
- package/dist/vault/vault.js.map +1 -1
- package/package.json +7 -7
- package/src/agency/agency-manager.test.ts +60 -40
- package/src/agency/default-rules.test.ts +17 -9
- package/src/capabilities/registry.test.ts +2 -12
- package/src/chat/agent-loop.test.ts +33 -43
- package/src/chat/mcp-bridge.test.ts +7 -2
- package/src/claudemd/inject.test.ts +2 -12
- package/src/context/context-engine.test.ts +96 -51
- package/src/control/intent-router.test.ts +3 -3
- package/src/curator/classifier.test.ts +14 -8
- package/src/curator/contradiction-detector.test.ts +30 -5
- package/src/curator/curator.ts +278 -56
- package/src/curator/duplicate-detector.test.ts +77 -15
- package/src/curator/quality-gate.test.ts +71 -31
- package/src/curator/tag-manager.test.ts +12 -4
- package/src/domain-packs/knowledge-installer.test.ts +2 -10
- package/src/domain-packs/token-resolver.test.ts +1 -3
- package/src/domain-packs/types.test.ts +16 -2
- package/src/enforcement/registry.test.ts +2 -8
- package/src/engine/bin/soleri-engine.ts +3 -1
- package/src/engine/module-manifest.test.ts +48 -4
- package/src/engine/module-manifest.ts +138 -1
- package/src/engine/register-engine.test.ts +6 -1
- package/src/engine/register-engine.ts +26 -3
- package/src/errors/classify.test.ts +6 -2
- package/src/errors/retry.test.ts +1 -4
- package/src/facades/facade-factory.test.ts +110 -64
- package/src/flows/epilogue.test.ts +16 -10
- package/src/flows/gate-evaluator.test.ts +12 -6
- package/src/flows/gate-evaluator.ts +1 -3
- package/src/governance/governance.test.ts +137 -21
- package/src/health/health-registry.test.ts +8 -1
- package/src/index.ts +8 -0
- package/src/intake/content-classifier.test.ts +121 -51
- package/src/intake/dedup-gate.test.ts +38 -22
- package/src/intake/intake-pipeline.test.ts +5 -3
- package/src/intake/text-ingester.test.ts +26 -20
- package/src/llm/key-pool.test.ts +1 -3
- package/src/llm/llm-client.test.ts +1 -4
- package/src/llm/oauth-discovery.test.ts +16 -16
- package/src/llm/utils.test.ts +62 -18
- package/src/logging/logger.test.ts +4 -1
- package/src/loop/loop-manager.test.ts +2 -6
- package/src/migrations/migration-runner.edge-cases.test.ts +2 -7
- package/src/operator/operator-profile-extended.test.ts +15 -5
- package/src/operator/operator-profile.test.ts +26 -8
- package/src/operator/operator-profile.ts +38 -22
- package/src/operator/operator-signals-extended.test.ts +35 -23
- package/src/operator/operator-signals.test.ts +6 -10
- package/src/operator/operator-signals.ts +2 -1
- package/src/operator/prompts/hook-precompact-operator-dispatch.md +10 -6
- package/src/operator/prompts/subagent-soft-signal-extractor.md +5 -0
- package/src/operator/prompts/subagent-synthesis-cognition.md +19 -10
- package/src/operator/prompts/subagent-synthesis-communication.md +13 -7
- package/src/operator/prompts/subagent-synthesis-technical.md +19 -9
- package/src/operator/prompts/subagent-synthesis-trust.md +27 -21
- package/src/persona/defaults.test.ts +1 -5
- package/src/planning/evidence-collector.test.ts +147 -38
- package/src/planning/evidence-collector.ts +1 -4
- package/src/planning/gap-analysis-alternatives.test.ts +41 -11
- package/src/planning/gap-passes.test.ts +215 -33
- package/src/planning/gap-passes.ts +115 -46
- package/src/planning/gap-patterns.test.ts +87 -13
- package/src/planning/gap-patterns.ts +114 -31
- package/src/planning/github-projection.test.ts +6 -1
- package/src/planning/github-projection.ts +41 -20
- package/src/planning/impact-analyzer.test.ts +10 -23
- package/src/planning/impact-analyzer.ts +33 -46
- package/src/planning/plan-lifecycle.test.ts +103 -36
- package/src/planning/plan-lifecycle.ts +49 -18
- package/src/planning/planner.test.ts +12 -2
- package/src/planning/planner.ts +198 -58
- package/src/planning/rationalization-detector.test.ts +5 -20
- package/src/planning/rationalization-detector.ts +14 -16
- package/src/planning/reconciliation-engine.test.ts +20 -3
- package/src/planning/reconciliation-engine.ts +1 -2
- package/src/planning/task-complexity-assessor.test.ts +298 -0
- package/src/planning/task-complexity-assessor.ts +183 -0
- package/src/planning/task-verifier.test.ts +59 -27
- package/src/planning/task-verifier.ts +15 -9
- package/src/playbooks/playbook-executor.test.ts +1 -3
- package/src/plugins/plugin-loader.test.ts +19 -14
- package/src/plugins/plugin-registry.test.ts +45 -33
- package/src/project/project-registry.test.ts +23 -12
- package/src/prompts/template-manager.test.ts +4 -1
- package/src/queue/job-queue.test.ts +10 -14
- package/src/runtime/admin-extra-ops.test.ts +5 -19
- package/src/runtime/admin-ops.test.ts +22 -1
- package/src/runtime/admin-ops.ts +19 -0
- package/src/runtime/admin-setup-ops.test.ts +3 -4
- package/src/runtime/admin-setup-ops.ts +9 -2
- package/src/runtime/archive-ops.test.ts +4 -1
- package/src/runtime/branching-ops.test.ts +144 -0
- package/src/runtime/branching-ops.ts +107 -0
- package/src/runtime/capture-ops.test.ts +7 -21
- package/src/runtime/chain-ops.test.ts +16 -6
- package/src/runtime/claude-md-helpers.test.ts +1 -3
- package/src/runtime/context-health.test.ts +1 -3
- package/src/runtime/context-health.ts +1 -3
- package/src/runtime/curator-extra-ops.test.ts +3 -1
- package/src/runtime/domain-ops.test.ts +46 -36
- package/src/runtime/facades/admin-facade.test.ts +1 -4
- package/src/runtime/facades/archive-facade.test.ts +21 -7
- package/src/runtime/facades/brain-facade.test.ts +176 -72
- package/src/runtime/facades/branching-facade.test.ts +43 -0
- package/src/runtime/facades/branching-facade.ts +11 -0
- package/src/runtime/facades/chat-facade.test.ts +81 -28
- package/src/runtime/facades/chat-service-ops.test.ts +178 -73
- package/src/runtime/facades/chat-service-ops.ts +3 -1
- package/src/runtime/facades/chat-session-ops.test.ts +25 -10
- package/src/runtime/facades/chat-transport-ops.test.ts +101 -34
- package/src/runtime/facades/chat-transport-ops.ts +0 -1
- package/src/runtime/facades/context-facade.test.ts +19 -4
- package/src/runtime/facades/control-facade.test.ts +3 -3
- package/src/runtime/facades/index.ts +42 -0
- package/src/runtime/facades/intake-facade.test.ts +215 -0
- package/src/runtime/facades/intake-facade.ts +14 -0
- package/src/runtime/facades/links-facade.test.ts +203 -0
- package/src/runtime/facades/links-facade.ts +13 -0
- package/src/runtime/facades/loop-facade.test.ts +22 -5
- package/src/runtime/facades/memory-facade.test.ts +19 -5
- package/src/runtime/facades/operator-facade.test.ts +17 -4
- package/src/runtime/facades/operator-facade.ts +11 -3
- package/src/runtime/facades/orchestrate-facade.test.ts +7 -1
- package/src/runtime/facades/plan-facade.test.ts +29 -12
- package/src/runtime/facades/plan-facade.ts +7 -2
- package/src/runtime/facades/tier-facade.test.ts +47 -0
- package/src/runtime/facades/tier-facade.ts +11 -0
- package/src/runtime/facades/vault-facade.test.ts +174 -242
- package/src/runtime/facades/vault-facade.ts +55 -199
- package/src/runtime/github-integration.ts +11 -8
- package/src/runtime/grading-ops.test.ts +39 -8
- package/src/runtime/intake-ops.test.ts +69 -16
- package/src/runtime/loop-ops.test.ts +16 -6
- package/src/runtime/memory-cross-project-ops.test.ts +25 -14
- package/src/runtime/orchestrate-ops.test.ts +204 -0
- package/src/runtime/orchestrate-ops.ts +103 -65
- package/src/runtime/pack-ops.test.ts +23 -6
- package/src/runtime/planning-extra-ops.test.ts +17 -7
- package/src/runtime/planning-extra-ops.ts +3 -1
- package/src/runtime/playbook-ops.test.ts +26 -3
- package/src/runtime/plugin-ops.test.ts +83 -25
- package/src/runtime/project-ops.test.ts +26 -6
- package/src/runtime/runtime.ts +3 -1
- package/src/runtime/session-briefing.test.ts +183 -54
- package/src/runtime/session-briefing.ts +8 -2
- package/src/runtime/sync-ops.test.ts +3 -12
- package/src/runtime/telemetry-ops.test.ts +31 -6
- package/src/runtime/tier-ops.test.ts +159 -0
- package/src/runtime/tier-ops.ts +119 -0
- package/src/runtime/vault-extra-ops.test.ts +32 -8
- package/src/runtime/vault-sharing-ops.test.ts +1 -4
- package/src/skills/sync-skills.ts +2 -12
- package/src/transport/ws-server.test.ts +7 -4
- package/src/vault/__tests__/vault-characterization.test.ts +492 -81
- package/src/vault/linking.test.ts +50 -17
- package/src/vault/linking.ts +48 -7
- package/src/vault/obsidian-sync.test.ts +6 -3
- package/src/vault/scope-detector.test.ts +1 -3
- package/src/vault/vault-branching.test.ts +9 -7
- package/src/vault/vault-entries.ts +209 -65
- package/src/vault/vault-maintenance.ts +7 -12
- package/src/vault/vault-manager.test.ts +10 -10
- package/src/vault/vault-markdown-sync.ts +4 -1
- package/src/vault/vault-memories.ts +7 -7
- package/src/vault/vault-scaling.test.ts +5 -5
- package/src/vault/vault-schema.ts +72 -15
- package/src/vault/vault.ts +55 -9
- package/src/brain/strength-scorer.ts +0 -404
- package/src/engine/index.ts +0 -21
- package/src/persona/index.ts +0 -9
- package/src/vault/vault-interfaces.ts +0 -56
|
@@ -0,0 +1,298 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import {
|
|
3
|
+
assessTaskComplexity,
|
|
4
|
+
type AssessmentInput,
|
|
5
|
+
type AssessmentResult,
|
|
6
|
+
} from './task-complexity-assessor.js';
|
|
7
|
+
|
|
8
|
+
// ─── Helpers ────────────────────────────────────────────────────────
|
|
9
|
+
|
|
10
|
+
function assess(partial: Partial<AssessmentInput> & { prompt: string }): AssessmentResult {
|
|
11
|
+
return assessTaskComplexity(partial);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function signalByName(result: AssessmentResult, name: string) {
|
|
15
|
+
return result.signals.find((s) => s.name === name);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
// ─── Simple Tasks ───────────────────────────────────────────────────
|
|
19
|
+
|
|
20
|
+
describe('assessTaskComplexity — simple tasks', () => {
|
|
21
|
+
it('classifies "rename variable X" as simple', () => {
|
|
22
|
+
const result = assess({ prompt: 'rename variable X to Y' });
|
|
23
|
+
expect(result.classification).toBe('simple');
|
|
24
|
+
expect(result.score).toBeLessThan(40);
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
it('classifies "fix typo in README" as simple', () => {
|
|
28
|
+
const result = assess({ prompt: 'fix typo in README' });
|
|
29
|
+
expect(result.classification).toBe('simple');
|
|
30
|
+
expect(result.score).toBeLessThan(40);
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
it('classifies "add CSS class" as simple', () => {
|
|
34
|
+
const result = assess({ prompt: 'add CSS class to the header' });
|
|
35
|
+
expect(result.classification).toBe('simple');
|
|
36
|
+
expect(result.score).toBeLessThan(40);
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
it('classifies single-file estimate as simple', () => {
|
|
40
|
+
const result = assess({ prompt: 'update button color', filesEstimated: 1 });
|
|
41
|
+
expect(result.classification).toBe('simple');
|
|
42
|
+
expect(signalByName(result, 'file-count')!.triggered).toBe(false);
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
it('classifies task with 2 files as simple', () => {
|
|
46
|
+
const result = assess({ prompt: 'update two files', filesEstimated: 2 });
|
|
47
|
+
expect(result.classification).toBe('simple');
|
|
48
|
+
expect(signalByName(result, 'file-count')!.triggered).toBe(false);
|
|
49
|
+
});
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
// ─── Complex Tasks ──────────────────────────────────────────────────
|
|
53
|
+
|
|
54
|
+
describe('assessTaskComplexity — complex tasks', () => {
|
|
55
|
+
it('classifies "add authentication" touching multiple files as complex', () => {
|
|
56
|
+
const result = assess({ prompt: 'add authentication to the API', filesEstimated: 4 });
|
|
57
|
+
expect(result.classification).toBe('complex');
|
|
58
|
+
expect(result.score).toBeGreaterThanOrEqual(40);
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
it('classifies "refactor the vault module" as complex via cross-cutting when combined with files', () => {
|
|
62
|
+
const result = assess({ prompt: 'refactor across the vault module', filesEstimated: 5 });
|
|
63
|
+
expect(result.classification).toBe('complex');
|
|
64
|
+
expect(result.score).toBeGreaterThanOrEqual(40);
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
it('classifies "migrate database schema" touching multiple files as complex', () => {
|
|
68
|
+
const result = assess({ prompt: 'migrate database schema to v2', filesEstimated: 3 });
|
|
69
|
+
expect(result.classification).toBe('complex');
|
|
70
|
+
expect(signalByName(result, 'cross-cutting-keywords')!.triggered).toBe(true);
|
|
71
|
+
expect(signalByName(result, 'file-count')!.triggered).toBe(true);
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
it('classifies many-file task with design decision as complex', () => {
|
|
75
|
+
const result = assess({ prompt: 'how should we update styles across the app', filesEstimated: 5 });
|
|
76
|
+
expect(result.classification).toBe('complex');
|
|
77
|
+
expect(result.score).toBeGreaterThanOrEqual(40);
|
|
78
|
+
expect(signalByName(result, 'file-count')!.triggered).toBe(true);
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
it('classifies task with design decision as complex', () => {
|
|
82
|
+
const result = assess({
|
|
83
|
+
prompt: 'how should we structure the new cache layer',
|
|
84
|
+
filesEstimated: 3,
|
|
85
|
+
});
|
|
86
|
+
expect(result.classification).toBe('complex');
|
|
87
|
+
expect(signalByName(result, 'design-decisions-needed')!.triggered).toBe(true);
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
it('classifies task with new dependency as complex', () => {
|
|
91
|
+
const result = assess({
|
|
92
|
+
prompt: 'add a new package for rate limiting and install it',
|
|
93
|
+
filesEstimated: 3,
|
|
94
|
+
});
|
|
95
|
+
expect(result.classification).toBe('complex');
|
|
96
|
+
expect(signalByName(result, 'new-dependencies')!.triggered).toBe(true);
|
|
97
|
+
});
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
// ─── Edge Cases ─────────────────────────────────────────────────────
|
|
101
|
+
|
|
102
|
+
describe('assessTaskComplexity — edge cases', () => {
|
|
103
|
+
it('handles empty prompt as simple', () => {
|
|
104
|
+
const result = assess({ prompt: '' });
|
|
105
|
+
expect(result.classification).toBe('simple');
|
|
106
|
+
expect(result.score).toBe(0);
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
it('clamps score to 0 minimum (negative weights only)', () => {
|
|
110
|
+
const result = assess({
|
|
111
|
+
prompt: 'do the thing',
|
|
112
|
+
hasParentPlan: true,
|
|
113
|
+
});
|
|
114
|
+
expect(result.score).toBe(0);
|
|
115
|
+
expect(result.classification).toBe('simple');
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
it('clamps score to 100 maximum', () => {
|
|
119
|
+
const result = assess({
|
|
120
|
+
prompt: 'add authentication, migrate the DB, install new package, how should we design this, refactor across all modules',
|
|
121
|
+
filesEstimated: 10,
|
|
122
|
+
domains: ['vault', 'brain', 'planning'],
|
|
123
|
+
});
|
|
124
|
+
expect(result.score).toBeLessThanOrEqual(100);
|
|
125
|
+
expect(result.score).toBeGreaterThanOrEqual(0);
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
it('parent context reduces complexity', () => {
|
|
129
|
+
const withoutParent = assess({
|
|
130
|
+
prompt: 'add authorization to the API',
|
|
131
|
+
filesEstimated: 4,
|
|
132
|
+
});
|
|
133
|
+
const withParent = assess({
|
|
134
|
+
prompt: 'add authorization to the API',
|
|
135
|
+
filesEstimated: 4,
|
|
136
|
+
hasParentPlan: true,
|
|
137
|
+
});
|
|
138
|
+
expect(withParent.score).toBeLessThan(withoutParent.score);
|
|
139
|
+
expect(signalByName(withParent, 'approach-already-described')!.triggered).toBe(true);
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
it('parentIssueContext also reduces complexity', () => {
|
|
143
|
+
const result = assess({
|
|
144
|
+
prompt: 'add authorization to the API',
|
|
145
|
+
filesEstimated: 4,
|
|
146
|
+
parentIssueContext: 'Use middleware pattern as described in RFC-42',
|
|
147
|
+
});
|
|
148
|
+
expect(signalByName(result, 'approach-already-described')!.triggered).toBe(true);
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
it('borderline score at exactly 40 is complex', () => {
|
|
152
|
+
// file-count (25) + new-dependencies (15) = 40
|
|
153
|
+
const result = assess({
|
|
154
|
+
prompt: 'install the redis package',
|
|
155
|
+
filesEstimated: 3,
|
|
156
|
+
});
|
|
157
|
+
expect(result.score).toBe(40);
|
|
158
|
+
expect(result.classification).toBe('complex');
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
it('borderline score at 39 is simple', () => {
|
|
162
|
+
// file-count (25) + new-dependencies (15) + approach-described (-15) = 25
|
|
163
|
+
const result = assess({
|
|
164
|
+
prompt: 'install the redis package',
|
|
165
|
+
filesEstimated: 3,
|
|
166
|
+
hasParentPlan: true,
|
|
167
|
+
});
|
|
168
|
+
expect(result.score).toBeLessThan(40);
|
|
169
|
+
expect(result.classification).toBe('simple');
|
|
170
|
+
});
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
// ─── Individual Signals ─────────────────────────────────────────────
|
|
174
|
+
|
|
175
|
+
describe('assessTaskComplexity — individual signals', () => {
|
|
176
|
+
describe('file-count signal', () => {
|
|
177
|
+
it('triggers at 3 files', () => {
|
|
178
|
+
const result = assess({ prompt: 'task', filesEstimated: 3 });
|
|
179
|
+
expect(signalByName(result, 'file-count')!.triggered).toBe(true);
|
|
180
|
+
expect(signalByName(result, 'file-count')!.weight).toBe(25);
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
it('does not trigger at 2 files', () => {
|
|
184
|
+
const result = assess({ prompt: 'task', filesEstimated: 2 });
|
|
185
|
+
expect(signalByName(result, 'file-count')!.triggered).toBe(false);
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
it('does not trigger when no estimate provided', () => {
|
|
189
|
+
const result = assess({ prompt: 'task' });
|
|
190
|
+
expect(signalByName(result, 'file-count')!.triggered).toBe(false);
|
|
191
|
+
});
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
describe('cross-cutting-keywords signal', () => {
|
|
195
|
+
it.each([
|
|
196
|
+
'add authentication',
|
|
197
|
+
'implement authorization',
|
|
198
|
+
'migrate the database',
|
|
199
|
+
'refactor across modules',
|
|
200
|
+
'handle cross-cutting concerns',
|
|
201
|
+
])('triggers for: "%s"', (prompt) => {
|
|
202
|
+
const result = assess({ prompt });
|
|
203
|
+
expect(signalByName(result, 'cross-cutting-keywords')!.triggered).toBe(true);
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
it('does not trigger for benign text', () => {
|
|
207
|
+
const result = assess({ prompt: 'fix button alignment' });
|
|
208
|
+
expect(signalByName(result, 'cross-cutting-keywords')!.triggered).toBe(false);
|
|
209
|
+
});
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
describe('new-dependencies signal', () => {
|
|
213
|
+
it.each([
|
|
214
|
+
'add dependency for caching',
|
|
215
|
+
'install redis',
|
|
216
|
+
'new package for validation',
|
|
217
|
+
'npm install lodash',
|
|
218
|
+
])('triggers for: "%s"', (prompt) => {
|
|
219
|
+
const result = assess({ prompt });
|
|
220
|
+
expect(signalByName(result, 'new-dependencies')!.triggered).toBe(true);
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
it('does not trigger for normal text', () => {
|
|
224
|
+
const result = assess({ prompt: 'update existing code' });
|
|
225
|
+
expect(signalByName(result, 'new-dependencies')!.triggered).toBe(false);
|
|
226
|
+
});
|
|
227
|
+
});
|
|
228
|
+
|
|
229
|
+
describe('design-decisions-needed signal', () => {
|
|
230
|
+
it.each([
|
|
231
|
+
'how should we handle caching',
|
|
232
|
+
'which approach for the API',
|
|
233
|
+
'design decision on storage',
|
|
234
|
+
'architectural decision for events',
|
|
235
|
+
'evaluate the trade-off between speed and accuracy',
|
|
236
|
+
])('triggers for: "%s"', (prompt) => {
|
|
237
|
+
const result = assess({ prompt });
|
|
238
|
+
expect(signalByName(result, 'design-decisions-needed')!.triggered).toBe(true);
|
|
239
|
+
});
|
|
240
|
+
});
|
|
241
|
+
|
|
242
|
+
describe('approach-already-described signal', () => {
|
|
243
|
+
it('triggers with hasParentPlan', () => {
|
|
244
|
+
const result = assess({ prompt: 'task', hasParentPlan: true });
|
|
245
|
+
const signal = signalByName(result, 'approach-already-described')!;
|
|
246
|
+
expect(signal.triggered).toBe(true);
|
|
247
|
+
expect(signal.weight).toBe(-15);
|
|
248
|
+
});
|
|
249
|
+
|
|
250
|
+
it('triggers with parentIssueContext', () => {
|
|
251
|
+
const result = assess({ prompt: 'task', parentIssueContext: 'Steps described here' });
|
|
252
|
+
expect(signalByName(result, 'approach-already-described')!.triggered).toBe(true);
|
|
253
|
+
});
|
|
254
|
+
|
|
255
|
+
it('does not trigger with empty parentIssueContext', () => {
|
|
256
|
+
const result = assess({ prompt: 'task', parentIssueContext: ' ' });
|
|
257
|
+
expect(signalByName(result, 'approach-already-described')!.triggered).toBe(false);
|
|
258
|
+
});
|
|
259
|
+
});
|
|
260
|
+
|
|
261
|
+
describe('multi-domain signal', () => {
|
|
262
|
+
it('triggers with 2+ domains', () => {
|
|
263
|
+
const result = assess({ prompt: 'task', domains: ['vault', 'brain'] });
|
|
264
|
+
expect(signalByName(result, 'multi-domain')!.triggered).toBe(true);
|
|
265
|
+
expect(signalByName(result, 'multi-domain')!.weight).toBe(5);
|
|
266
|
+
});
|
|
267
|
+
|
|
268
|
+
it('does not trigger with single domain', () => {
|
|
269
|
+
const result = assess({ prompt: 'task', domains: ['vault'] });
|
|
270
|
+
expect(signalByName(result, 'multi-domain')!.triggered).toBe(false);
|
|
271
|
+
});
|
|
272
|
+
|
|
273
|
+
it('does not trigger with no domains', () => {
|
|
274
|
+
const result = assess({ prompt: 'task' });
|
|
275
|
+
expect(signalByName(result, 'multi-domain')!.triggered).toBe(false);
|
|
276
|
+
});
|
|
277
|
+
});
|
|
278
|
+
});
|
|
279
|
+
|
|
280
|
+
// ─── Reasoning Output ───────────────────────────────────────────────
|
|
281
|
+
|
|
282
|
+
describe('assessTaskComplexity — reasoning', () => {
|
|
283
|
+
it('includes signal names in reasoning when triggered', () => {
|
|
284
|
+
const result = assess({ prompt: 'migrate the database', filesEstimated: 5 });
|
|
285
|
+
expect(result.reasoning).toContain('cross-cutting-keywords');
|
|
286
|
+
expect(result.reasoning).toContain('file-count');
|
|
287
|
+
});
|
|
288
|
+
|
|
289
|
+
it('provides fallback reasoning when nothing triggers', () => {
|
|
290
|
+
const result = assess({ prompt: 'fix typo' });
|
|
291
|
+
expect(result.reasoning).toContain('No complexity signals detected');
|
|
292
|
+
});
|
|
293
|
+
|
|
294
|
+
it('always returns 6 signals', () => {
|
|
295
|
+
const result = assess({ prompt: 'anything' });
|
|
296
|
+
expect(result.signals).toHaveLength(6);
|
|
297
|
+
});
|
|
298
|
+
});
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Task Complexity Assessor — pure function that classifies tasks as simple or complex.
|
|
3
|
+
*
|
|
4
|
+
* Used by the planning module to decide whether a decomposed GH issue
|
|
5
|
+
* needs a full plan or can be executed directly.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
// ─── Types ──────────────────────────────────────────────────────────
|
|
9
|
+
|
|
10
|
+
export interface AssessmentInput {
|
|
11
|
+
/** User's task description. */
|
|
12
|
+
prompt: string;
|
|
13
|
+
/** Estimated number of files to touch. */
|
|
14
|
+
filesEstimated?: number;
|
|
15
|
+
/** GH issue body if available. */
|
|
16
|
+
parentIssueContext?: string;
|
|
17
|
+
/** Whether the approach is already described in a parent plan. */
|
|
18
|
+
hasParentPlan?: boolean;
|
|
19
|
+
/** Which domains are involved. */
|
|
20
|
+
domains?: string[];
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export interface AssessmentSignal {
|
|
24
|
+
name: string;
|
|
25
|
+
weight: number;
|
|
26
|
+
triggered: boolean;
|
|
27
|
+
detail: string;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export interface AssessmentResult {
|
|
31
|
+
classification: 'simple' | 'complex';
|
|
32
|
+
/** 0-100 complexity score. Threshold at 40. */
|
|
33
|
+
score: number;
|
|
34
|
+
signals: AssessmentSignal[];
|
|
35
|
+
/** One-line explanation. */
|
|
36
|
+
reasoning: string;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// ─── Signal Detectors ───────────────────────────────────────────────
|
|
40
|
+
|
|
41
|
+
const CROSS_CUTTING_PATTERNS = [
|
|
42
|
+
/\bauth(?:entication|orization)?\b/i,
|
|
43
|
+
/\bmigrat(?:e|ion|ing)\b/i,
|
|
44
|
+
/\brefactor(?:ing)?\s+across\b/i,
|
|
45
|
+
/\bcross[- ]cutting\b/i,
|
|
46
|
+
];
|
|
47
|
+
|
|
48
|
+
const NEW_DEPENDENCY_PATTERNS = [
|
|
49
|
+
/\badd\s+dep(?:endency|endencies)?\b/i,
|
|
50
|
+
/\binstall\b/i,
|
|
51
|
+
/\bnew\s+package\b/i,
|
|
52
|
+
/\bnpm\s+install\b/i,
|
|
53
|
+
/\badd\s+(?:a\s+)?(?:new\s+)?(?:npm\s+)?package\b/i,
|
|
54
|
+
];
|
|
55
|
+
|
|
56
|
+
const DESIGN_DECISION_PATTERNS = [
|
|
57
|
+
/\bhow\s+should\b/i,
|
|
58
|
+
/\bwhich\s+approach\b/i,
|
|
59
|
+
/\bdesign\s+decision\b/i,
|
|
60
|
+
/\barchitectur(?:e|al)\s+(?:decision|choice)\b/i,
|
|
61
|
+
/\btrade[- ]?off/i,
|
|
62
|
+
];
|
|
63
|
+
|
|
64
|
+
function detectFileCount(input: AssessmentInput): AssessmentSignal {
|
|
65
|
+
const files = input.filesEstimated ?? 0;
|
|
66
|
+
const triggered = files >= 3;
|
|
67
|
+
return {
|
|
68
|
+
name: 'file-count',
|
|
69
|
+
weight: 25,
|
|
70
|
+
triggered,
|
|
71
|
+
detail: triggered
|
|
72
|
+
? `Estimated ${files} files (≥3 threshold)`
|
|
73
|
+
: files > 0
|
|
74
|
+
? `Estimated ${files} file${files === 1 ? '' : 's'} (under threshold)`
|
|
75
|
+
: 'No file estimate provided',
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function detectCrossCutting(input: AssessmentInput): AssessmentSignal {
|
|
80
|
+
const text = input.prompt;
|
|
81
|
+
const match = CROSS_CUTTING_PATTERNS.find((p) => p.test(text));
|
|
82
|
+
return {
|
|
83
|
+
name: 'cross-cutting-keywords',
|
|
84
|
+
weight: 20,
|
|
85
|
+
triggered: !!match,
|
|
86
|
+
detail: match
|
|
87
|
+
? `Detected cross-cutting keyword: "${text.match(match)?.[0]}"`
|
|
88
|
+
: 'No cross-cutting keywords detected',
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function detectNewDependencies(input: AssessmentInput): AssessmentSignal {
|
|
93
|
+
const text = input.prompt;
|
|
94
|
+
const match = NEW_DEPENDENCY_PATTERNS.find((p) => p.test(text));
|
|
95
|
+
return {
|
|
96
|
+
name: 'new-dependencies',
|
|
97
|
+
weight: 15,
|
|
98
|
+
triggered: !!match,
|
|
99
|
+
detail: match
|
|
100
|
+
? `Detected dependency signal: "${text.match(match)?.[0]}"`
|
|
101
|
+
: 'No new dependency signals detected',
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function detectDesignDecisions(input: AssessmentInput): AssessmentSignal {
|
|
106
|
+
const text = input.prompt;
|
|
107
|
+
const match = DESIGN_DECISION_PATTERNS.find((p) => p.test(text));
|
|
108
|
+
return {
|
|
109
|
+
name: 'design-decisions-needed',
|
|
110
|
+
weight: 20,
|
|
111
|
+
triggered: !!match,
|
|
112
|
+
detail: match
|
|
113
|
+
? `Detected design decision signal: "${text.match(match)?.[0]}"`
|
|
114
|
+
: 'No design decision signals detected',
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
function detectApproachDescribed(input: AssessmentInput): AssessmentSignal {
|
|
119
|
+
const hasContext = !!(input.hasParentPlan || input.parentIssueContext?.trim());
|
|
120
|
+
return {
|
|
121
|
+
name: 'approach-already-described',
|
|
122
|
+
weight: -15,
|
|
123
|
+
triggered: hasContext,
|
|
124
|
+
detail: hasContext
|
|
125
|
+
? 'Approach already described in parent plan or issue'
|
|
126
|
+
: 'No pre-existing approach context',
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
function detectMultiDomain(input: AssessmentInput): AssessmentSignal {
|
|
131
|
+
const domains = input.domains ?? [];
|
|
132
|
+
const triggered = domains.length >= 2;
|
|
133
|
+
return {
|
|
134
|
+
name: 'multi-domain',
|
|
135
|
+
weight: 5,
|
|
136
|
+
triggered,
|
|
137
|
+
detail: triggered
|
|
138
|
+
? `Involves ${domains.length} domains: ${domains.join(', ')}`
|
|
139
|
+
: domains.length === 1
|
|
140
|
+
? `Single domain: ${domains[0]}`
|
|
141
|
+
: 'No domains specified',
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// ─── Assessor ───────────────────────────────────────────────────────
|
|
146
|
+
|
|
147
|
+
const COMPLEXITY_THRESHOLD = 40;
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* Assess task complexity from structured input.
|
|
151
|
+
*
|
|
152
|
+
* Returns a classification (`simple` | `complex`), a numeric score (0-100),
|
|
153
|
+
* the individual signals that contributed, and a one-line reasoning string.
|
|
154
|
+
*
|
|
155
|
+
* Pure function — no side effects, no DB, no MCP calls.
|
|
156
|
+
*/
|
|
157
|
+
export function assessTaskComplexity(input: AssessmentInput): AssessmentResult {
|
|
158
|
+
const signals: AssessmentSignal[] = [
|
|
159
|
+
detectFileCount(input),
|
|
160
|
+
detectCrossCutting(input),
|
|
161
|
+
detectNewDependencies(input),
|
|
162
|
+
detectDesignDecisions(input),
|
|
163
|
+
detectApproachDescribed(input),
|
|
164
|
+
detectMultiDomain(input),
|
|
165
|
+
];
|
|
166
|
+
|
|
167
|
+
const rawScore = signals.reduce(
|
|
168
|
+
(sum, s) => sum + (s.triggered ? s.weight : 0),
|
|
169
|
+
0,
|
|
170
|
+
);
|
|
171
|
+
|
|
172
|
+
// Clamp to 0-100
|
|
173
|
+
const score = Math.max(0, Math.min(100, rawScore));
|
|
174
|
+
const classification = score >= COMPLEXITY_THRESHOLD ? 'complex' : 'simple';
|
|
175
|
+
|
|
176
|
+
const triggered = signals.filter((s) => s.triggered);
|
|
177
|
+
const reasoning =
|
|
178
|
+
triggered.length === 0
|
|
179
|
+
? 'No complexity signals detected — treating as simple task'
|
|
180
|
+
: `${classification === 'complex' ? 'Complex' : 'Simple'}: ${triggered.map((s) => s.name).join(', ')} (score ${score})`;
|
|
181
|
+
|
|
182
|
+
return { classification, score, signals, reasoning };
|
|
183
|
+
}
|
|
@@ -11,8 +11,12 @@ import {
|
|
|
11
11
|
import type { PlanTask, TaskEvidence, TaskDeliverable, ReviewEvidence } from './planner-types.js';
|
|
12
12
|
|
|
13
13
|
const makeTask = (overrides: Partial<PlanTask> = {}): PlanTask => ({
|
|
14
|
-
id: 'task-1',
|
|
15
|
-
|
|
14
|
+
id: 'task-1',
|
|
15
|
+
title: 'Test task',
|
|
16
|
+
description: 'Do something',
|
|
17
|
+
status: 'pending',
|
|
18
|
+
updatedAt: 0,
|
|
19
|
+
...overrides,
|
|
16
20
|
});
|
|
17
21
|
|
|
18
22
|
describe('task-verifier', () => {
|
|
@@ -21,7 +25,11 @@ describe('task-verifier', () => {
|
|
|
21
25
|
const existing: TaskEvidence[] = [
|
|
22
26
|
{ criterion: 'cr1', content: 'result', type: 'description', submittedAt: 100 },
|
|
23
27
|
];
|
|
24
|
-
const result = createEvidence(existing, {
|
|
28
|
+
const result = createEvidence(existing, {
|
|
29
|
+
criterion: 'cr2',
|
|
30
|
+
content: 'output',
|
|
31
|
+
type: 'command_output',
|
|
32
|
+
});
|
|
25
33
|
expect(result).toHaveLength(2);
|
|
26
34
|
expect(result[1].criterion).toBe('cr2');
|
|
27
35
|
expect(result[1].submittedAt).toBeGreaterThan(0);
|
|
@@ -66,22 +74,50 @@ describe('task-verifier', () => {
|
|
|
66
74
|
it('returns verified=true when task has approved review', () => {
|
|
67
75
|
const task = makeTask({ status: 'completed' });
|
|
68
76
|
const reviews: ReviewEvidence[] = [
|
|
69
|
-
{
|
|
77
|
+
{
|
|
78
|
+
planId: 'p1',
|
|
79
|
+
taskId: 'task-1',
|
|
80
|
+
reviewer: 'r',
|
|
81
|
+
outcome: 'approved',
|
|
82
|
+
comments: '',
|
|
83
|
+
reviewedAt: 0,
|
|
84
|
+
},
|
|
70
85
|
];
|
|
71
86
|
expect(verifyTaskLogic(task, reviews).verified).toBe(true);
|
|
72
87
|
});
|
|
73
88
|
it('returns verified=false when task has rejected review', () => {
|
|
74
89
|
const task = makeTask({ status: 'completed' });
|
|
75
90
|
const reviews: ReviewEvidence[] = [
|
|
76
|
-
{
|
|
91
|
+
{
|
|
92
|
+
planId: 'p1',
|
|
93
|
+
taskId: 'task-1',
|
|
94
|
+
reviewer: 'r',
|
|
95
|
+
outcome: 'rejected',
|
|
96
|
+
comments: '',
|
|
97
|
+
reviewedAt: 0,
|
|
98
|
+
},
|
|
77
99
|
];
|
|
78
100
|
expect(verifyTaskLogic(task, reviews).verified).toBe(false);
|
|
79
101
|
});
|
|
80
102
|
it('uses latest review outcome', () => {
|
|
81
103
|
const task = makeTask({ status: 'completed' });
|
|
82
104
|
const reviews: ReviewEvidence[] = [
|
|
83
|
-
{
|
|
84
|
-
|
|
105
|
+
{
|
|
106
|
+
planId: 'p1',
|
|
107
|
+
taskId: 'task-1',
|
|
108
|
+
reviewer: 'r',
|
|
109
|
+
outcome: 'rejected',
|
|
110
|
+
comments: '',
|
|
111
|
+
reviewedAt: 0,
|
|
112
|
+
},
|
|
113
|
+
{
|
|
114
|
+
planId: 'p1',
|
|
115
|
+
taskId: 'task-1',
|
|
116
|
+
reviewer: 'r',
|
|
117
|
+
outcome: 'approved',
|
|
118
|
+
comments: '',
|
|
119
|
+
reviewedAt: 1,
|
|
120
|
+
},
|
|
85
121
|
];
|
|
86
122
|
expect(verifyTaskLogic(task, reviews).verified).toBe(true);
|
|
87
123
|
});
|
|
@@ -110,9 +146,13 @@ describe('task-verifier', () => {
|
|
|
110
146
|
expect(result.issues[0].issue).toContain('pending');
|
|
111
147
|
});
|
|
112
148
|
it('flags missing evidence for completed tasks with acceptance criteria', () => {
|
|
113
|
-
const tasks: PlanTask[] = [
|
|
114
|
-
|
|
115
|
-
|
|
149
|
+
const tasks: PlanTask[] = [
|
|
150
|
+
makeTask({
|
|
151
|
+
status: 'completed',
|
|
152
|
+
acceptanceCriteria: ['cr1'],
|
|
153
|
+
evidence: [],
|
|
154
|
+
}),
|
|
155
|
+
];
|
|
116
156
|
const result = verifyPlanLogic('plan-1', tasks);
|
|
117
157
|
expect(result.issues[0].issue).toContain('Missing evidence');
|
|
118
158
|
});
|
|
@@ -138,35 +178,27 @@ describe('task-verifier', () => {
|
|
|
138
178
|
expect(result.staleCount).toBe(0);
|
|
139
179
|
});
|
|
140
180
|
it('marks file deliverables as stale when file does not exist', () => {
|
|
141
|
-
const deliverables: TaskDeliverable[] = [
|
|
142
|
-
{ type: 'file', path: '/nonexistent/path/file.ts' },
|
|
143
|
-
];
|
|
181
|
+
const deliverables: TaskDeliverable[] = [{ type: 'file', path: '/nonexistent/path/file.ts' }];
|
|
144
182
|
const result = verifyDeliverablesLogic(deliverables);
|
|
145
183
|
expect(result.verified).toBe(false);
|
|
146
184
|
expect(result.staleCount).toBe(1);
|
|
147
185
|
expect(result.deliverables[0].stale).toBe(true);
|
|
148
186
|
});
|
|
149
187
|
it('marks vault_entry deliverables as stale when vault returns null', () => {
|
|
150
|
-
const deliverables: TaskDeliverable[] = [
|
|
151
|
-
{ type: 'vault_entry', path: 'entry-123' },
|
|
152
|
-
];
|
|
188
|
+
const deliverables: TaskDeliverable[] = [{ type: 'vault_entry', path: 'entry-123' }];
|
|
153
189
|
const vault = { get: () => null };
|
|
154
190
|
const result = verifyDeliverablesLogic(deliverables, vault);
|
|
155
191
|
expect(result.staleCount).toBe(1);
|
|
156
192
|
});
|
|
157
193
|
it('marks vault_entry deliverables as valid when vault returns non-null', () => {
|
|
158
|
-
const deliverables: TaskDeliverable[] = [
|
|
159
|
-
{ type: 'vault_entry', path: 'entry-123' },
|
|
160
|
-
];
|
|
194
|
+
const deliverables: TaskDeliverable[] = [{ type: 'vault_entry', path: 'entry-123' }];
|
|
161
195
|
const vault = { get: () => ({ id: 'entry-123' }) };
|
|
162
196
|
const result = verifyDeliverablesLogic(deliverables, vault);
|
|
163
197
|
expect(result.verified).toBe(true);
|
|
164
198
|
expect(result.staleCount).toBe(0);
|
|
165
199
|
});
|
|
166
200
|
it('skips url deliverables (no verification)', () => {
|
|
167
|
-
const deliverables: TaskDeliverable[] = [
|
|
168
|
-
{ type: 'url', path: 'https://example.com' },
|
|
169
|
-
];
|
|
201
|
+
const deliverables: TaskDeliverable[] = [{ type: 'url', path: 'https://example.com' }];
|
|
170
202
|
const result = verifyDeliverablesLogic(deliverables);
|
|
171
203
|
expect(result.verified).toBe(true);
|
|
172
204
|
});
|
|
@@ -214,17 +246,17 @@ describe('task-verifier', () => {
|
|
|
214
246
|
expect(prompt).toContain('2. No regressions');
|
|
215
247
|
});
|
|
216
248
|
it('omits criteria section when empty', () => {
|
|
217
|
-
const prompt = buildSpecReviewPrompt(
|
|
218
|
-
{ title: 'T', description: 'd' },
|
|
219
|
-
'Objective',
|
|
220
|
-
);
|
|
249
|
+
const prompt = buildSpecReviewPrompt({ title: 'T', description: 'd' }, 'Objective');
|
|
221
250
|
expect(prompt).not.toContain('Acceptance Criteria');
|
|
222
251
|
});
|
|
223
252
|
});
|
|
224
253
|
|
|
225
254
|
describe('buildQualityReviewPrompt', () => {
|
|
226
255
|
it('includes task info and quality checklist', () => {
|
|
227
|
-
const prompt = buildQualityReviewPrompt({
|
|
256
|
+
const prompt = buildQualityReviewPrompt({
|
|
257
|
+
title: 'Refactor X',
|
|
258
|
+
description: 'Clean up code',
|
|
259
|
+
});
|
|
228
260
|
expect(prompt).toContain('Refactor X');
|
|
229
261
|
expect(prompt).toContain('Clean up code');
|
|
230
262
|
expect(prompt).toContain('Code Quality Review');
|