qualia-framework 2.1.0
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/README.md +50 -0
- package/bin/cli.js +519 -0
- package/framework/agents/architecture-strategist.md +53 -0
- package/framework/agents/backend-agent.md +150 -0
- package/framework/agents/code-simplicity-reviewer.md +86 -0
- package/framework/agents/frontend-agent.md +111 -0
- package/framework/agents/kieran-typescript-reviewer.md +96 -0
- package/framework/agents/performance-oracle.md +111 -0
- package/framework/agents/qualia-codebase-mapper.md +760 -0
- package/framework/agents/qualia-debugger.md +1203 -0
- package/framework/agents/qualia-executor.md +881 -0
- package/framework/agents/qualia-integration-checker.md +423 -0
- package/framework/agents/qualia-phase-researcher.md +453 -0
- package/framework/agents/qualia-plan-checker.md +699 -0
- package/framework/agents/qualia-planner.md +1241 -0
- package/framework/agents/qualia-project-researcher.md +602 -0
- package/framework/agents/qualia-research-synthesizer.md +236 -0
- package/framework/agents/qualia-roadmapper.md +605 -0
- package/framework/agents/qualia-verifier.md +685 -0
- package/framework/agents/team-orchestrator.md +228 -0
- package/framework/agents/teams/full-stack-team.md +48 -0
- package/framework/agents/teams/optimize-team.md +53 -0
- package/framework/agents/teams/review-team.md +62 -0
- package/framework/agents/teams/ship-team.md +86 -0
- package/framework/agents/test-agent.md +182 -0
- package/framework/askpass.sh +2 -0
- package/framework/commands/design.md +53 -0
- package/framework/commands/quick-db.md +22 -0
- package/framework/config/retention.json +35 -0
- package/framework/core/PRINCIPLES.md +77 -0
- package/framework/hooks/auto-format.sh +45 -0
- package/framework/hooks/block-env-edit.sh +42 -0
- package/framework/hooks/branch-guard.sh +46 -0
- package/framework/hooks/confirm-delete.sh +56 -0
- package/framework/hooks/migration-validate.sh +68 -0
- package/framework/hooks/notification-speak.sh +15 -0
- package/framework/hooks/pre-commit.sh +80 -0
- package/framework/hooks/pre-compact.sh +55 -0
- package/framework/hooks/pre-deploy-gate.sh +151 -0
- package/framework/hooks/qualia-colors.sh +32 -0
- package/framework/hooks/retention-cleanup.sh +43 -0
- package/framework/hooks/save-session-state.sh +153 -0
- package/framework/hooks/session-context-loader.sh +28 -0
- package/framework/hooks/session-learn.sh +30 -0
- package/framework/knowledge/claudecode-bible.md +1384 -0
- package/framework/knowledge/client-prefs.md +22 -0
- package/framework/knowledge/common-fixes.md +25 -0
- package/framework/knowledge/deployment-map.md +35 -0
- package/framework/knowledge/email-signature.html +1 -0
- package/framework/knowledge/employees.md +8 -0
- package/framework/knowledge/learned-patterns.md +51 -0
- package/framework/knowledge/optimization-research-2026.md +137 -0
- package/framework/knowledge/qualia-context.md +67 -0
- package/framework/knowledge/supabase-patterns.md +50 -0
- package/framework/knowledge/voice-agent-patterns.md +46 -0
- package/framework/qualia-engine/VERSION +1 -0
- package/framework/qualia-engine/bin/qualia-tools.js +2160 -0
- package/framework/qualia-engine/bin/qualia-tools.test.js +1054 -0
- package/framework/qualia-engine/references/checkpoints.md +775 -0
- package/framework/qualia-engine/references/continuation-format.md +249 -0
- package/framework/qualia-engine/references/decimal-phase-calculation.md +65 -0
- package/framework/qualia-engine/references/design-quality.md +56 -0
- package/framework/qualia-engine/references/git-integration.md +254 -0
- package/framework/qualia-engine/references/git-planning-commit.md +50 -0
- package/framework/qualia-engine/references/model-profile-resolution.md +32 -0
- package/framework/qualia-engine/references/model-profiles.md +73 -0
- package/framework/qualia-engine/references/phase-argument-parsing.md +61 -0
- package/framework/qualia-engine/references/planning-config.md +195 -0
- package/framework/qualia-engine/references/questioning.md +141 -0
- package/framework/qualia-engine/references/tdd.md +263 -0
- package/framework/qualia-engine/references/ui-brand.md +160 -0
- package/framework/qualia-engine/references/verification-patterns.md +612 -0
- package/framework/qualia-engine/templates/DEBUG.md +159 -0
- package/framework/qualia-engine/templates/DESIGN.md +81 -0
- package/framework/qualia-engine/templates/UAT.md +247 -0
- package/framework/qualia-engine/templates/codebase/architecture.md +255 -0
- package/framework/qualia-engine/templates/codebase/concerns.md +310 -0
- package/framework/qualia-engine/templates/codebase/conventions.md +307 -0
- package/framework/qualia-engine/templates/codebase/integrations.md +280 -0
- package/framework/qualia-engine/templates/codebase/stack.md +186 -0
- package/framework/qualia-engine/templates/codebase/structure.md +285 -0
- package/framework/qualia-engine/templates/codebase/testing.md +480 -0
- package/framework/qualia-engine/templates/config.json +35 -0
- package/framework/qualia-engine/templates/context.md +283 -0
- package/framework/qualia-engine/templates/continue-here.md +78 -0
- package/framework/qualia-engine/templates/debug-subagent-prompt.md +91 -0
- package/framework/qualia-engine/templates/discovery.md +146 -0
- package/framework/qualia-engine/templates/milestone-archive.md +123 -0
- package/framework/qualia-engine/templates/milestone.md +115 -0
- package/framework/qualia-engine/templates/phase-prompt.md +567 -0
- package/framework/qualia-engine/templates/planner-subagent-prompt.md +117 -0
- package/framework/qualia-engine/templates/project.md +184 -0
- package/framework/qualia-engine/templates/projects/ai-agent.md +156 -0
- package/framework/qualia-engine/templates/projects/mobile-app.md +181 -0
- package/framework/qualia-engine/templates/projects/voice-agent.md +134 -0
- package/framework/qualia-engine/templates/projects/website.md +137 -0
- package/framework/qualia-engine/templates/requirements.md +231 -0
- package/framework/qualia-engine/templates/research-project/ARCHITECTURE.md +204 -0
- package/framework/qualia-engine/templates/research-project/FEATURES.md +147 -0
- package/framework/qualia-engine/templates/research-project/PITFALLS.md +200 -0
- package/framework/qualia-engine/templates/research-project/STACK.md +120 -0
- package/framework/qualia-engine/templates/research-project/SUMMARY.md +170 -0
- package/framework/qualia-engine/templates/research.md +552 -0
- package/framework/qualia-engine/templates/roadmap.md +202 -0
- package/framework/qualia-engine/templates/state.md +176 -0
- package/framework/qualia-engine/templates/summary-complex.md +59 -0
- package/framework/qualia-engine/templates/summary-minimal.md +41 -0
- package/framework/qualia-engine/templates/summary-standard.md +48 -0
- package/framework/qualia-engine/templates/summary.md +246 -0
- package/framework/qualia-engine/templates/user-setup.md +311 -0
- package/framework/qualia-engine/templates/verification-report.md +322 -0
- package/framework/qualia-engine/workflows/add-phase.md +179 -0
- package/framework/qualia-engine/workflows/add-todo.md +157 -0
- package/framework/qualia-engine/workflows/audit-milestone.md +241 -0
- package/framework/qualia-engine/workflows/check-todos.md +176 -0
- package/framework/qualia-engine/workflows/complete-milestone.md +858 -0
- package/framework/qualia-engine/workflows/diagnose-issues.md +219 -0
- package/framework/qualia-engine/workflows/discovery-phase.md +289 -0
- package/framework/qualia-engine/workflows/discuss-phase.md +534 -0
- package/framework/qualia-engine/workflows/execute-phase.md +559 -0
- package/framework/qualia-engine/workflows/execute-plan.md +438 -0
- package/framework/qualia-engine/workflows/help.md +470 -0
- package/framework/qualia-engine/workflows/insert-phase.md +220 -0
- package/framework/qualia-engine/workflows/list-phase-assumptions.md +178 -0
- package/framework/qualia-engine/workflows/map-codebase.md +327 -0
- package/framework/qualia-engine/workflows/new-milestone.md +363 -0
- package/framework/qualia-engine/workflows/new-project.md +1037 -0
- package/framework/qualia-engine/workflows/pause-work.md +122 -0
- package/framework/qualia-engine/workflows/plan-milestone-gaps.md +256 -0
- package/framework/qualia-engine/workflows/plan-phase.md +422 -0
- package/framework/qualia-engine/workflows/progress.md +354 -0
- package/framework/qualia-engine/workflows/quick.md +252 -0
- package/framework/qualia-engine/workflows/remove-phase.md +326 -0
- package/framework/qualia-engine/workflows/research-phase.md +74 -0
- package/framework/qualia-engine/workflows/resume-project.md +306 -0
- package/framework/qualia-engine/workflows/set-profile.md +80 -0
- package/framework/qualia-engine/workflows/settings.md +145 -0
- package/framework/qualia-engine/workflows/transition.md +556 -0
- package/framework/qualia-engine/workflows/update.md +197 -0
- package/framework/qualia-engine/workflows/verify-phase.md +195 -0
- package/framework/qualia-engine/workflows/verify-work.md +625 -0
- package/framework/rules/context7.md +11 -0
- package/framework/rules/deployment.md +29 -0
- package/framework/rules/frontend.md +33 -0
- package/framework/rules/security.md +12 -0
- package/framework/rules/speed.md +20 -0
- package/framework/scripts/__pycache__/say.cpython-314.pyc +0 -0
- package/framework/scripts/apply-retention.sh +120 -0
- package/framework/scripts/bootstrap-pop-os.sh +354 -0
- package/framework/scripts/claude-voice +13 -0
- package/framework/scripts/cleanup.sh +131 -0
- package/framework/scripts/cowork-mode.sh +141 -0
- package/framework/scripts/generate-project-claude-md.sh +153 -0
- package/framework/scripts/load-test-webhook.js +172 -0
- package/framework/scripts/say.py +236 -0
- package/framework/scripts/showcase-video-recorder/ffmpeg-builder.js +167 -0
- package/framework/scripts/showcase-video-recorder/playwright-helpers.js +216 -0
- package/framework/scripts/speak.py +55 -0
- package/framework/scripts/speak.sh +18 -0
- package/framework/scripts/status.sh +138 -0
- package/framework/scripts/sync-to-framework.sh +65 -0
- package/framework/scripts/voice-hotkey.py +227 -0
- package/framework/scripts/voice-input.sh +51 -0
- package/framework/skills/animate/SKILL.md +202 -0
- package/framework/skills/bolder/SKILL.md +144 -0
- package/framework/skills/browser-qa/SKILL.md +536 -0
- package/framework/skills/clarify/SKILL.md +179 -0
- package/framework/skills/colorize/SKILL.md +170 -0
- package/framework/skills/critique/SKILL.md +126 -0
- package/framework/skills/deep-research/SKILL.md +271 -0
- package/framework/skills/delight/SKILL.md +329 -0
- package/framework/skills/deploy/SKILL.md +261 -0
- package/framework/skills/deploy-verify/SKILL.md +377 -0
- package/framework/skills/deploy-verify/scripts/canary-check.sh +206 -0
- package/framework/skills/deploy-verify/scripts/check-console-errors.js +147 -0
- package/framework/skills/deploy-verify/scripts/check-cwv.js +139 -0
- package/framework/skills/deploy-verify/scripts/project-detect.sh +84 -0
- package/framework/skills/deploy-verify/scripts/verify.sh +548 -0
- package/framework/skills/design-quieter/SKILL.md +130 -0
- package/framework/skills/distill/SKILL.md +149 -0
- package/framework/skills/docs-lookup/SKILL.md +78 -0
- package/framework/skills/fcm-notifications/SKILL.md +125 -0
- package/framework/skills/financial-ledger/SKILL.md +1039 -0
- package/framework/skills/frontend-master/NOTICE.md +4 -0
- package/framework/skills/frontend-master/SKILL.md +127 -0
- package/framework/skills/frontend-master/reference/color-and-contrast.md +132 -0
- package/framework/skills/frontend-master/reference/interaction-design.md +123 -0
- package/framework/skills/frontend-master/reference/motion-design.md +99 -0
- package/framework/skills/frontend-master/reference/responsive-design.md +114 -0
- package/framework/skills/frontend-master/reference/spatial-design.md +100 -0
- package/framework/skills/frontend-master/reference/typography.md +131 -0
- package/framework/skills/frontend-master/reference/ux-writing.md +107 -0
- package/framework/skills/harden/SKILL.md +357 -0
- package/framework/skills/i18n-rtl/SKILL.md +752 -0
- package/framework/skills/learn/SKILL.md +71 -0
- package/framework/skills/memory/SKILL.md +50 -0
- package/framework/skills/mobile-expo/SKILL.md +864 -0
- package/framework/skills/mobile-expo/references/store-checklist.md +550 -0
- package/framework/skills/nestjs-backend/README.md +73 -0
- package/framework/skills/nestjs-backend/SKILL.md +446 -0
- package/framework/skills/nestjs-backend/references/templates.md +1173 -0
- package/framework/skills/normalize/SKILL.md +79 -0
- package/framework/skills/onboard/SKILL.md +242 -0
- package/framework/skills/polish/SKILL.md +209 -0
- package/framework/skills/pr/SKILL.md +66 -0
- package/framework/skills/qualia/SKILL.md +153 -0
- package/framework/skills/qualia-add-todo/SKILL.md +68 -0
- package/framework/skills/qualia-audit-milestone/SKILL.md +92 -0
- package/framework/skills/qualia-check-todos/SKILL.md +55 -0
- package/framework/skills/qualia-complete-milestone/SKILL.md +108 -0
- package/framework/skills/qualia-debug/SKILL.md +149 -0
- package/framework/skills/qualia-design/SKILL.md +203 -0
- package/framework/skills/qualia-discuss-phase/SKILL.md +72 -0
- package/framework/skills/qualia-execute-phase/SKILL.md +86 -0
- package/framework/skills/qualia-help/SKILL.md +67 -0
- package/framework/skills/qualia-idk/SKILL.md +352 -0
- package/framework/skills/qualia-list-phase-assumptions/SKILL.md +67 -0
- package/framework/skills/qualia-new-milestone/SKILL.md +72 -0
- package/framework/skills/qualia-new-project/SKILL.md +92 -0
- package/framework/skills/qualia-optimize/SKILL.md +417 -0
- package/framework/skills/qualia-pause-work/SKILL.md +96 -0
- package/framework/skills/qualia-plan-milestone-gaps/SKILL.md +57 -0
- package/framework/skills/qualia-plan-phase/SKILL.md +101 -0
- package/framework/skills/qualia-progress/SKILL.md +53 -0
- package/framework/skills/qualia-quick/SKILL.md +89 -0
- package/framework/skills/qualia-research-phase/SKILL.md +88 -0
- package/framework/skills/qualia-resume-work/SKILL.md +62 -0
- package/framework/skills/qualia-review/SKILL.md +263 -0
- package/framework/skills/qualia-start/SKILL.md +182 -0
- package/framework/skills/qualia-verify-work/SKILL.md +105 -0
- package/framework/skills/qualia-workflow/SKILL.md +130 -0
- package/framework/skills/rag/SKILL.md +750 -0
- package/framework/skills/responsive/SKILL.md +231 -0
- package/framework/skills/retro/SKILL.md +284 -0
- package/framework/skills/sakani-conventions/SKILL.md +136 -0
- package/framework/skills/sakani-conventions/evals/evals.json +23 -0
- package/framework/skills/sakani-conventions/references/entities.md +365 -0
- package/framework/skills/sakani-conventions/references/error-codes.md +95 -0
- package/framework/skills/seo-master/SKILL.md +490 -0
- package/framework/skills/seo-master/references/checklist.md +199 -0
- package/framework/skills/seo-master/references/structured-data.md +609 -0
- package/framework/skills/ship/SKILL.md +202 -0
- package/framework/skills/stack-researcher/SKILL.md +215 -0
- package/framework/skills/status/SKILL.md +154 -0
- package/framework/skills/status/scripts/health-check.sh +562 -0
- package/framework/skills/subscription-payments/SKILL.md +250 -0
- package/framework/skills/supabase/SKILL.md +973 -0
- package/framework/skills/supabase/references/templates.md +159 -0
- package/framework/skills/team/SKILL.md +67 -0
- package/framework/skills/test-runner/SKILL.md +202 -0
- package/framework/skills/voice-agent/SKILL.md +407 -0
- package/framework/skills/zoho-workflow/SKILL.md +51 -0
- package/framework/statusline-command.sh +117 -0
- package/package.json +24 -0
- package/profiles/fawzi.json +16 -0
- package/profiles/hasan.json +16 -0
- package/profiles/moayad.json +16 -0
- package/templates/CLAUDE-owner.md +52 -0
- package/templates/CLAUDE.md.hbs +58 -0
- package/templates/env.claude.template +12 -0
- package/templates/settings.json +141 -0
|
@@ -0,0 +1,1054 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Qualia Tools Tests — Schema validation for history-digest command
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
const { test, describe, beforeEach, afterEach } = require('node:test');
|
|
6
|
+
const assert = require('node:assert');
|
|
7
|
+
const fs = require('fs');
|
|
8
|
+
const path = require('path');
|
|
9
|
+
const { execSync } = require('child_process');
|
|
10
|
+
|
|
11
|
+
const TOOLS_PATH = path.join(__dirname, 'qualia-tools.js');
|
|
12
|
+
|
|
13
|
+
// Helper to run qualia-tools command
|
|
14
|
+
function runQualiaTools(args, cwd = process.cwd()) {
|
|
15
|
+
try {
|
|
16
|
+
const result = execSync(`node "${TOOLS_PATH}" ${args}`, {
|
|
17
|
+
cwd,
|
|
18
|
+
encoding: 'utf-8',
|
|
19
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
20
|
+
});
|
|
21
|
+
return { success: true, output: result.trim() };
|
|
22
|
+
} catch (err) {
|
|
23
|
+
return {
|
|
24
|
+
success: false,
|
|
25
|
+
output: err.stdout?.toString().trim() || '',
|
|
26
|
+
error: err.stderr?.toString().trim() || err.message,
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// Create temp directory structure
|
|
32
|
+
function createTempProject() {
|
|
33
|
+
const tmpDir = fs.mkdtempSync(path.join(require('os').tmpdir(), 'qualia-test-'));
|
|
34
|
+
fs.mkdirSync(path.join(tmpDir, '.planning', 'phases'), { recursive: true });
|
|
35
|
+
return tmpDir;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function cleanup(tmpDir) {
|
|
39
|
+
fs.rmSync(tmpDir, { recursive: true, force: true });
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
describe('history-digest command', () => {
|
|
43
|
+
let tmpDir;
|
|
44
|
+
|
|
45
|
+
beforeEach(() => {
|
|
46
|
+
tmpDir = createTempProject();
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
afterEach(() => {
|
|
50
|
+
cleanup(tmpDir);
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
test('empty phases directory returns valid schema', () => {
|
|
54
|
+
const result = runQualiaTools('history-digest', tmpDir);
|
|
55
|
+
assert.ok(result.success, `Command failed: ${result.error}`);
|
|
56
|
+
|
|
57
|
+
const digest = JSON.parse(result.output);
|
|
58
|
+
|
|
59
|
+
assert.deepStrictEqual(digest.phases, {}, 'phases should be empty object');
|
|
60
|
+
assert.deepStrictEqual(digest.decisions, [], 'decisions should be empty array');
|
|
61
|
+
assert.deepStrictEqual(digest.tech_stack, [], 'tech_stack should be empty array');
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
test('nested frontmatter fields extracted correctly', () => {
|
|
65
|
+
// Create phase directory with SUMMARY containing nested frontmatter
|
|
66
|
+
const phaseDir = path.join(tmpDir, '.planning', 'phases', '01-foundation');
|
|
67
|
+
fs.mkdirSync(phaseDir, { recursive: true });
|
|
68
|
+
|
|
69
|
+
const summaryContent = `---
|
|
70
|
+
phase: "01"
|
|
71
|
+
name: "Foundation Setup"
|
|
72
|
+
dependency-graph:
|
|
73
|
+
provides:
|
|
74
|
+
- "Database schema"
|
|
75
|
+
- "Auth system"
|
|
76
|
+
affects:
|
|
77
|
+
- "API layer"
|
|
78
|
+
tech-stack:
|
|
79
|
+
added:
|
|
80
|
+
- "prisma"
|
|
81
|
+
- "jose"
|
|
82
|
+
patterns-established:
|
|
83
|
+
- "Repository pattern"
|
|
84
|
+
- "JWT auth flow"
|
|
85
|
+
key-decisions:
|
|
86
|
+
- "Use Prisma over Drizzle"
|
|
87
|
+
- "JWT in httpOnly cookies"
|
|
88
|
+
---
|
|
89
|
+
|
|
90
|
+
# Summary content here
|
|
91
|
+
`;
|
|
92
|
+
|
|
93
|
+
fs.writeFileSync(path.join(phaseDir, '01-01-SUMMARY.md'), summaryContent);
|
|
94
|
+
|
|
95
|
+
const result = runQualiaTools('history-digest', tmpDir);
|
|
96
|
+
assert.ok(result.success, `Command failed: ${result.error}`);
|
|
97
|
+
|
|
98
|
+
const digest = JSON.parse(result.output);
|
|
99
|
+
|
|
100
|
+
// Check nested dependency-graph.provides
|
|
101
|
+
assert.ok(digest.phases['01'], 'Phase 01 should exist');
|
|
102
|
+
assert.deepStrictEqual(
|
|
103
|
+
digest.phases['01'].provides.sort(),
|
|
104
|
+
['Auth system', 'Database schema'],
|
|
105
|
+
'provides should contain nested values'
|
|
106
|
+
);
|
|
107
|
+
|
|
108
|
+
// Check nested dependency-graph.affects
|
|
109
|
+
assert.deepStrictEqual(
|
|
110
|
+
digest.phases['01'].affects,
|
|
111
|
+
['API layer'],
|
|
112
|
+
'affects should contain nested values'
|
|
113
|
+
);
|
|
114
|
+
|
|
115
|
+
// Check nested tech-stack.added
|
|
116
|
+
assert.deepStrictEqual(
|
|
117
|
+
digest.tech_stack.sort(),
|
|
118
|
+
['jose', 'prisma'],
|
|
119
|
+
'tech_stack should contain nested values'
|
|
120
|
+
);
|
|
121
|
+
|
|
122
|
+
// Check patterns-established (flat array)
|
|
123
|
+
assert.deepStrictEqual(
|
|
124
|
+
digest.phases['01'].patterns.sort(),
|
|
125
|
+
['JWT auth flow', 'Repository pattern'],
|
|
126
|
+
'patterns should be extracted'
|
|
127
|
+
);
|
|
128
|
+
|
|
129
|
+
// Check key-decisions
|
|
130
|
+
assert.strictEqual(digest.decisions.length, 2, 'Should have 2 decisions');
|
|
131
|
+
assert.ok(
|
|
132
|
+
digest.decisions.some(d => d.decision === 'Use Prisma over Drizzle'),
|
|
133
|
+
'Should contain first decision'
|
|
134
|
+
);
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
test('multiple phases merged into single digest', () => {
|
|
138
|
+
// Create phase 01
|
|
139
|
+
const phase01Dir = path.join(tmpDir, '.planning', 'phases', '01-foundation');
|
|
140
|
+
fs.mkdirSync(phase01Dir, { recursive: true });
|
|
141
|
+
fs.writeFileSync(
|
|
142
|
+
path.join(phase01Dir, '01-01-SUMMARY.md'),
|
|
143
|
+
`---
|
|
144
|
+
phase: "01"
|
|
145
|
+
name: "Foundation"
|
|
146
|
+
provides:
|
|
147
|
+
- "Database"
|
|
148
|
+
patterns-established:
|
|
149
|
+
- "Pattern A"
|
|
150
|
+
key-decisions:
|
|
151
|
+
- "Decision 1"
|
|
152
|
+
---
|
|
153
|
+
`
|
|
154
|
+
);
|
|
155
|
+
|
|
156
|
+
// Create phase 02
|
|
157
|
+
const phase02Dir = path.join(tmpDir, '.planning', 'phases', '02-api');
|
|
158
|
+
fs.mkdirSync(phase02Dir, { recursive: true });
|
|
159
|
+
fs.writeFileSync(
|
|
160
|
+
path.join(phase02Dir, '02-01-SUMMARY.md'),
|
|
161
|
+
`---
|
|
162
|
+
phase: "02"
|
|
163
|
+
name: "API"
|
|
164
|
+
provides:
|
|
165
|
+
- "REST endpoints"
|
|
166
|
+
patterns-established:
|
|
167
|
+
- "Pattern B"
|
|
168
|
+
key-decisions:
|
|
169
|
+
- "Decision 2"
|
|
170
|
+
tech-stack:
|
|
171
|
+
added:
|
|
172
|
+
- "zod"
|
|
173
|
+
---
|
|
174
|
+
`
|
|
175
|
+
);
|
|
176
|
+
|
|
177
|
+
const result = runQualiaTools('history-digest', tmpDir);
|
|
178
|
+
assert.ok(result.success, `Command failed: ${result.error}`);
|
|
179
|
+
|
|
180
|
+
const digest = JSON.parse(result.output);
|
|
181
|
+
|
|
182
|
+
// Both phases present
|
|
183
|
+
assert.ok(digest.phases['01'], 'Phase 01 should exist');
|
|
184
|
+
assert.ok(digest.phases['02'], 'Phase 02 should exist');
|
|
185
|
+
|
|
186
|
+
// Decisions merged
|
|
187
|
+
assert.strictEqual(digest.decisions.length, 2, 'Should have 2 decisions total');
|
|
188
|
+
|
|
189
|
+
// Tech stack merged
|
|
190
|
+
assert.deepStrictEqual(digest.tech_stack, ['zod'], 'tech_stack should have zod');
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
test('malformed SUMMARY.md skipped gracefully', () => {
|
|
194
|
+
const phaseDir = path.join(tmpDir, '.planning', 'phases', '01-test');
|
|
195
|
+
fs.mkdirSync(phaseDir, { recursive: true });
|
|
196
|
+
|
|
197
|
+
// Valid summary
|
|
198
|
+
fs.writeFileSync(
|
|
199
|
+
path.join(phaseDir, '01-01-SUMMARY.md'),
|
|
200
|
+
`---
|
|
201
|
+
phase: "01"
|
|
202
|
+
provides:
|
|
203
|
+
- "Valid feature"
|
|
204
|
+
---
|
|
205
|
+
`
|
|
206
|
+
);
|
|
207
|
+
|
|
208
|
+
// Malformed summary (no frontmatter)
|
|
209
|
+
fs.writeFileSync(
|
|
210
|
+
path.join(phaseDir, '01-02-SUMMARY.md'),
|
|
211
|
+
`# Just a heading
|
|
212
|
+
No frontmatter here
|
|
213
|
+
`
|
|
214
|
+
);
|
|
215
|
+
|
|
216
|
+
// Another malformed summary (broken YAML)
|
|
217
|
+
fs.writeFileSync(
|
|
218
|
+
path.join(phaseDir, '01-03-SUMMARY.md'),
|
|
219
|
+
`---
|
|
220
|
+
broken: [unclosed
|
|
221
|
+
---
|
|
222
|
+
`
|
|
223
|
+
);
|
|
224
|
+
|
|
225
|
+
const result = runQualiaTools('history-digest', tmpDir);
|
|
226
|
+
assert.ok(result.success, `Command should succeed despite malformed files: ${result.error}`);
|
|
227
|
+
|
|
228
|
+
const digest = JSON.parse(result.output);
|
|
229
|
+
assert.ok(digest.phases['01'], 'Phase 01 should exist');
|
|
230
|
+
assert.ok(
|
|
231
|
+
digest.phases['01'].provides.includes('Valid feature'),
|
|
232
|
+
'Valid feature should be extracted'
|
|
233
|
+
);
|
|
234
|
+
});
|
|
235
|
+
|
|
236
|
+
test('flat provides field still works (backward compatibility)', () => {
|
|
237
|
+
const phaseDir = path.join(tmpDir, '.planning', 'phases', '01-test');
|
|
238
|
+
fs.mkdirSync(phaseDir, { recursive: true });
|
|
239
|
+
|
|
240
|
+
fs.writeFileSync(
|
|
241
|
+
path.join(phaseDir, '01-01-SUMMARY.md'),
|
|
242
|
+
`---
|
|
243
|
+
phase: "01"
|
|
244
|
+
provides:
|
|
245
|
+
- "Direct provides"
|
|
246
|
+
---
|
|
247
|
+
`
|
|
248
|
+
);
|
|
249
|
+
|
|
250
|
+
const result = runQualiaTools('history-digest', tmpDir);
|
|
251
|
+
assert.ok(result.success, `Command failed: ${result.error}`);
|
|
252
|
+
|
|
253
|
+
const digest = JSON.parse(result.output);
|
|
254
|
+
assert.deepStrictEqual(
|
|
255
|
+
digest.phases['01'].provides,
|
|
256
|
+
['Direct provides'],
|
|
257
|
+
'Direct provides should work'
|
|
258
|
+
);
|
|
259
|
+
});
|
|
260
|
+
|
|
261
|
+
test('inline array syntax supported', () => {
|
|
262
|
+
const phaseDir = path.join(tmpDir, '.planning', 'phases', '01-test');
|
|
263
|
+
fs.mkdirSync(phaseDir, { recursive: true });
|
|
264
|
+
|
|
265
|
+
fs.writeFileSync(
|
|
266
|
+
path.join(phaseDir, '01-01-SUMMARY.md'),
|
|
267
|
+
`---
|
|
268
|
+
phase: "01"
|
|
269
|
+
provides: [Feature A, Feature B]
|
|
270
|
+
patterns-established: ["Pattern X", "Pattern Y"]
|
|
271
|
+
---
|
|
272
|
+
`
|
|
273
|
+
);
|
|
274
|
+
|
|
275
|
+
const result = runQualiaTools('history-digest', tmpDir);
|
|
276
|
+
assert.ok(result.success, `Command failed: ${result.error}`);
|
|
277
|
+
|
|
278
|
+
const digest = JSON.parse(result.output);
|
|
279
|
+
assert.deepStrictEqual(
|
|
280
|
+
digest.phases['01'].provides.sort(),
|
|
281
|
+
['Feature A', 'Feature B'],
|
|
282
|
+
'Inline array should work'
|
|
283
|
+
);
|
|
284
|
+
assert.deepStrictEqual(
|
|
285
|
+
digest.phases['01'].patterns.sort(),
|
|
286
|
+
['Pattern X', 'Pattern Y'],
|
|
287
|
+
'Inline quoted array should work'
|
|
288
|
+
);
|
|
289
|
+
});
|
|
290
|
+
});
|
|
291
|
+
|
|
292
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
293
|
+
// phases list command
|
|
294
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
295
|
+
|
|
296
|
+
describe('phases list command', () => {
|
|
297
|
+
let tmpDir;
|
|
298
|
+
|
|
299
|
+
beforeEach(() => {
|
|
300
|
+
tmpDir = createTempProject();
|
|
301
|
+
});
|
|
302
|
+
|
|
303
|
+
afterEach(() => {
|
|
304
|
+
cleanup(tmpDir);
|
|
305
|
+
});
|
|
306
|
+
|
|
307
|
+
test('empty phases directory returns empty array', () => {
|
|
308
|
+
const result = runQualiaTools('phases list', tmpDir);
|
|
309
|
+
assert.ok(result.success, `Command failed: ${result.error}`);
|
|
310
|
+
|
|
311
|
+
const output = JSON.parse(result.output);
|
|
312
|
+
assert.deepStrictEqual(output.directories, [], 'directories should be empty');
|
|
313
|
+
assert.strictEqual(output.count, 0, 'count should be 0');
|
|
314
|
+
});
|
|
315
|
+
|
|
316
|
+
test('lists phase directories sorted numerically', () => {
|
|
317
|
+
// Create out-of-order directories
|
|
318
|
+
fs.mkdirSync(path.join(tmpDir, '.planning', 'phases', '10-final'), { recursive: true });
|
|
319
|
+
fs.mkdirSync(path.join(tmpDir, '.planning', 'phases', '02-api'), { recursive: true });
|
|
320
|
+
fs.mkdirSync(path.join(tmpDir, '.planning', 'phases', '01-foundation'), { recursive: true });
|
|
321
|
+
|
|
322
|
+
const result = runQualiaTools('phases list', tmpDir);
|
|
323
|
+
assert.ok(result.success, `Command failed: ${result.error}`);
|
|
324
|
+
|
|
325
|
+
const output = JSON.parse(result.output);
|
|
326
|
+
assert.strictEqual(output.count, 3, 'should have 3 directories');
|
|
327
|
+
assert.deepStrictEqual(
|
|
328
|
+
output.directories,
|
|
329
|
+
['01-foundation', '02-api', '10-final'],
|
|
330
|
+
'should be sorted numerically'
|
|
331
|
+
);
|
|
332
|
+
});
|
|
333
|
+
|
|
334
|
+
test('handles decimal phases in sort order', () => {
|
|
335
|
+
fs.mkdirSync(path.join(tmpDir, '.planning', 'phases', '02-api'), { recursive: true });
|
|
336
|
+
fs.mkdirSync(path.join(tmpDir, '.planning', 'phases', '02.1-hotfix'), { recursive: true });
|
|
337
|
+
fs.mkdirSync(path.join(tmpDir, '.planning', 'phases', '02.2-patch'), { recursive: true });
|
|
338
|
+
fs.mkdirSync(path.join(tmpDir, '.planning', 'phases', '03-ui'), { recursive: true });
|
|
339
|
+
|
|
340
|
+
const result = runQualiaTools('phases list', tmpDir);
|
|
341
|
+
assert.ok(result.success, `Command failed: ${result.error}`);
|
|
342
|
+
|
|
343
|
+
const output = JSON.parse(result.output);
|
|
344
|
+
assert.deepStrictEqual(
|
|
345
|
+
output.directories,
|
|
346
|
+
['02-api', '02.1-hotfix', '02.2-patch', '03-ui'],
|
|
347
|
+
'decimal phases should sort correctly between whole numbers'
|
|
348
|
+
);
|
|
349
|
+
});
|
|
350
|
+
|
|
351
|
+
test('--type plans lists only PLAN.md files', () => {
|
|
352
|
+
const phaseDir = path.join(tmpDir, '.planning', 'phases', '01-test');
|
|
353
|
+
fs.mkdirSync(phaseDir, { recursive: true });
|
|
354
|
+
fs.writeFileSync(path.join(phaseDir, '01-01-PLAN.md'), '# Plan 1');
|
|
355
|
+
fs.writeFileSync(path.join(phaseDir, '01-02-PLAN.md'), '# Plan 2');
|
|
356
|
+
fs.writeFileSync(path.join(phaseDir, '01-01-SUMMARY.md'), '# Summary');
|
|
357
|
+
fs.writeFileSync(path.join(phaseDir, 'RESEARCH.md'), '# Research');
|
|
358
|
+
|
|
359
|
+
const result = runQualiaTools('phases list --type plans', tmpDir);
|
|
360
|
+
assert.ok(result.success, `Command failed: ${result.error}`);
|
|
361
|
+
|
|
362
|
+
const output = JSON.parse(result.output);
|
|
363
|
+
assert.deepStrictEqual(
|
|
364
|
+
output.files.sort(),
|
|
365
|
+
['01-01-PLAN.md', '01-02-PLAN.md'],
|
|
366
|
+
'should list only PLAN files'
|
|
367
|
+
);
|
|
368
|
+
});
|
|
369
|
+
|
|
370
|
+
test('--type summaries lists only SUMMARY.md files', () => {
|
|
371
|
+
const phaseDir = path.join(tmpDir, '.planning', 'phases', '01-test');
|
|
372
|
+
fs.mkdirSync(phaseDir, { recursive: true });
|
|
373
|
+
fs.writeFileSync(path.join(phaseDir, '01-01-PLAN.md'), '# Plan');
|
|
374
|
+
fs.writeFileSync(path.join(phaseDir, '01-01-SUMMARY.md'), '# Summary 1');
|
|
375
|
+
fs.writeFileSync(path.join(phaseDir, '01-02-SUMMARY.md'), '# Summary 2');
|
|
376
|
+
|
|
377
|
+
const result = runQualiaTools('phases list --type summaries', tmpDir);
|
|
378
|
+
assert.ok(result.success, `Command failed: ${result.error}`);
|
|
379
|
+
|
|
380
|
+
const output = JSON.parse(result.output);
|
|
381
|
+
assert.deepStrictEqual(
|
|
382
|
+
output.files.sort(),
|
|
383
|
+
['01-01-SUMMARY.md', '01-02-SUMMARY.md'],
|
|
384
|
+
'should list only SUMMARY files'
|
|
385
|
+
);
|
|
386
|
+
});
|
|
387
|
+
|
|
388
|
+
test('--phase filters to specific phase directory', () => {
|
|
389
|
+
const phase01 = path.join(tmpDir, '.planning', 'phases', '01-foundation');
|
|
390
|
+
const phase02 = path.join(tmpDir, '.planning', 'phases', '02-api');
|
|
391
|
+
fs.mkdirSync(phase01, { recursive: true });
|
|
392
|
+
fs.mkdirSync(phase02, { recursive: true });
|
|
393
|
+
fs.writeFileSync(path.join(phase01, '01-01-PLAN.md'), '# Plan');
|
|
394
|
+
fs.writeFileSync(path.join(phase02, '02-01-PLAN.md'), '# Plan');
|
|
395
|
+
|
|
396
|
+
const result = runQualiaTools('phases list --type plans --phase 01', tmpDir);
|
|
397
|
+
assert.ok(result.success, `Command failed: ${result.error}`);
|
|
398
|
+
|
|
399
|
+
const output = JSON.parse(result.output);
|
|
400
|
+
assert.deepStrictEqual(output.files, ['01-01-PLAN.md'], 'should only list phase 01 plans');
|
|
401
|
+
assert.strictEqual(output.phase_dir, 'foundation', 'should report phase name without number prefix');
|
|
402
|
+
});
|
|
403
|
+
});
|
|
404
|
+
|
|
405
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
406
|
+
// roadmap get-phase command
|
|
407
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
408
|
+
|
|
409
|
+
describe('roadmap get-phase command', () => {
|
|
410
|
+
let tmpDir;
|
|
411
|
+
|
|
412
|
+
beforeEach(() => {
|
|
413
|
+
tmpDir = createTempProject();
|
|
414
|
+
});
|
|
415
|
+
|
|
416
|
+
afterEach(() => {
|
|
417
|
+
cleanup(tmpDir);
|
|
418
|
+
});
|
|
419
|
+
|
|
420
|
+
test('extracts phase section from ROADMAP.md', () => {
|
|
421
|
+
fs.writeFileSync(
|
|
422
|
+
path.join(tmpDir, '.planning', 'ROADMAP.md'),
|
|
423
|
+
`# Roadmap v1.0
|
|
424
|
+
|
|
425
|
+
## Phases
|
|
426
|
+
|
|
427
|
+
### Phase 1: Foundation
|
|
428
|
+
**Goal:** Set up project infrastructure
|
|
429
|
+
**Plans:** 2 plans
|
|
430
|
+
|
|
431
|
+
Some description here.
|
|
432
|
+
|
|
433
|
+
### Phase 2: API
|
|
434
|
+
**Goal:** Build REST API
|
|
435
|
+
**Plans:** 3 plans
|
|
436
|
+
`
|
|
437
|
+
);
|
|
438
|
+
|
|
439
|
+
const result = runQualiaTools('roadmap get-phase 1', tmpDir);
|
|
440
|
+
assert.ok(result.success, `Command failed: ${result.error}`);
|
|
441
|
+
|
|
442
|
+
const output = JSON.parse(result.output);
|
|
443
|
+
assert.strictEqual(output.found, true, 'phase should be found');
|
|
444
|
+
assert.strictEqual(output.phase_number, '1', 'phase number correct');
|
|
445
|
+
assert.strictEqual(output.phase_name, 'Foundation', 'phase name extracted');
|
|
446
|
+
assert.strictEqual(output.goal, 'Set up project infrastructure', 'goal extracted');
|
|
447
|
+
});
|
|
448
|
+
|
|
449
|
+
test('returns not found for missing phase', () => {
|
|
450
|
+
fs.writeFileSync(
|
|
451
|
+
path.join(tmpDir, '.planning', 'ROADMAP.md'),
|
|
452
|
+
`# Roadmap v1.0
|
|
453
|
+
|
|
454
|
+
### Phase 1: Foundation
|
|
455
|
+
**Goal:** Set up project
|
|
456
|
+
`
|
|
457
|
+
);
|
|
458
|
+
|
|
459
|
+
const result = runQualiaTools('roadmap get-phase 5', tmpDir);
|
|
460
|
+
assert.ok(result.success, `Command failed: ${result.error}`);
|
|
461
|
+
|
|
462
|
+
const output = JSON.parse(result.output);
|
|
463
|
+
assert.strictEqual(output.found, false, 'phase should not be found');
|
|
464
|
+
});
|
|
465
|
+
|
|
466
|
+
test('handles decimal phase numbers', () => {
|
|
467
|
+
fs.writeFileSync(
|
|
468
|
+
path.join(tmpDir, '.planning', 'ROADMAP.md'),
|
|
469
|
+
`# Roadmap
|
|
470
|
+
|
|
471
|
+
### Phase 2: Main
|
|
472
|
+
**Goal:** Main work
|
|
473
|
+
|
|
474
|
+
### Phase 2.1: Hotfix
|
|
475
|
+
**Goal:** Emergency fix
|
|
476
|
+
`
|
|
477
|
+
);
|
|
478
|
+
|
|
479
|
+
const result = runQualiaTools('roadmap get-phase 2.1', tmpDir);
|
|
480
|
+
assert.ok(result.success, `Command failed: ${result.error}`);
|
|
481
|
+
|
|
482
|
+
const output = JSON.parse(result.output);
|
|
483
|
+
assert.strictEqual(output.found, true, 'decimal phase should be found');
|
|
484
|
+
assert.strictEqual(output.phase_name, 'Hotfix', 'phase name correct');
|
|
485
|
+
assert.strictEqual(output.goal, 'Emergency fix', 'goal extracted');
|
|
486
|
+
});
|
|
487
|
+
|
|
488
|
+
test('extracts full section content', () => {
|
|
489
|
+
fs.writeFileSync(
|
|
490
|
+
path.join(tmpDir, '.planning', 'ROADMAP.md'),
|
|
491
|
+
`# Roadmap
|
|
492
|
+
|
|
493
|
+
### Phase 1: Setup
|
|
494
|
+
**Goal:** Initialize everything
|
|
495
|
+
|
|
496
|
+
This phase covers:
|
|
497
|
+
- Database setup
|
|
498
|
+
- Auth configuration
|
|
499
|
+
- CI/CD pipeline
|
|
500
|
+
|
|
501
|
+
### Phase 2: Build
|
|
502
|
+
**Goal:** Build features
|
|
503
|
+
`
|
|
504
|
+
);
|
|
505
|
+
|
|
506
|
+
const result = runQualiaTools('roadmap get-phase 1', tmpDir);
|
|
507
|
+
assert.ok(result.success, `Command failed: ${result.error}`);
|
|
508
|
+
|
|
509
|
+
const output = JSON.parse(result.output);
|
|
510
|
+
assert.ok(output.section.includes('Database setup'), 'section includes description');
|
|
511
|
+
assert.ok(output.section.includes('CI/CD pipeline'), 'section includes all bullets');
|
|
512
|
+
assert.ok(!output.section.includes('Phase 2'), 'section does not include next phase');
|
|
513
|
+
});
|
|
514
|
+
|
|
515
|
+
test('handles missing ROADMAP.md gracefully', () => {
|
|
516
|
+
const result = runQualiaTools('roadmap get-phase 1', tmpDir);
|
|
517
|
+
assert.ok(result.success, `Command failed: ${result.error}`);
|
|
518
|
+
|
|
519
|
+
const output = JSON.parse(result.output);
|
|
520
|
+
assert.strictEqual(output.found, false, 'should return not found');
|
|
521
|
+
assert.strictEqual(output.error, 'ROADMAP.md not found', 'should explain why');
|
|
522
|
+
});
|
|
523
|
+
});
|
|
524
|
+
|
|
525
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
526
|
+
// phase next-decimal command
|
|
527
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
528
|
+
|
|
529
|
+
describe('phase next-decimal command', () => {
|
|
530
|
+
let tmpDir;
|
|
531
|
+
|
|
532
|
+
beforeEach(() => {
|
|
533
|
+
tmpDir = createTempProject();
|
|
534
|
+
});
|
|
535
|
+
|
|
536
|
+
afterEach(() => {
|
|
537
|
+
cleanup(tmpDir);
|
|
538
|
+
});
|
|
539
|
+
|
|
540
|
+
test('returns X.1 when no decimal phases exist', () => {
|
|
541
|
+
fs.mkdirSync(path.join(tmpDir, '.planning', 'phases', '06-feature'), { recursive: true });
|
|
542
|
+
fs.mkdirSync(path.join(tmpDir, '.planning', 'phases', '07-next'), { recursive: true });
|
|
543
|
+
|
|
544
|
+
const result = runQualiaTools('phase next-decimal 06', tmpDir);
|
|
545
|
+
assert.ok(result.success, `Command failed: ${result.error}`);
|
|
546
|
+
|
|
547
|
+
const output = JSON.parse(result.output);
|
|
548
|
+
assert.strictEqual(output.next, '06.1', 'should return 06.1');
|
|
549
|
+
assert.deepStrictEqual(output.existing, [], 'no existing decimals');
|
|
550
|
+
});
|
|
551
|
+
|
|
552
|
+
test('increments from existing decimal phases', () => {
|
|
553
|
+
fs.mkdirSync(path.join(tmpDir, '.planning', 'phases', '06-feature'), { recursive: true });
|
|
554
|
+
fs.mkdirSync(path.join(tmpDir, '.planning', 'phases', '06.1-hotfix'), { recursive: true });
|
|
555
|
+
fs.mkdirSync(path.join(tmpDir, '.planning', 'phases', '06.2-patch'), { recursive: true });
|
|
556
|
+
|
|
557
|
+
const result = runQualiaTools('phase next-decimal 06', tmpDir);
|
|
558
|
+
assert.ok(result.success, `Command failed: ${result.error}`);
|
|
559
|
+
|
|
560
|
+
const output = JSON.parse(result.output);
|
|
561
|
+
assert.strictEqual(output.next, '06.3', 'should return 06.3');
|
|
562
|
+
assert.deepStrictEqual(output.existing, ['06.1', '06.2'], 'lists existing decimals');
|
|
563
|
+
});
|
|
564
|
+
|
|
565
|
+
test('handles gaps in decimal sequence', () => {
|
|
566
|
+
fs.mkdirSync(path.join(tmpDir, '.planning', 'phases', '06-feature'), { recursive: true });
|
|
567
|
+
fs.mkdirSync(path.join(tmpDir, '.planning', 'phases', '06.1-first'), { recursive: true });
|
|
568
|
+
fs.mkdirSync(path.join(tmpDir, '.planning', 'phases', '06.3-third'), { recursive: true });
|
|
569
|
+
|
|
570
|
+
const result = runQualiaTools('phase next-decimal 06', tmpDir);
|
|
571
|
+
assert.ok(result.success, `Command failed: ${result.error}`);
|
|
572
|
+
|
|
573
|
+
const output = JSON.parse(result.output);
|
|
574
|
+
// Should take next after highest, not fill gap
|
|
575
|
+
assert.strictEqual(output.next, '06.4', 'should return 06.4, not fill gap at 06.2');
|
|
576
|
+
});
|
|
577
|
+
|
|
578
|
+
test('handles single-digit phase input', () => {
|
|
579
|
+
fs.mkdirSync(path.join(tmpDir, '.planning', 'phases', '06-feature'), { recursive: true });
|
|
580
|
+
|
|
581
|
+
const result = runQualiaTools('phase next-decimal 6', tmpDir);
|
|
582
|
+
assert.ok(result.success, `Command failed: ${result.error}`);
|
|
583
|
+
|
|
584
|
+
const output = JSON.parse(result.output);
|
|
585
|
+
assert.strictEqual(output.next, '06.1', 'should normalize to 06.1');
|
|
586
|
+
assert.strictEqual(output.base_phase, '06', 'base phase should be padded');
|
|
587
|
+
});
|
|
588
|
+
|
|
589
|
+
test('returns error if base phase does not exist', () => {
|
|
590
|
+
fs.mkdirSync(path.join(tmpDir, '.planning', 'phases', '01-start'), { recursive: true });
|
|
591
|
+
|
|
592
|
+
const result = runQualiaTools('phase next-decimal 06', tmpDir);
|
|
593
|
+
assert.ok(result.success, `Command should succeed: ${result.error}`);
|
|
594
|
+
|
|
595
|
+
const output = JSON.parse(result.output);
|
|
596
|
+
assert.strictEqual(output.found, false, 'base phase not found');
|
|
597
|
+
assert.strictEqual(output.next, '06.1', 'should still suggest 06.1');
|
|
598
|
+
});
|
|
599
|
+
});
|
|
600
|
+
|
|
601
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
602
|
+
// phase-plan-index command
|
|
603
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
604
|
+
|
|
605
|
+
describe('phase-plan-index command', () => {
|
|
606
|
+
let tmpDir;
|
|
607
|
+
|
|
608
|
+
beforeEach(() => {
|
|
609
|
+
tmpDir = createTempProject();
|
|
610
|
+
});
|
|
611
|
+
|
|
612
|
+
afterEach(() => {
|
|
613
|
+
cleanup(tmpDir);
|
|
614
|
+
});
|
|
615
|
+
|
|
616
|
+
test('empty phase directory returns empty plans array', () => {
|
|
617
|
+
fs.mkdirSync(path.join(tmpDir, '.planning', 'phases', '03-api'), { recursive: true });
|
|
618
|
+
|
|
619
|
+
const result = runQualiaTools('phase-plan-index 03', tmpDir);
|
|
620
|
+
assert.ok(result.success, `Command failed: ${result.error}`);
|
|
621
|
+
|
|
622
|
+
const output = JSON.parse(result.output);
|
|
623
|
+
assert.strictEqual(output.phase, '03', 'phase number correct');
|
|
624
|
+
assert.deepStrictEqual(output.plans, [], 'plans should be empty');
|
|
625
|
+
assert.deepStrictEqual(output.waves, {}, 'waves should be empty');
|
|
626
|
+
assert.deepStrictEqual(output.incomplete, [], 'incomplete should be empty');
|
|
627
|
+
assert.strictEqual(output.has_checkpoints, false, 'no checkpoints');
|
|
628
|
+
});
|
|
629
|
+
|
|
630
|
+
test('extracts single plan with frontmatter', () => {
|
|
631
|
+
const phaseDir = path.join(tmpDir, '.planning', 'phases', '03-api');
|
|
632
|
+
fs.mkdirSync(phaseDir, { recursive: true });
|
|
633
|
+
|
|
634
|
+
fs.writeFileSync(
|
|
635
|
+
path.join(phaseDir, '03-01-PLAN.md'),
|
|
636
|
+
`---
|
|
637
|
+
wave: 1
|
|
638
|
+
autonomous: true
|
|
639
|
+
objective: Set up database schema
|
|
640
|
+
files-modified: [prisma/schema.prisma, src/lib/db.ts]
|
|
641
|
+
---
|
|
642
|
+
|
|
643
|
+
## Task 1: Create schema
|
|
644
|
+
## Task 2: Generate client
|
|
645
|
+
`
|
|
646
|
+
);
|
|
647
|
+
|
|
648
|
+
const result = runQualiaTools('phase-plan-index 03', tmpDir);
|
|
649
|
+
assert.ok(result.success, `Command failed: ${result.error}`);
|
|
650
|
+
|
|
651
|
+
const output = JSON.parse(result.output);
|
|
652
|
+
assert.strictEqual(output.plans.length, 1, 'should have 1 plan');
|
|
653
|
+
assert.strictEqual(output.plans[0].id, '03-01', 'plan id correct');
|
|
654
|
+
assert.strictEqual(output.plans[0].wave, 1, 'wave extracted');
|
|
655
|
+
assert.strictEqual(output.plans[0].autonomous, true, 'autonomous extracted');
|
|
656
|
+
assert.strictEqual(output.plans[0].objective, 'Set up database schema', 'objective extracted');
|
|
657
|
+
assert.deepStrictEqual(output.plans[0].files_modified, ['prisma/schema.prisma', 'src/lib/db.ts'], 'files extracted');
|
|
658
|
+
assert.strictEqual(output.plans[0].task_count, 2, 'task count correct');
|
|
659
|
+
assert.strictEqual(output.plans[0].has_summary, false, 'no summary yet');
|
|
660
|
+
});
|
|
661
|
+
|
|
662
|
+
test('groups multiple plans by wave', () => {
|
|
663
|
+
const phaseDir = path.join(tmpDir, '.planning', 'phases', '03-api');
|
|
664
|
+
fs.mkdirSync(phaseDir, { recursive: true });
|
|
665
|
+
|
|
666
|
+
fs.writeFileSync(
|
|
667
|
+
path.join(phaseDir, '03-01-PLAN.md'),
|
|
668
|
+
`---
|
|
669
|
+
wave: 1
|
|
670
|
+
autonomous: true
|
|
671
|
+
objective: Database setup
|
|
672
|
+
---
|
|
673
|
+
|
|
674
|
+
## Task 1: Schema
|
|
675
|
+
`
|
|
676
|
+
);
|
|
677
|
+
|
|
678
|
+
fs.writeFileSync(
|
|
679
|
+
path.join(phaseDir, '03-02-PLAN.md'),
|
|
680
|
+
`---
|
|
681
|
+
wave: 1
|
|
682
|
+
autonomous: true
|
|
683
|
+
objective: Auth setup
|
|
684
|
+
---
|
|
685
|
+
|
|
686
|
+
## Task 1: JWT
|
|
687
|
+
`
|
|
688
|
+
);
|
|
689
|
+
|
|
690
|
+
fs.writeFileSync(
|
|
691
|
+
path.join(phaseDir, '03-03-PLAN.md'),
|
|
692
|
+
`---
|
|
693
|
+
wave: 2
|
|
694
|
+
autonomous: false
|
|
695
|
+
objective: API routes
|
|
696
|
+
---
|
|
697
|
+
|
|
698
|
+
## Task 1: Routes
|
|
699
|
+
`
|
|
700
|
+
);
|
|
701
|
+
|
|
702
|
+
const result = runQualiaTools('phase-plan-index 03', tmpDir);
|
|
703
|
+
assert.ok(result.success, `Command failed: ${result.error}`);
|
|
704
|
+
|
|
705
|
+
const output = JSON.parse(result.output);
|
|
706
|
+
assert.strictEqual(output.plans.length, 3, 'should have 3 plans');
|
|
707
|
+
assert.deepStrictEqual(output.waves['1'], ['03-01', '03-02'], 'wave 1 has 2 plans');
|
|
708
|
+
assert.deepStrictEqual(output.waves['2'], ['03-03'], 'wave 2 has 1 plan');
|
|
709
|
+
});
|
|
710
|
+
|
|
711
|
+
test('detects incomplete plans (no matching summary)', () => {
|
|
712
|
+
const phaseDir = path.join(tmpDir, '.planning', 'phases', '03-api');
|
|
713
|
+
fs.mkdirSync(phaseDir, { recursive: true });
|
|
714
|
+
|
|
715
|
+
// Plan with summary
|
|
716
|
+
fs.writeFileSync(path.join(phaseDir, '03-01-PLAN.md'), `---\nwave: 1\n---\n## Task 1`);
|
|
717
|
+
fs.writeFileSync(path.join(phaseDir, '03-01-SUMMARY.md'), `# Summary`);
|
|
718
|
+
|
|
719
|
+
// Plan without summary
|
|
720
|
+
fs.writeFileSync(path.join(phaseDir, '03-02-PLAN.md'), `---\nwave: 2\n---\n## Task 1`);
|
|
721
|
+
|
|
722
|
+
const result = runQualiaTools('phase-plan-index 03', tmpDir);
|
|
723
|
+
assert.ok(result.success, `Command failed: ${result.error}`);
|
|
724
|
+
|
|
725
|
+
const output = JSON.parse(result.output);
|
|
726
|
+
assert.strictEqual(output.plans[0].has_summary, true, 'first plan has summary');
|
|
727
|
+
assert.strictEqual(output.plans[1].has_summary, false, 'second plan has no summary');
|
|
728
|
+
assert.deepStrictEqual(output.incomplete, ['03-02'], 'incomplete list correct');
|
|
729
|
+
});
|
|
730
|
+
|
|
731
|
+
test('detects checkpoints (autonomous: false)', () => {
|
|
732
|
+
const phaseDir = path.join(tmpDir, '.planning', 'phases', '03-api');
|
|
733
|
+
fs.mkdirSync(phaseDir, { recursive: true });
|
|
734
|
+
|
|
735
|
+
fs.writeFileSync(
|
|
736
|
+
path.join(phaseDir, '03-01-PLAN.md'),
|
|
737
|
+
`---
|
|
738
|
+
wave: 1
|
|
739
|
+
autonomous: false
|
|
740
|
+
objective: Manual review needed
|
|
741
|
+
---
|
|
742
|
+
|
|
743
|
+
## Task 1: Review
|
|
744
|
+
`
|
|
745
|
+
);
|
|
746
|
+
|
|
747
|
+
const result = runQualiaTools('phase-plan-index 03', tmpDir);
|
|
748
|
+
assert.ok(result.success, `Command failed: ${result.error}`);
|
|
749
|
+
|
|
750
|
+
const output = JSON.parse(result.output);
|
|
751
|
+
assert.strictEqual(output.has_checkpoints, true, 'should detect checkpoint');
|
|
752
|
+
assert.strictEqual(output.plans[0].autonomous, false, 'plan marked non-autonomous');
|
|
753
|
+
});
|
|
754
|
+
|
|
755
|
+
test('phase not found returns error', () => {
|
|
756
|
+
const result = runQualiaTools('phase-plan-index 99', tmpDir);
|
|
757
|
+
assert.ok(result.success, `Command should succeed: ${result.error}`);
|
|
758
|
+
|
|
759
|
+
const output = JSON.parse(result.output);
|
|
760
|
+
assert.strictEqual(output.error, 'Phase not found', 'should report phase not found');
|
|
761
|
+
});
|
|
762
|
+
});
|
|
763
|
+
|
|
764
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
765
|
+
// state-snapshot command
|
|
766
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
767
|
+
|
|
768
|
+
describe('state-snapshot command', () => {
|
|
769
|
+
let tmpDir;
|
|
770
|
+
|
|
771
|
+
beforeEach(() => {
|
|
772
|
+
tmpDir = createTempProject();
|
|
773
|
+
});
|
|
774
|
+
|
|
775
|
+
afterEach(() => {
|
|
776
|
+
cleanup(tmpDir);
|
|
777
|
+
});
|
|
778
|
+
|
|
779
|
+
test('missing STATE.md returns error', () => {
|
|
780
|
+
const result = runQualiaTools('state-snapshot', tmpDir);
|
|
781
|
+
assert.ok(result.success, `Command should succeed: ${result.error}`);
|
|
782
|
+
|
|
783
|
+
const output = JSON.parse(result.output);
|
|
784
|
+
assert.strictEqual(output.error, 'STATE.md not found', 'should report missing file');
|
|
785
|
+
});
|
|
786
|
+
|
|
787
|
+
test('extracts basic fields from STATE.md', () => {
|
|
788
|
+
fs.writeFileSync(
|
|
789
|
+
path.join(tmpDir, '.planning', 'STATE.md'),
|
|
790
|
+
`# Project State
|
|
791
|
+
|
|
792
|
+
**Current Phase:** 03
|
|
793
|
+
**Current Phase Name:** API Layer
|
|
794
|
+
**Total Phases:** 6
|
|
795
|
+
**Current Plan:** 03-02
|
|
796
|
+
**Total Plans in Phase:** 3
|
|
797
|
+
**Status:** In progress
|
|
798
|
+
**Progress:** 45%
|
|
799
|
+
**Last Activity:** 2024-01-15
|
|
800
|
+
**Last Activity Description:** Completed 03-01-PLAN.md
|
|
801
|
+
`
|
|
802
|
+
);
|
|
803
|
+
|
|
804
|
+
const result = runQualiaTools('state-snapshot', tmpDir);
|
|
805
|
+
assert.ok(result.success, `Command failed: ${result.error}`);
|
|
806
|
+
|
|
807
|
+
const output = JSON.parse(result.output);
|
|
808
|
+
assert.strictEqual(output.current_phase, '03', 'current phase extracted');
|
|
809
|
+
assert.strictEqual(output.current_phase_name, 'API Layer', 'phase name extracted');
|
|
810
|
+
assert.strictEqual(output.total_phases, 6, 'total phases extracted');
|
|
811
|
+
assert.strictEqual(output.current_plan, '03-02', 'current plan extracted');
|
|
812
|
+
assert.strictEqual(output.total_plans_in_phase, 3, 'total plans extracted');
|
|
813
|
+
assert.strictEqual(output.status, 'In progress', 'status extracted');
|
|
814
|
+
assert.strictEqual(output.progress_percent, 45, 'progress extracted');
|
|
815
|
+
assert.strictEqual(output.last_activity, '2024-01-15', 'last activity date extracted');
|
|
816
|
+
});
|
|
817
|
+
|
|
818
|
+
test('extracts decisions table', () => {
|
|
819
|
+
fs.writeFileSync(
|
|
820
|
+
path.join(tmpDir, '.planning', 'STATE.md'),
|
|
821
|
+
`# Project State
|
|
822
|
+
|
|
823
|
+
**Current Phase:** 01
|
|
824
|
+
|
|
825
|
+
## Decisions Made
|
|
826
|
+
|
|
827
|
+
| Phase | Decision | Rationale |
|
|
828
|
+
|-------|----------|-----------|
|
|
829
|
+
| 01 | Use Prisma | Better DX than raw SQL |
|
|
830
|
+
| 02 | JWT auth | Stateless authentication |
|
|
831
|
+
`
|
|
832
|
+
);
|
|
833
|
+
|
|
834
|
+
const result = runQualiaTools('state-snapshot', tmpDir);
|
|
835
|
+
assert.ok(result.success, `Command failed: ${result.error}`);
|
|
836
|
+
|
|
837
|
+
const output = JSON.parse(result.output);
|
|
838
|
+
assert.strictEqual(output.decisions.length, 2, 'should have 2 decisions');
|
|
839
|
+
assert.strictEqual(output.decisions[0].phase, '01', 'first decision phase');
|
|
840
|
+
assert.strictEqual(output.decisions[0].summary, 'Use Prisma', 'first decision summary');
|
|
841
|
+
assert.strictEqual(output.decisions[0].rationale, 'Better DX than raw SQL', 'first decision rationale');
|
|
842
|
+
});
|
|
843
|
+
|
|
844
|
+
test('extracts blockers list', () => {
|
|
845
|
+
fs.writeFileSync(
|
|
846
|
+
path.join(tmpDir, '.planning', 'STATE.md'),
|
|
847
|
+
`# Project State
|
|
848
|
+
|
|
849
|
+
**Current Phase:** 03
|
|
850
|
+
|
|
851
|
+
## Blockers
|
|
852
|
+
|
|
853
|
+
- Waiting for API credentials
|
|
854
|
+
- Need design review for dashboard
|
|
855
|
+
`
|
|
856
|
+
);
|
|
857
|
+
|
|
858
|
+
const result = runQualiaTools('state-snapshot', tmpDir);
|
|
859
|
+
assert.ok(result.success, `Command failed: ${result.error}`);
|
|
860
|
+
|
|
861
|
+
const output = JSON.parse(result.output);
|
|
862
|
+
assert.deepStrictEqual(output.blockers, [
|
|
863
|
+
'Waiting for API credentials',
|
|
864
|
+
'Need design review for dashboard',
|
|
865
|
+
], 'blockers extracted');
|
|
866
|
+
});
|
|
867
|
+
|
|
868
|
+
test('extracts session continuity info', () => {
|
|
869
|
+
fs.writeFileSync(
|
|
870
|
+
path.join(tmpDir, '.planning', 'STATE.md'),
|
|
871
|
+
`# Project State
|
|
872
|
+
|
|
873
|
+
**Current Phase:** 03
|
|
874
|
+
|
|
875
|
+
## Session
|
|
876
|
+
|
|
877
|
+
**Last Date:** 2024-01-15
|
|
878
|
+
**Stopped At:** Phase 3, Plan 2, Task 1
|
|
879
|
+
**Resume File:** .planning/phases/03-api/03-02-PLAN.md
|
|
880
|
+
`
|
|
881
|
+
);
|
|
882
|
+
|
|
883
|
+
const result = runQualiaTools('state-snapshot', tmpDir);
|
|
884
|
+
assert.ok(result.success, `Command failed: ${result.error}`);
|
|
885
|
+
|
|
886
|
+
const output = JSON.parse(result.output);
|
|
887
|
+
assert.strictEqual(output.session.last_date, '2024-01-15', 'session date extracted');
|
|
888
|
+
assert.strictEqual(output.session.stopped_at, 'Phase 3, Plan 2, Task 1', 'stopped at extracted');
|
|
889
|
+
assert.strictEqual(output.session.resume_file, '.planning/phases/03-api/03-02-PLAN.md', 'resume file extracted');
|
|
890
|
+
});
|
|
891
|
+
|
|
892
|
+
test('handles paused_at field', () => {
|
|
893
|
+
fs.writeFileSync(
|
|
894
|
+
path.join(tmpDir, '.planning', 'STATE.md'),
|
|
895
|
+
`# Project State
|
|
896
|
+
|
|
897
|
+
**Current Phase:** 03
|
|
898
|
+
**Paused At:** Phase 3, Plan 1, Task 2 - mid-implementation
|
|
899
|
+
`
|
|
900
|
+
);
|
|
901
|
+
|
|
902
|
+
const result = runQualiaTools('state-snapshot', tmpDir);
|
|
903
|
+
assert.ok(result.success, `Command failed: ${result.error}`);
|
|
904
|
+
|
|
905
|
+
const output = JSON.parse(result.output);
|
|
906
|
+
assert.strictEqual(output.paused_at, 'Phase 3, Plan 1, Task 2 - mid-implementation', 'paused_at extracted');
|
|
907
|
+
});
|
|
908
|
+
});
|
|
909
|
+
|
|
910
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
911
|
+
// summary-extract command
|
|
912
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
913
|
+
|
|
914
|
+
describe('summary-extract command', () => {
|
|
915
|
+
let tmpDir;
|
|
916
|
+
|
|
917
|
+
beforeEach(() => {
|
|
918
|
+
tmpDir = createTempProject();
|
|
919
|
+
});
|
|
920
|
+
|
|
921
|
+
afterEach(() => {
|
|
922
|
+
cleanup(tmpDir);
|
|
923
|
+
});
|
|
924
|
+
|
|
925
|
+
test('missing file returns error', () => {
|
|
926
|
+
const result = runQualiaTools('summary-extract .planning/phases/01-test/01-01-SUMMARY.md', tmpDir);
|
|
927
|
+
assert.ok(result.success, `Command should succeed: ${result.error}`);
|
|
928
|
+
|
|
929
|
+
const output = JSON.parse(result.output);
|
|
930
|
+
assert.strictEqual(output.error, 'File not found', 'should report missing file');
|
|
931
|
+
});
|
|
932
|
+
|
|
933
|
+
test('extracts all fields from SUMMARY.md', () => {
|
|
934
|
+
const phaseDir = path.join(tmpDir, '.planning', 'phases', '01-foundation');
|
|
935
|
+
fs.mkdirSync(phaseDir, { recursive: true });
|
|
936
|
+
|
|
937
|
+
fs.writeFileSync(
|
|
938
|
+
path.join(phaseDir, '01-01-SUMMARY.md'),
|
|
939
|
+
`---
|
|
940
|
+
one-liner: Set up Prisma with User and Project models
|
|
941
|
+
key-files:
|
|
942
|
+
- prisma/schema.prisma
|
|
943
|
+
- src/lib/db.ts
|
|
944
|
+
tech-stack:
|
|
945
|
+
added:
|
|
946
|
+
- prisma
|
|
947
|
+
- zod
|
|
948
|
+
patterns-established:
|
|
949
|
+
- Repository pattern
|
|
950
|
+
- Dependency injection
|
|
951
|
+
key-decisions:
|
|
952
|
+
- Use Prisma over Drizzle: Better DX and ecosystem
|
|
953
|
+
- Single database: Start simple, shard later
|
|
954
|
+
---
|
|
955
|
+
|
|
956
|
+
# Summary
|
|
957
|
+
|
|
958
|
+
Full summary content here.
|
|
959
|
+
`
|
|
960
|
+
);
|
|
961
|
+
|
|
962
|
+
const result = runQualiaTools('summary-extract .planning/phases/01-foundation/01-01-SUMMARY.md', tmpDir);
|
|
963
|
+
assert.ok(result.success, `Command failed: ${result.error}`);
|
|
964
|
+
|
|
965
|
+
const output = JSON.parse(result.output);
|
|
966
|
+
assert.strictEqual(output.path, '.planning/phases/01-foundation/01-01-SUMMARY.md', 'path correct');
|
|
967
|
+
assert.strictEqual(output.one_liner, 'Set up Prisma with User and Project models', 'one-liner extracted');
|
|
968
|
+
assert.deepStrictEqual(output.key_files, ['prisma/schema.prisma', 'src/lib/db.ts'], 'key files extracted');
|
|
969
|
+
assert.deepStrictEqual(output.tech_added, ['prisma', 'zod'], 'tech added extracted');
|
|
970
|
+
assert.deepStrictEqual(output.patterns, ['Repository pattern', 'Dependency injection'], 'patterns extracted');
|
|
971
|
+
assert.strictEqual(output.decisions.length, 2, 'decisions extracted');
|
|
972
|
+
});
|
|
973
|
+
|
|
974
|
+
test('selective extraction with --fields', () => {
|
|
975
|
+
const phaseDir = path.join(tmpDir, '.planning', 'phases', '01-foundation');
|
|
976
|
+
fs.mkdirSync(phaseDir, { recursive: true });
|
|
977
|
+
|
|
978
|
+
fs.writeFileSync(
|
|
979
|
+
path.join(phaseDir, '01-01-SUMMARY.md'),
|
|
980
|
+
`---
|
|
981
|
+
one-liner: Set up database
|
|
982
|
+
key-files:
|
|
983
|
+
- prisma/schema.prisma
|
|
984
|
+
tech-stack:
|
|
985
|
+
added:
|
|
986
|
+
- prisma
|
|
987
|
+
patterns-established:
|
|
988
|
+
- Repository pattern
|
|
989
|
+
key-decisions:
|
|
990
|
+
- Use Prisma: Better DX
|
|
991
|
+
---
|
|
992
|
+
`
|
|
993
|
+
);
|
|
994
|
+
|
|
995
|
+
const result = runQualiaTools('summary-extract .planning/phases/01-foundation/01-01-SUMMARY.md --fields one_liner,key_files', tmpDir);
|
|
996
|
+
assert.ok(result.success, `Command failed: ${result.error}`);
|
|
997
|
+
|
|
998
|
+
const output = JSON.parse(result.output);
|
|
999
|
+
assert.strictEqual(output.one_liner, 'Set up database', 'one_liner included');
|
|
1000
|
+
assert.deepStrictEqual(output.key_files, ['prisma/schema.prisma'], 'key_files included');
|
|
1001
|
+
assert.strictEqual(output.tech_added, undefined, 'tech_added excluded');
|
|
1002
|
+
assert.strictEqual(output.patterns, undefined, 'patterns excluded');
|
|
1003
|
+
assert.strictEqual(output.decisions, undefined, 'decisions excluded');
|
|
1004
|
+
});
|
|
1005
|
+
|
|
1006
|
+
test('handles missing frontmatter fields gracefully', () => {
|
|
1007
|
+
const phaseDir = path.join(tmpDir, '.planning', 'phases', '01-foundation');
|
|
1008
|
+
fs.mkdirSync(phaseDir, { recursive: true });
|
|
1009
|
+
|
|
1010
|
+
fs.writeFileSync(
|
|
1011
|
+
path.join(phaseDir, '01-01-SUMMARY.md'),
|
|
1012
|
+
`---
|
|
1013
|
+
one-liner: Minimal summary
|
|
1014
|
+
---
|
|
1015
|
+
|
|
1016
|
+
# Summary
|
|
1017
|
+
`
|
|
1018
|
+
);
|
|
1019
|
+
|
|
1020
|
+
const result = runQualiaTools('summary-extract .planning/phases/01-foundation/01-01-SUMMARY.md', tmpDir);
|
|
1021
|
+
assert.ok(result.success, `Command failed: ${result.error}`);
|
|
1022
|
+
|
|
1023
|
+
const output = JSON.parse(result.output);
|
|
1024
|
+
assert.strictEqual(output.one_liner, 'Minimal summary', 'one-liner extracted');
|
|
1025
|
+
assert.deepStrictEqual(output.key_files, [], 'key_files defaults to empty');
|
|
1026
|
+
assert.deepStrictEqual(output.tech_added, [], 'tech_added defaults to empty');
|
|
1027
|
+
assert.deepStrictEqual(output.patterns, [], 'patterns defaults to empty');
|
|
1028
|
+
assert.deepStrictEqual(output.decisions, [], 'decisions defaults to empty');
|
|
1029
|
+
});
|
|
1030
|
+
|
|
1031
|
+
test('parses key-decisions with rationale', () => {
|
|
1032
|
+
const phaseDir = path.join(tmpDir, '.planning', 'phases', '01-foundation');
|
|
1033
|
+
fs.mkdirSync(phaseDir, { recursive: true });
|
|
1034
|
+
|
|
1035
|
+
fs.writeFileSync(
|
|
1036
|
+
path.join(phaseDir, '01-01-SUMMARY.md'),
|
|
1037
|
+
`---
|
|
1038
|
+
key-decisions:
|
|
1039
|
+
- Use Prisma: Better DX than alternatives
|
|
1040
|
+
- JWT tokens: Stateless auth for scalability
|
|
1041
|
+
---
|
|
1042
|
+
`
|
|
1043
|
+
);
|
|
1044
|
+
|
|
1045
|
+
const result = runQualiaTools('summary-extract .planning/phases/01-foundation/01-01-SUMMARY.md', tmpDir);
|
|
1046
|
+
assert.ok(result.success, `Command failed: ${result.error}`);
|
|
1047
|
+
|
|
1048
|
+
const output = JSON.parse(result.output);
|
|
1049
|
+
assert.strictEqual(output.decisions[0].summary, 'Use Prisma', 'decision summary parsed');
|
|
1050
|
+
assert.strictEqual(output.decisions[0].rationale, 'Better DX than alternatives', 'decision rationale parsed');
|
|
1051
|
+
assert.strictEqual(output.decisions[1].summary, 'JWT tokens', 'second decision summary');
|
|
1052
|
+
assert.strictEqual(output.decisions[1].rationale, 'Stateless auth for scalability', 'second decision rationale');
|
|
1053
|
+
});
|
|
1054
|
+
});
|