nubos-pilot 0.4.0 → 0.5.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 +149 -0
- package/agents/np-executor.md +10 -5
- package/agents/np-nyquist-auditor.md +17 -17
- package/agents/np-plan-checker.md +39 -29
- package/agents/np-planner.md +83 -6
- package/agents/np-verifier.md +19 -15
- package/bin/install.js +33 -52
- package/bin/np-tools/_commands.cjs +23 -39
- package/bin/np-tools/add-tests.cjs +34 -37
- package/bin/np-tools/add-tests.test.cjs +34 -28
- package/bin/np-tools/add-todo.cjs +2 -2
- package/bin/np-tools/checkpoint.test.cjs +17 -17
- package/bin/np-tools/commit-task.cjs +14 -33
- package/bin/np-tools/commit-task.test.cjs +19 -19
- package/bin/np-tools/discuss-phase.cjs +28 -41
- package/bin/np-tools/discuss-phase.test.cjs +37 -53
- package/bin/np-tools/doctor.cjs +63 -0
- package/bin/np-tools/execute-milestone.cjs +225 -0
- package/bin/np-tools/execute-milestone.test.cjs +154 -0
- package/bin/np-tools/help.test.cjs +4 -6
- package/bin/np-tools/init-dispatch.test.cjs +27 -41
- package/bin/np-tools/new-milestone.cjs +121 -121
- package/bin/np-tools/new-milestone.test.cjs +56 -49
- package/bin/np-tools/new-project.cjs +97 -95
- package/bin/np-tools/new-project.test.cjs +49 -41
- package/bin/np-tools/park.cjs +4 -30
- package/bin/np-tools/park.test.cjs +10 -9
- package/bin/np-tools/pause-work.test.cjs +4 -4
- package/bin/np-tools/plan-milestone.cjs +381 -0
- package/bin/np-tools/plan-milestone.test.cjs +209 -0
- package/bin/np-tools/research-phase.cjs +36 -53
- package/bin/np-tools/research-phase.test.cjs +31 -40
- package/bin/np-tools/reset-slice.cjs +93 -5
- package/bin/np-tools/reset-slice.test.cjs +89 -37
- package/bin/np-tools/resume-work.test.cjs +7 -7
- package/bin/np-tools/skip.cjs +4 -30
- package/bin/np-tools/skip.test.cjs +12 -12
- package/bin/np-tools/slug.cjs +2 -2
- package/bin/np-tools/undo-task.cjs +33 -6
- package/bin/np-tools/undo-task.test.cjs +63 -74
- package/bin/np-tools/undo.cjs +55 -28
- package/bin/np-tools/undo.test.cjs +81 -68
- package/bin/np-tools/unpark.cjs +4 -30
- package/bin/np-tools/unpark.test.cjs +10 -9
- package/bin/np-tools/verify-work.cjs +67 -42
- package/bin/np-tools/verify-work.test.cjs +46 -30
- package/lib/agents.test.cjs +22 -53
- package/lib/checkpoint.test.cjs +35 -35
- package/lib/fixtures/plans/cycle/tasks/{T-01.md → T0001/T0001-PLAN.md} +4 -4
- package/lib/fixtures/plans/cycle/tasks/{T-02.md → T0002/T0002-PLAN.md} +4 -4
- package/lib/fixtures/plans/cycle/tasks/{T-03.md → T0003/T0003-PLAN.md} +4 -4
- package/lib/fixtures/plans/{parallel/tasks/T-01.md → linear/tasks/T0001/T0001-PLAN.md} +3 -3
- package/lib/fixtures/plans/linear/tasks/{T-02.md → T0002/T0002-PLAN.md} +4 -4
- package/lib/fixtures/plans/linear/tasks/{T-03.md → T0003/T0003-PLAN.md} +4 -4
- package/lib/fixtures/plans/{linear/tasks/T-01.md → parallel/tasks/T0001/T0001-PLAN.md} +3 -3
- package/lib/fixtures/plans/parallel/tasks/{T-02.md → T0002/T0002-PLAN.md} +4 -4
- package/lib/fixtures/plans/parallel/tasks/{T-03.md → T0003/T0003-PLAN.md} +4 -4
- package/lib/fixtures/plans/wave-conflict/tasks/{T-01.md → T0001/T0001-PLAN.md} +3 -3
- package/lib/fixtures/plans/wave-conflict/tasks/{T-02.md → T0002/T0002-PLAN.md} +4 -4
- package/lib/git.test.cjs +21 -21
- package/lib/layout.cjs +266 -0
- package/lib/layout.test.cjs +140 -0
- package/lib/model-profiles.cjs +4 -4
- package/lib/model-profiles.test.cjs +9 -6
- package/lib/roadmap.cjs +38 -3
- package/lib/tasks.cjs +26 -20
- package/lib/tasks.test.cjs +45 -40
- package/lib/verify.cjs +36 -39
- package/lib/verify.test.cjs +47 -46
- package/np-tools.cjs +22 -170
- package/package.json +1 -1
- package/templates/milestone/CONTEXT.md +28 -0
- package/templates/milestone/META.json +11 -0
- package/templates/milestone/ROADMAP.md +11 -0
- package/templates/slice/ASSESSMENT.md +24 -0
- package/templates/slice/PLAN.md +43 -0
- package/templates/slice/RESEARCH.md +20 -0
- package/templates/slice/SUMMARY.md +17 -0
- package/templates/slice/UAT.md +21 -0
- package/templates/task/PLAN.md +48 -0
- package/templates/task/SUMMARY.md +24 -0
- package/workflows/add-tests.md +1 -0
- package/workflows/add-todo.md +6 -5
- package/workflows/discuss-phase.md +44 -34
- package/workflows/discuss-project.md +1 -0
- package/workflows/doctor.md +6 -0
- package/workflows/execute-phase.md +89 -75
- package/workflows/help.md +6 -0
- package/workflows/new-milestone.md +30 -51
- package/workflows/new-project.md +12 -7
- package/workflows/note.md +4 -3
- package/workflows/park.md +1 -0
- package/workflows/plan-phase.md +140 -200
- package/workflows/research-phase.md +18 -17
- package/workflows/reset-slice.md +75 -27
- package/workflows/resume-work.md +2 -2
- package/workflows/scan-codebase.md +1 -0
- package/workflows/session-report.md +1 -0
- package/workflows/skip.md +1 -0
- package/workflows/stats.md +1 -0
- package/workflows/thread.md +4 -3
- package/workflows/undo-task.md +54 -27
- package/workflows/undo.md +75 -38
- package/workflows/unpark.md +1 -0
- package/workflows/update-docs.md +1 -0
- package/workflows/validate-phase.md +52 -103
- package/workflows/verify-work.md +16 -20
- package/agents/np-ai-researcher.md +0 -140
- package/agents/np-code-fixer.md +0 -381
- package/agents/np-code-reviewer.md +0 -352
- package/agents/np-domain-researcher.md +0 -136
- package/agents/np-eval-auditor.md +0 -167
- package/agents/np-eval-planner.md +0 -153
- package/agents/np-framework-selector.md +0 -171
- package/agents/np-security-auditor.md +0 -206
- package/agents/np-ui-auditor.md +0 -369
- package/agents/np-ui-checker.md +0 -192
- package/agents/np-ui-researcher.md +0 -324
- package/bin/np-tools/ai-integration-phase.cjs +0 -109
- package/bin/np-tools/ai-integration-phase.test.cjs +0 -123
- package/bin/np-tools/autonomous.cjs +0 -69
- package/bin/np-tools/autonomous.test.cjs +0 -74
- package/bin/np-tools/code-review.cjs +0 -133
- package/bin/np-tools/code-review.test.cjs +0 -96
- package/bin/np-tools/discuss-phase-power.cjs +0 -265
- package/bin/np-tools/discuss-phase-power.test.cjs +0 -242
- package/bin/np-tools/dispatch.cjs +0 -116
- package/bin/np-tools/eval-review.cjs +0 -116
- package/bin/np-tools/eval-review.test.cjs +0 -123
- package/bin/np-tools/execute-phase.cjs +0 -182
- package/bin/np-tools/execute-phase.test.cjs +0 -116
- package/bin/np-tools/execute-plan.cjs +0 -124
- package/bin/np-tools/execute-plan.test.cjs +0 -82
- package/bin/np-tools/next.cjs +0 -7
- package/bin/np-tools/next.test.cjs +0 -30
- package/bin/np-tools/phase.cjs +0 -71
- package/bin/np-tools/phase.test.cjs +0 -81
- package/bin/np-tools/plan-diff.cjs +0 -57
- package/bin/np-tools/plan-diff.test.cjs +0 -134
- package/bin/np-tools/plan-milestone-gaps.cjs +0 -115
- package/bin/np-tools/plan-milestone-gaps.test.cjs +0 -122
- package/bin/np-tools/plan-phase.cjs +0 -350
- package/bin/np-tools/plan-phase.test.cjs +0 -263
- package/bin/np-tools/progress.cjs +0 -7
- package/bin/np-tools/progress.test.cjs +0 -44
- package/bin/np-tools/queue.cjs +0 -213
- package/bin/np-tools/triage.cjs +0 -128
- package/bin/np-tools/ui-phase.cjs +0 -108
- package/bin/np-tools/ui-phase.test.cjs +0 -121
- package/bin/np-tools/ui-review.cjs +0 -108
- package/bin/np-tools/ui-review.test.cjs +0 -120
- package/lib/gaps.cjs +0 -197
- package/lib/gaps.test.cjs +0 -200
- package/lib/next.cjs +0 -236
- package/lib/next.test.cjs +0 -194
- package/lib/phase.cjs +0 -95
- package/lib/phase.test.cjs +0 -189
- package/lib/plan-diff.cjs +0 -173
- package/lib/plan-diff.test.cjs +0 -217
- package/lib/plan.cjs +0 -85
- package/lib/plan.test.cjs +0 -263
- package/lib/progress.cjs +0 -95
- package/lib/progress.test.cjs +0 -116
- package/lib/undo.cjs +0 -179
- package/lib/undo.test.cjs +0 -261
- package/templates/AI-SPEC.md +0 -90
- package/templates/CONTEXT.md +0 -32
- package/templates/PLAN.md +0 -69
- package/templates/SECURITY.md +0 -61
- package/templates/UI-SPEC.md +0 -64
- package/workflows/add-backlog.md +0 -212
- package/workflows/ai-integration-phase.md +0 -230
- package/workflows/autonomous.md +0 -94
- package/workflows/cleanup.md +0 -325
- package/workflows/code-review-fix.md +0 -435
- package/workflows/code-review.md +0 -447
- package/workflows/discuss-phase-assumptions.md +0 -269
- package/workflows/discuss-phase-power.md +0 -139
- package/workflows/dispatch.md +0 -9
- package/workflows/eval-review.md +0 -243
- package/workflows/execute-plan.md +0 -82
- package/workflows/next.md +0 -8
- package/workflows/plan-milestone-gaps.md +0 -233
- package/workflows/progress.md +0 -8
- package/workflows/queue.md +0 -9
- package/workflows/review.md +0 -489
- package/workflows/secure-phase.md +0 -209
- package/workflows/triage.md +0 -9
- package/workflows/ui-phase.md +0 -246
- package/workflows/ui-review.md +0 -222
package/bin/install.js
CHANGED
|
@@ -222,19 +222,18 @@ async function _runInitQuestions(detectedRuntime, askUser, flags) {
|
|
|
222
222
|
const scope = f.scope || (await askUser({ type: 'select', question: 'Installation scope?',
|
|
223
223
|
options: VALID_SCOPES, default: 'local' })).value;
|
|
224
224
|
const model_profile = (await askUser({ type: 'select', question: 'Model-Profile?',
|
|
225
|
-
options: ['
|
|
226
|
-
const commit_docs = (await askUser({ type: 'confirm', question: 'Commit documentation artefacts?', default: true })).value;
|
|
227
|
-
const branching_strategy = (await askUser({ type: 'select', question: 'Branching strategy?',
|
|
228
|
-
options: ['single-branch', 'phase-branches', 'milestone-branches'], default: 'single-branch' })).value;
|
|
229
|
-
const phase_branch_template = (await askUser({ type: 'input', question: 'Phase branch template?', default: 'phase/{number}-{slug}' })).value;
|
|
230
|
-
const milestone_branch_template = (await askUser({ type: 'input', question: 'Milestone branch template?', default: 'milestone/{name}' })).value;
|
|
231
|
-
const parallelization = (await askUser({ type: 'confirm', question: 'Enable parallelization?', default: true })).value;
|
|
232
|
-
const research = (await askUser({ type: 'confirm', question: 'Enable research step?', default: false })).value;
|
|
233
|
-
const plan_checker = (await askUser({ type: 'confirm', question: 'Enable plan_checker?', default: true })).value;
|
|
234
|
-
const verifier = (await askUser({ type: 'confirm', question: 'Enable verifier?', default: true })).value;
|
|
225
|
+
options: ['frontier', 'quality', 'balanced', 'budget', 'inherit'], default: 'frontier' })).value;
|
|
235
226
|
const response_language = (await askUser({ type: 'input', question: 'Response language (ISO-639 code)?', default: 'en' })).value;
|
|
236
|
-
return {
|
|
237
|
-
|
|
227
|
+
return {
|
|
228
|
+
runtime, runtimes, scope, mcp: !!f.mcp,
|
|
229
|
+
model_profile,
|
|
230
|
+
response_language,
|
|
231
|
+
commit_docs: true,
|
|
232
|
+
parallelization: true,
|
|
233
|
+
research: true,
|
|
234
|
+
plan_checker: true,
|
|
235
|
+
verifier: true,
|
|
236
|
+
};
|
|
238
237
|
}
|
|
239
238
|
|
|
240
239
|
function _repairCodexConfig() {
|
|
@@ -251,56 +250,38 @@ function _repairCodexConfig() {
|
|
|
251
250
|
|
|
252
251
|
const LEGACY_AGENTS = new Set(['claude', 'codex', 'gemini', 'opencode']);
|
|
253
252
|
|
|
253
|
+
const DEFAULT_CLAUDE_MD = '# CLAUDE.md\n\n'
|
|
254
|
+
+ 'Project guidance for Claude Code. Add project-specific instructions above the'
|
|
255
|
+
+ ' managed block — `npx nubos-pilot` only rewrites the block between the markers.\n';
|
|
256
|
+
|
|
254
257
|
function _rewriteManagedMarkdown(projectRoot, runtimes) {
|
|
255
258
|
const claudePath = path.join(projectRoot, 'CLAUDE.md');
|
|
256
259
|
const agentsPath = path.join(projectRoot, 'AGENTS.md');
|
|
257
260
|
const geminiPath = path.join(projectRoot, 'GEMINI.md');
|
|
258
|
-
const
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
if (agentsBase !== null) {
|
|
274
|
-
const agentsNext = managedBlockMod.rewriteBlock(agentsBase, MANAGED_BLOCK_INNER);
|
|
275
|
-
atomicWriteFileSync(agentsPath, agentsNext);
|
|
276
|
-
}
|
|
277
|
-
|
|
278
|
-
let geminiBase;
|
|
279
|
-
if (fs.existsSync(geminiPath)) {
|
|
280
|
-
geminiBase = fs.readFileSync(geminiPath, 'utf-8');
|
|
281
|
-
} else if (claudeExists) {
|
|
282
|
-
geminiBase = agentsMdMod.generateAgentsMd(fs.readFileSync(claudePath, 'utf-8'), 'gemini');
|
|
283
|
-
} else {
|
|
284
|
-
geminiBase = null;
|
|
285
|
-
}
|
|
286
|
-
if (geminiBase !== null) {
|
|
287
|
-
const geminiNext = managedBlockMod.rewriteBlock(geminiBase, MANAGED_BLOCK_INNER);
|
|
288
|
-
atomicWriteFileSync(geminiPath, geminiNext);
|
|
289
|
-
}
|
|
261
|
+
const claudeBase = fs.existsSync(claudePath)
|
|
262
|
+
? fs.readFileSync(claudePath, 'utf-8')
|
|
263
|
+
: DEFAULT_CLAUDE_MD;
|
|
264
|
+
const claudeNext = managedBlockMod.rewriteBlock(claudeBase, MANAGED_BLOCK_INNER);
|
|
265
|
+
atomicWriteFileSync(claudePath, claudeNext);
|
|
266
|
+
|
|
267
|
+
const agentsBase = fs.existsSync(agentsPath)
|
|
268
|
+
? fs.readFileSync(agentsPath, 'utf-8')
|
|
269
|
+
: agentsMdMod.generateAgentsMd(claudeNext, 'codex');
|
|
270
|
+
atomicWriteFileSync(agentsPath, managedBlockMod.rewriteBlock(agentsBase, MANAGED_BLOCK_INNER));
|
|
271
|
+
|
|
272
|
+
const geminiBase = fs.existsSync(geminiPath)
|
|
273
|
+
? fs.readFileSync(geminiPath, 'utf-8')
|
|
274
|
+
: agentsMdMod.generateAgentsMd(claudeNext, 'gemini');
|
|
275
|
+
atomicWriteFileSync(geminiPath, managedBlockMod.rewriteBlock(geminiBase, MANAGED_BLOCK_INNER));
|
|
290
276
|
|
|
291
277
|
const extras = (runtimes || []).filter((id) => !LEGACY_AGENTS.has(id));
|
|
292
278
|
for (const id of extras) {
|
|
293
279
|
const meta = registryMod.getRuntimeMeta(id);
|
|
294
280
|
if (!meta) continue;
|
|
295
281
|
const targetPath = registryMod.runtimeAgentsPath(meta, 'local', projectRoot);
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
} else if (claudeExists) {
|
|
300
|
-
base = agentsMdMod.generateAgentsMd(fs.readFileSync(claudePath, 'utf-8'), 'codex');
|
|
301
|
-
} else {
|
|
302
|
-
base = '';
|
|
303
|
-
}
|
|
282
|
+
const base = fs.existsSync(targetPath)
|
|
283
|
+
? fs.readFileSync(targetPath, 'utf-8')
|
|
284
|
+
: agentsMdMod.generateAgentsMd(claudeNext, 'codex');
|
|
304
285
|
const next = managedBlockMod.rewriteBlock(base, MANAGED_BLOCK_INNER);
|
|
305
286
|
fs.mkdirSync(path.dirname(targetPath), { recursive: true });
|
|
306
287
|
atomicWriteFileSync(targetPath, next);
|
|
@@ -1,70 +1,54 @@
|
|
|
1
1
|
const COMMANDS = [
|
|
2
|
-
{ name: 'next', category: 'Utility', description: 'Print the next actionable step' },
|
|
3
|
-
{ name: 'progress', category: 'Utility', description: 'Report % complete across phases and plans' },
|
|
4
2
|
{ name: 'state', category: 'Utility', description: 'Print the current project state snapshot' },
|
|
5
3
|
{ name: 'help', category: 'Utility', description: 'List available commands' },
|
|
6
4
|
{ name: 'init', category: 'Utility', description: 'Dispatcher init payload for workflows' },
|
|
7
5
|
|
|
8
|
-
{ name: 'discuss-
|
|
9
|
-
{ name: 'discuss-phase
|
|
10
|
-
{ name: 'research-phase', category: 'Planning', description: '
|
|
11
|
-
{ name: 'plan-
|
|
12
|
-
{ name: 'new-project', category: 'Planning', description: 'Greenfield project init (PROJECT.md + REQUIREMENTS.md +
|
|
13
|
-
{ name: 'new-milestone', category: 'Planning', description: 'Append
|
|
14
|
-
{ name: 'plan-milestone-gaps', category: 'Planning', description: 'Create corrective phases from audit gaps' },
|
|
6
|
+
{ name: 'discuss-project', category: 'Planning', description: 'Adaptive project-context interview (writes PROJECT.md decisions)' },
|
|
7
|
+
{ name: 'discuss-phase', category: 'Planning', description: 'Adaptive milestone-context interview (writes M<NNN>-CONTEXT.md)' },
|
|
8
|
+
{ name: 'research-phase', category: 'Planning', description: 'Milestone-level research (WebFetch + MCP; offline fallback)' },
|
|
9
|
+
{ name: 'plan-milestone', category: 'Planning', description: 'Plan a milestone: scaffolds slices + tasks' },
|
|
10
|
+
{ name: 'new-project', category: 'Planning', description: 'Greenfield project init (PROJECT.md + REQUIREMENTS.md + M001 milestone)' },
|
|
11
|
+
{ name: 'new-milestone', category: 'Planning', description: 'Append a new milestone (M<NNN>) to an existing project' },
|
|
15
12
|
{ name: 'agent-skills', category: 'Planning', description: 'Print agent_skills config for a given subagent' },
|
|
16
13
|
|
|
17
|
-
{ name: 'execute-
|
|
18
|
-
{ name: '
|
|
19
|
-
{ name: 'commit-task', category: 'Execution', description: 'Atomic per-task git commit via lib/git.cjs (D-03/D-25 enforced)' },
|
|
14
|
+
{ name: 'execute-milestone', category: 'Execution', description: 'Wave-based milestone execution — slice by slice, tasks parallel within a slice' },
|
|
15
|
+
{ name: 'commit-task', category: 'Execution', description: 'Atomic per-task git commit via lib/git.cjs' },
|
|
20
16
|
{ name: 'checkpoint', category: 'Execution', description: 'Per-task crash-safety checkpoint CRUD (start/transition/touch/show)' },
|
|
21
|
-
{ name: '
|
|
22
|
-
{ name: 'verify-work', category: 'Execution', description: 'Two-pass goal-backward verification (D-21/D-22 VERIFICATION.md render/record)' },
|
|
17
|
+
{ name: 'verify-work', category: 'Execution', description: 'Two-pass goal-backward verification (milestone-level VERIFICATION.md)' },
|
|
23
18
|
{ name: 'add-tests', category: 'Execution', description: 'Persist VERIFICATION Pass-cases as node:test UAT (Sentinel-preserving)' },
|
|
24
19
|
{ name: 'pause-work', category: 'Execution', description: 'Stamp STATE.session.stopped_at + resume_file for explicit handoff' },
|
|
25
20
|
{ name: 'resume-work', category: 'Execution', description: 'Classify session state (resume | orphan | clean) from STATE + checkpoints' },
|
|
26
21
|
|
|
27
|
-
{ name: 'undo', category: 'Execution', description: 'Revert all task commits of a phase or plan via git revert (no history rewrite)' },
|
|
28
|
-
{ name: 'undo-task', category: 'Execution', description: 'Revert a single task commit and reset task status to pending' },
|
|
29
|
-
{ name: 'reset-slice', category: 'Execution', description: 'Restore working-tree files of the in-flight task and clear current_task (no commit)' },
|
|
30
22
|
{ name: 'skip', category: 'Execution', description: 'Mark task status skipped (lifecycle CRUD)' },
|
|
31
23
|
{ name: 'park', category: 'Execution', description: 'Mark task status parked (lifecycle CRUD)' },
|
|
32
24
|
{ name: 'unpark', category: 'Execution', description: 'Return a parked task to pending (lifecycle CRUD)' },
|
|
33
25
|
|
|
34
|
-
{ name: '
|
|
35
|
-
{ name: '
|
|
36
|
-
{ name: '
|
|
37
|
-
{ name: 'triage', category: 'Install', description: 'Interactive per-item triage loop (promote/keep/drop)' },
|
|
26
|
+
{ name: 'undo', category: 'Execution', description: 'Revert every task commit of a milestone or slice via git revert (no history rewrite)' },
|
|
27
|
+
{ name: 'undo-task', category: 'Execution', description: 'Revert a single task commit and reset task status to pending' },
|
|
28
|
+
{ name: 'reset-slice', category: 'Execution', description: 'Discard in-flight task: restore working tree from HEAD, drop checkpoint, clear STATE.current_task' },
|
|
38
29
|
|
|
39
|
-
{ name: '
|
|
30
|
+
{ name: 'doctor', category: 'Install', description: '5-check install-integrity scan (--fix for auto-safe fixes)' },
|
|
31
|
+
{ name: 'scan-codebase', category: 'Install', description: 'Initial deep codebase inventory → .nubos-pilot/codebase/ skill docs' },
|
|
32
|
+
{ name: 'update-docs', category: 'Install', description: 'Refresh stale module docs after code changes' },
|
|
40
33
|
|
|
34
|
+
{ name: 'resolve-model', category: 'Utility', description: 'Resolve agent/tier to model alias or id (Tier×Profile matrix)' },
|
|
41
35
|
{ name: 'metrics', category: 'Utility', description: 'Record JSONL metrics entry (record | now | start-timestamp | end-timestamp)' },
|
|
42
36
|
|
|
43
|
-
{ name: '
|
|
37
|
+
{ name: 'validate-phase', category: 'Review', description: 'Nyquist validation gap-fill via np-nyquist-auditor' },
|
|
44
38
|
|
|
45
|
-
{ name: '
|
|
46
|
-
{ name: '
|
|
47
|
-
{ name: '
|
|
48
|
-
{ name: 'eval-review', category: 'Review', description: 'Retroactive eval-coverage audit on a completed phase' },
|
|
39
|
+
{ name: 'add-todo', category: 'Capture', description: 'Capture a pending todo to .nubos-pilot/todos/pending/ + increment STATE count' },
|
|
40
|
+
{ name: 'note', category: 'Capture', description: 'Capture a free-form note (project default, --global writes to ~/.nubos-pilot/notes/)' },
|
|
41
|
+
{ name: 'add-backlog', category: 'Capture', description: 'Append backlog item to ROADMAP.md' },
|
|
49
42
|
|
|
50
43
|
{ name: 'askuser', category: 'Utility', description: 'Capability-layer prompt wrapper (reads spec JSON, returns chosen label)' },
|
|
51
|
-
{ name: 'commit', category: 'Utility', description: 'Atomic git commit wrapper with gitignore-guard
|
|
44
|
+
{ name: 'commit', category: 'Utility', description: 'Atomic git commit wrapper with gitignore-guard' },
|
|
52
45
|
{ name: 'config-get', category: 'Utility', description: 'Read value from .nubos-pilot/config.json by dotted key path' },
|
|
53
|
-
{ name: 'generate-slug', category: 'Utility', description: 'Slugify text via lib/
|
|
54
|
-
{ name: 'phase', category: 'Utility', description: 'Phase utilities (next-decimal <base> — used by add-backlog 999.x)' },
|
|
46
|
+
{ name: 'generate-slug', category: 'Utility', description: 'Slugify text via lib/layout.cjs.slugify' },
|
|
55
47
|
{ name: 'stats', category: 'Utility', description: 'Aggregated project stats (roadmap + STATE + git + metrics JSON shape)' },
|
|
56
48
|
|
|
57
|
-
{ name: 'code-review', category: 'Review', description: 'Source-file review via np-code-reviewer (depth quick/standard/deep) — writes REVIEW.md sidecar' },
|
|
58
|
-
{ name: 'code-review-fix', category: 'Review', description: 'Auto-fix REVIEW.md findings via np-code-fixer (atomic commit per finding)' },
|
|
59
|
-
{ name: 'review', category: 'Review', description: 'Cross-AI peer review via 7-CLI fan-out (gemini/claude/codex/coderabbit/opencode/qwen/cursor)' },
|
|
60
|
-
{ name: 'secure-phase', category: 'Review', description: 'Threat-mitigation audit via np-security-auditor against PLAN.md threat_model' },
|
|
61
|
-
{ name: 'validate-phase', category: 'Review', description: 'Nyquist validation gap-fill via np-nyquist-auditor' },
|
|
62
|
-
{ name: 'add-todo', category: 'Capture', description: 'Capture a pending todo to .nubos-pilot/todos/pending/ + increment STATE count' },
|
|
63
|
-
{ name: 'note', category: 'Capture', description: 'Capture a free-form note (project default, --global writes to ~/.nubos-pilot/notes/)' },
|
|
64
|
-
{ name: 'add-backlog', category: 'Capture', description: 'Append backlog item 999.x to ROADMAP.md + scaffold phase dir' },
|
|
65
49
|
{ name: 'thread', category: 'Utility', description: 'Cross-session thread CRUD (create/resume under .nubos-pilot/threads/)' },
|
|
66
50
|
{ name: 'session-report', category: 'Utility', description: 'Generate session report from metrics since .last-session pointer' },
|
|
67
|
-
{ name: 'cleanup', category: 'Utility', description: 'Archive completed milestones to .nubos-pilot/archive/v<X.Y>/
|
|
51
|
+
{ name: 'cleanup', category: 'Utility', description: 'Archive completed milestones to .nubos-pilot/archive/v<X.Y>/' },
|
|
68
52
|
];
|
|
69
53
|
|
|
70
54
|
module.exports = { COMMANDS };
|
|
@@ -3,65 +3,61 @@ const path = require('node:path');
|
|
|
3
3
|
|
|
4
4
|
const { NubosPilotError, atomicWriteFileSync, withFileLock } = require('../../lib/core.cjs');
|
|
5
5
|
const { getPhase } = require('../../lib/roadmap.cjs');
|
|
6
|
-
const
|
|
7
|
-
const { parseVerificationMd } = require('../../lib/verify.cjs');
|
|
6
|
+
const layout = require('../../lib/layout.cjs');
|
|
7
|
+
const { parseVerificationMd, milestoneVerificationPath } = require('../../lib/verify.cjs');
|
|
8
8
|
|
|
9
9
|
const BEGIN_MARKER = '// >>> np:add-tests begin';
|
|
10
10
|
const END_MARKER = '// <<< np:add-tests end';
|
|
11
11
|
|
|
12
|
-
function
|
|
13
|
-
if (raw == null || raw === '' || !/^\d
|
|
12
|
+
function _validateMilestoneArg(raw) {
|
|
13
|
+
if (raw == null || raw === '' || !/^\d+$/.test(String(raw))) {
|
|
14
14
|
throw new NubosPilotError(
|
|
15
15
|
'add-tests-invalid-phase',
|
|
16
|
-
'add-tests requires a
|
|
16
|
+
'add-tests requires a positive integer milestone argument',
|
|
17
17
|
{ value: raw == null ? '' : String(raw) },
|
|
18
18
|
);
|
|
19
19
|
}
|
|
20
|
-
return
|
|
20
|
+
return Number(raw);
|
|
21
21
|
}
|
|
22
22
|
|
|
23
|
-
function _resolveTestTarget(
|
|
24
|
-
const
|
|
25
|
-
const
|
|
26
|
-
const
|
|
27
|
-
const padded = paddedPhase(phaseN);
|
|
23
|
+
function _resolveTestTarget(mNum, cwd) {
|
|
24
|
+
const def = getPhase(mNum, cwd);
|
|
25
|
+
const slug = layout.slugify(def.name || 'milestone');
|
|
26
|
+
const mIdStr = layout.mId(mNum);
|
|
28
27
|
let dir = path.resolve(cwd);
|
|
29
28
|
for (let i = 0; i < 10; i++) {
|
|
30
29
|
if (fs.existsSync(path.join(dir, 'package.json'))) {
|
|
31
30
|
return {
|
|
32
31
|
pkg_root: dir,
|
|
33
|
-
target_path: path.join(dir, 'test', 'uat',
|
|
34
|
-
|
|
32
|
+
target_path: path.join(dir, 'test', 'uat', mIdStr.toLowerCase() + '-' + slug + '.test.cjs'),
|
|
33
|
+
milestone_id: mIdStr, slug,
|
|
35
34
|
};
|
|
36
35
|
}
|
|
37
36
|
const parent = path.dirname(dir);
|
|
38
37
|
if (parent === dir) break;
|
|
39
38
|
dir = parent;
|
|
40
39
|
}
|
|
41
|
-
|
|
42
40
|
return {
|
|
43
41
|
pkg_root: path.resolve(cwd),
|
|
44
|
-
target_path: path.join(path.resolve(cwd), 'test', 'uat',
|
|
45
|
-
|
|
42
|
+
target_path: path.join(path.resolve(cwd), 'test', 'uat', mIdStr.toLowerCase() + '-' + slug + '.test.cjs'),
|
|
43
|
+
milestone_id: mIdStr, slug,
|
|
46
44
|
};
|
|
47
45
|
}
|
|
48
46
|
|
|
49
|
-
function _loadCases(
|
|
50
|
-
const
|
|
51
|
-
|
|
52
|
-
const phase_dir = findPhaseDir(phaseN, cwd);
|
|
53
|
-
if (!phase_dir) {
|
|
47
|
+
function _loadCases(mNum, cwd) {
|
|
48
|
+
const mDir = layout.findMilestoneDir(mNum, cwd);
|
|
49
|
+
if (!mDir) {
|
|
54
50
|
throw new NubosPilotError(
|
|
55
51
|
'add-tests-phase-dir-missing',
|
|
56
|
-
'
|
|
57
|
-
{
|
|
52
|
+
'Milestone directory not found for milestone ' + mNum,
|
|
53
|
+
{ milestone: mNum },
|
|
58
54
|
);
|
|
59
55
|
}
|
|
60
|
-
const vp =
|
|
56
|
+
const vp = milestoneVerificationPath(mNum, cwd);
|
|
61
57
|
if (!fs.existsSync(vp)) {
|
|
62
58
|
throw new NubosPilotError(
|
|
63
59
|
'add-tests-verification-missing',
|
|
64
|
-
'VERIFICATION.md not found — run `/np:verify-work ' +
|
|
60
|
+
'VERIFICATION.md not found — run `/np:verify-work ' + mNum + '` first',
|
|
65
61
|
{ path: vp },
|
|
66
62
|
);
|
|
67
63
|
}
|
|
@@ -75,10 +71,10 @@ function _jsString(s) {
|
|
|
75
71
|
return "'" + String(s).replace(/\\/g, '\\\\').replace(/'/g, "\\'").replace(/\n/g, '\\n') + "'";
|
|
76
72
|
}
|
|
77
73
|
|
|
78
|
-
function _renderBlock(
|
|
74
|
+
function _renderBlock(milestoneId, passes, skips) {
|
|
79
75
|
const date = new Date().toISOString().slice(0, 10);
|
|
80
76
|
const lines = [];
|
|
81
|
-
lines.push(BEGIN_MARKER + ' (
|
|
77
|
+
lines.push(BEGIN_MARKER + ' (' + milestoneId + ', generated ' + date + ')');
|
|
82
78
|
lines.push("const { test } = require('node:test');");
|
|
83
79
|
lines.push("const assert = require('node:assert');");
|
|
84
80
|
lines.push('');
|
|
@@ -111,11 +107,11 @@ function _mergeBlock(existing, block) {
|
|
|
111
107
|
return before + block + after;
|
|
112
108
|
}
|
|
113
109
|
|
|
114
|
-
function _emitTests(
|
|
115
|
-
const { passes, skips } = _loadCases(
|
|
116
|
-
const target = _resolveTestTarget(
|
|
110
|
+
function _emitTests(mNum, cwd) {
|
|
111
|
+
const { passes, skips } = _loadCases(mNum, cwd);
|
|
112
|
+
const target = _resolveTestTarget(mNum, cwd);
|
|
117
113
|
fs.mkdirSync(path.dirname(target.target_path), { recursive: true });
|
|
118
|
-
const block = _renderBlock(target.
|
|
114
|
+
const block = _renderBlock(target.milestone_id, passes, skips);
|
|
119
115
|
return withFileLock(target.target_path, () => {
|
|
120
116
|
let existing = null;
|
|
121
117
|
try { existing = fs.readFileSync(target.target_path, 'utf-8'); } catch { existing = null; }
|
|
@@ -139,12 +135,13 @@ function run(args, ctx) {
|
|
|
139
135
|
|
|
140
136
|
switch (verb) {
|
|
141
137
|
case 'init': {
|
|
142
|
-
const
|
|
143
|
-
const target = _resolveTestTarget(
|
|
144
|
-
const { passes, skips, verification_path } = _loadCases(
|
|
138
|
+
const mNum = _validateMilestoneArg(list[1]);
|
|
139
|
+
const target = _resolveTestTarget(mNum, cwd);
|
|
140
|
+
const { passes, skips, verification_path } = _loadCases(mNum, cwd);
|
|
145
141
|
const payload = {
|
|
146
142
|
_workflow: 'add-tests',
|
|
147
|
-
|
|
143
|
+
milestone: mNum,
|
|
144
|
+
milestone_id: target.milestone_id,
|
|
148
145
|
target_path: target.target_path,
|
|
149
146
|
verification_path,
|
|
150
147
|
pass_cases: passes,
|
|
@@ -154,8 +151,8 @@ function run(args, ctx) {
|
|
|
154
151
|
return payload;
|
|
155
152
|
}
|
|
156
153
|
case 'emit': {
|
|
157
|
-
const
|
|
158
|
-
const result = _emitTests(
|
|
154
|
+
const mNum = _validateMilestoneArg(list[1]);
|
|
155
|
+
const result = _emitTests(mNum, cwd);
|
|
159
156
|
stdout.write(JSON.stringify(result));
|
|
160
157
|
return result;
|
|
161
158
|
}
|
|
@@ -3,30 +3,40 @@ const assert = require('node:assert/strict');
|
|
|
3
3
|
const fs = require('node:fs');
|
|
4
4
|
const path = require('node:path');
|
|
5
5
|
|
|
6
|
-
const { makeSandbox, seedRoadmapYaml,
|
|
6
|
+
const { makeSandbox, seedRoadmapYaml, seedMilestoneDir, cleanupAll } =
|
|
7
7
|
require('../../tests/helpers/fixture.cjs');
|
|
8
8
|
const subcmd = require('./add-tests.cjs');
|
|
9
9
|
|
|
10
10
|
function _roadmap() {
|
|
11
11
|
return {
|
|
12
|
-
schema_version:
|
|
13
|
-
milestones: [
|
|
14
|
-
{
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
12
|
+
schema_version: 2,
|
|
13
|
+
milestones: [
|
|
14
|
+
{
|
|
15
|
+
id: 'M006',
|
|
16
|
+
number: 6,
|
|
17
|
+
name: 'Execution',
|
|
18
|
+
goal: '',
|
|
19
|
+
requirements: [],
|
|
20
|
+
success_criteria: ['a', 'b', 'c'],
|
|
21
|
+
status: 'pending',
|
|
22
|
+
slices: [],
|
|
23
|
+
},
|
|
24
|
+
],
|
|
18
25
|
};
|
|
19
26
|
}
|
|
20
27
|
|
|
21
|
-
function _capture() {
|
|
28
|
+
function _capture() {
|
|
29
|
+
let b = '';
|
|
30
|
+
return { stub: { write: (s) => { b += s; return true; } }, get: () => b };
|
|
31
|
+
}
|
|
22
32
|
|
|
23
33
|
function _seedVerification(sandbox) {
|
|
24
|
-
const
|
|
34
|
+
const mDir = seedMilestoneDir(sandbox, 6, {});
|
|
25
35
|
const v = [
|
|
26
|
-
'#
|
|
36
|
+
'# M006 — Execution — Verification',
|
|
27
37
|
'',
|
|
28
38
|
'**Verified:** 2026-04-15',
|
|
29
|
-
'**
|
|
39
|
+
'**Milestone Status:** deferred',
|
|
30
40
|
'',
|
|
31
41
|
'## Success Criteria',
|
|
32
42
|
'',
|
|
@@ -46,10 +56,9 @@ function _seedVerification(sandbox) {
|
|
|
46
56
|
'- **Evidence:** —',
|
|
47
57
|
'',
|
|
48
58
|
].join('\n');
|
|
49
|
-
fs.writeFileSync(path.join(
|
|
50
|
-
|
|
59
|
+
fs.writeFileSync(path.join(mDir, 'M006-VERIFICATION.md'), v, 'utf-8');
|
|
51
60
|
fs.writeFileSync(path.join(sandbox, 'package.json'), '{"name":"sandbox"}', 'utf-8');
|
|
52
|
-
return
|
|
61
|
+
return mDir;
|
|
53
62
|
}
|
|
54
63
|
|
|
55
64
|
afterEach(cleanupAll);
|
|
@@ -60,40 +69,39 @@ test('AT-1: init emits pass_cases (1) and skip_cases (2) categorized', () => {
|
|
|
60
69
|
_seedVerification(sandbox);
|
|
61
70
|
const cap = _capture();
|
|
62
71
|
const p = subcmd.run(['init', '6'], { cwd: sandbox, stdout: cap.stub });
|
|
72
|
+
assert.equal(p.milestone, 6);
|
|
73
|
+
assert.equal(p.milestone_id, 'M006');
|
|
63
74
|
assert.equal(p.pass_cases.length, 1);
|
|
64
75
|
assert.equal(p.skip_cases.length, 2);
|
|
65
|
-
assert.ok(p.target_path.endsWith('test
|
|
76
|
+
assert.ok(p.target_path.endsWith(path.join('test', 'uat', 'm006-execution.test.cjs')));
|
|
66
77
|
});
|
|
67
78
|
|
|
68
79
|
test('AT-2: emit writes node:test file with begin/end sentinels', () => {
|
|
69
80
|
const sandbox = makeSandbox();
|
|
70
81
|
seedRoadmapYaml(sandbox, _roadmap());
|
|
71
82
|
_seedVerification(sandbox);
|
|
72
|
-
const
|
|
73
|
-
const r = subcmd.run(['emit', '6'], { cwd: sandbox, stdout: cap.stub });
|
|
83
|
+
const r = subcmd.run(['emit', '6'], { cwd: sandbox, stdout: _capture().stub });
|
|
74
84
|
const body = fs.readFileSync(r.target_path, 'utf-8');
|
|
75
85
|
assert.ok(body.includes('// >>> np:add-tests begin'));
|
|
76
86
|
assert.ok(body.includes('// <<< np:add-tests end'));
|
|
77
87
|
assert.ok(body.includes("test('SC-1: First passes'"));
|
|
78
88
|
assert.ok(body.includes("test.skip('SC-2: Second fails'"));
|
|
79
89
|
assert.ok(body.includes("test.skip('SC-3: Deferred'"));
|
|
90
|
+
assert.ok(body.includes('M006'));
|
|
80
91
|
});
|
|
81
92
|
|
|
82
93
|
test('AT-3: sentinel preservation — user edits OUTSIDE block survive regeneration', () => {
|
|
83
94
|
const sandbox = makeSandbox();
|
|
84
95
|
seedRoadmapYaml(sandbox, _roadmap());
|
|
85
96
|
_seedVerification(sandbox);
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
const target = path.join(sandbox, 'test', 'uat', 'phase-06-execution.test.cjs');
|
|
89
|
-
|
|
97
|
+
subcmd.run(['emit', '6'], { cwd: sandbox, stdout: _capture().stub });
|
|
98
|
+
const target = path.join(sandbox, 'test', 'uat', 'm006-execution.test.cjs');
|
|
90
99
|
let body = fs.readFileSync(target, 'utf-8');
|
|
91
100
|
const userTest = "// USER AUTHORED: do not delete\ntest('user: custom case', () => { assert.ok(1); });\n\n";
|
|
92
101
|
body = userTest + body;
|
|
93
102
|
fs.writeFileSync(target, body, 'utf-8');
|
|
94
103
|
|
|
95
|
-
|
|
96
|
-
subcmd.run(['emit', '6'], { cwd: sandbox, stdout: cap2.stub });
|
|
104
|
+
subcmd.run(['emit', '6'], { cwd: sandbox, stdout: _capture().stub });
|
|
97
105
|
const after = fs.readFileSync(target, 'utf-8');
|
|
98
106
|
assert.ok(after.includes('// USER AUTHORED: do not delete'), 'user content lost');
|
|
99
107
|
assert.ok(after.includes("test('user: custom case'"), 'user test lost');
|
|
@@ -103,20 +111,18 @@ test('AT-3: sentinel preservation — user edits OUTSIDE block survive regenerat
|
|
|
103
111
|
test('AT-4: missing VERIFICATION.md → loud error', () => {
|
|
104
112
|
const sandbox = makeSandbox();
|
|
105
113
|
seedRoadmapYaml(sandbox, _roadmap());
|
|
106
|
-
|
|
114
|
+
seedMilestoneDir(sandbox, 6, {});
|
|
107
115
|
fs.writeFileSync(path.join(sandbox, 'package.json'), '{}', 'utf-8');
|
|
108
|
-
const cap = _capture();
|
|
109
116
|
assert.throws(
|
|
110
|
-
() => subcmd.run(['init', '6'], { cwd: sandbox, stdout:
|
|
117
|
+
() => subcmd.run(['init', '6'], { cwd: sandbox, stdout: _capture().stub }),
|
|
111
118
|
(err) => err && err.code === 'add-tests-verification-missing',
|
|
112
119
|
);
|
|
113
120
|
});
|
|
114
121
|
|
|
115
122
|
test('AT-5: unknown verb throws', () => {
|
|
116
123
|
const sandbox = makeSandbox();
|
|
117
|
-
const cap = _capture();
|
|
118
124
|
assert.throws(
|
|
119
|
-
() => subcmd.run(['bogus'], { cwd: sandbox, stdout:
|
|
125
|
+
() => subcmd.run(['bogus'], { cwd: sandbox, stdout: _capture().stub }),
|
|
120
126
|
(err) => err && err.code === 'add-tests-unknown-verb',
|
|
121
127
|
);
|
|
122
128
|
});
|
|
@@ -2,7 +2,7 @@ const fs = require('node:fs');
|
|
|
2
2
|
const path = require('node:path');
|
|
3
3
|
|
|
4
4
|
const { projectStateDir, NubosPilotError } = require('../../lib/core.cjs');
|
|
5
|
-
const {
|
|
5
|
+
const { slugify } = require('../../lib/layout.cjs');
|
|
6
6
|
|
|
7
7
|
const MAX_DESCRIPTION_LENGTH = 500;
|
|
8
8
|
|
|
@@ -34,7 +34,7 @@ function _buildPayload(description, cwd) {
|
|
|
34
34
|
const now = new Date();
|
|
35
35
|
const iso = now.toISOString();
|
|
36
36
|
const date = iso.slice(0, 10);
|
|
37
|
-
const slug =
|
|
37
|
+
const slug = slugify(description);
|
|
38
38
|
if (!slug) {
|
|
39
39
|
throw new NubosPilotError(
|
|
40
40
|
'add-todo-empty-slug',
|
|
@@ -53,29 +53,29 @@ after(() => {
|
|
|
53
53
|
test('CPT-1: checkpoint start writes file and updates STATE.current_task', () => {
|
|
54
54
|
const root = makeRoot();
|
|
55
55
|
const cap = _capture();
|
|
56
|
-
subcmd.run(['start', '
|
|
57
|
-
const cp = JSON.parse(fs.readFileSync(path.join(root, '.nubos-pilot', 'checkpoints', '
|
|
58
|
-
assert.equal(cp.task_id, '
|
|
56
|
+
subcmd.run(['start', 'M006-S001-T0001', '--phase', '6', '--plan', '06-01', '--wave', '1'], { cwd: root, stdout: cap.stub });
|
|
57
|
+
const cp = JSON.parse(fs.readFileSync(path.join(root, '.nubos-pilot', 'checkpoints', 'M006-S001-T0001.json'), 'utf-8'));
|
|
58
|
+
assert.equal(cp.task_id, 'M006-S001-T0001');
|
|
59
59
|
assert.equal(cp.status, 'in-progress');
|
|
60
60
|
const state = fs.readFileSync(path.join(root, '.nubos-pilot', 'STATE.md'), 'utf-8');
|
|
61
|
-
assert.ok(state.includes('current_task:
|
|
61
|
+
assert.ok(state.includes('current_task: M006-S001-T0001'), state);
|
|
62
62
|
});
|
|
63
63
|
|
|
64
64
|
test('CPT-2: checkpoint transition updates status', () => {
|
|
65
65
|
const root = makeRoot();
|
|
66
66
|
const cap = _capture();
|
|
67
|
-
subcmd.run(['start', '
|
|
68
|
-
subcmd.run(['transition', '
|
|
69
|
-
const cp = JSON.parse(fs.readFileSync(path.join(root, '.nubos-pilot', 'checkpoints', '
|
|
67
|
+
subcmd.run(['start', 'M006-S001-T0002', '--phase', '6', '--plan', '06-01', '--wave', '1'], { cwd: root, stdout: cap.stub });
|
|
68
|
+
subcmd.run(['transition', 'M006-S001-T0002', 'verifying'], { cwd: root, stdout: cap.stub });
|
|
69
|
+
const cp = JSON.parse(fs.readFileSync(path.join(root, '.nubos-pilot', 'checkpoints', 'M006-S001-T0002.json'), 'utf-8'));
|
|
70
70
|
assert.equal(cp.status, 'verifying');
|
|
71
71
|
});
|
|
72
72
|
|
|
73
73
|
test('CPT-3: checkpoint transition rejects unknown status', () => {
|
|
74
74
|
const root = makeRoot();
|
|
75
75
|
const cap = _capture();
|
|
76
|
-
subcmd.run(['start', '
|
|
76
|
+
subcmd.run(['start', 'M006-S001-T0003', '--phase', '6', '--plan', '06-01', '--wave', '1'], { cwd: root, stdout: cap.stub });
|
|
77
77
|
assert.throws(
|
|
78
|
-
() => subcmd.run(['transition', '
|
|
78
|
+
() => subcmd.run(['transition', 'M006-S001-T0003', 'weird'], { cwd: root, stdout: cap.stub }),
|
|
79
79
|
(err) => err && err.code === 'checkpoint-invalid-status',
|
|
80
80
|
);
|
|
81
81
|
});
|
|
@@ -83,28 +83,28 @@ test('CPT-3: checkpoint transition rejects unknown status', () => {
|
|
|
83
83
|
test('CPT-4: checkpoint touch appends to files_touched', () => {
|
|
84
84
|
const root = makeRoot();
|
|
85
85
|
const cap = _capture();
|
|
86
|
-
subcmd.run(['start', '
|
|
87
|
-
subcmd.run(['touch', '
|
|
88
|
-
subcmd.run(['touch', '
|
|
89
|
-
const cp = JSON.parse(fs.readFileSync(path.join(root, '.nubos-pilot', 'checkpoints', '
|
|
86
|
+
subcmd.run(['start', 'M006-S001-T0004', '--phase', '6', '--plan', '06-01', '--wave', '1'], { cwd: root, stdout: cap.stub });
|
|
87
|
+
subcmd.run(['touch', 'M006-S001-T0004', 'src/a.ts'], { cwd: root, stdout: cap.stub });
|
|
88
|
+
subcmd.run(['touch', 'M006-S001-T0004', 'src/b.ts'], { cwd: root, stdout: cap.stub });
|
|
89
|
+
const cp = JSON.parse(fs.readFileSync(path.join(root, '.nubos-pilot', 'checkpoints', 'M006-S001-T0004.json'), 'utf-8'));
|
|
90
90
|
assert.deepEqual(cp.files_touched, ['src/a.ts', 'src/b.ts']);
|
|
91
91
|
});
|
|
92
92
|
|
|
93
93
|
test('CPT-5: checkpoint show emits JSON', () => {
|
|
94
94
|
const root = makeRoot();
|
|
95
95
|
const cap1 = _capture();
|
|
96
|
-
subcmd.run(['start', '
|
|
96
|
+
subcmd.run(['start', 'M006-S001-T0005', '--phase', '6', '--plan', '06-01', '--wave', '1'], { cwd: root, stdout: cap1.stub });
|
|
97
97
|
const cap2 = _capture();
|
|
98
|
-
subcmd.run(['show', '
|
|
98
|
+
subcmd.run(['show', 'M006-S001-T0005'], { cwd: root, stdout: cap2.stub });
|
|
99
99
|
const json = JSON.parse(cap2.get());
|
|
100
|
-
assert.equal(json.task_id, '
|
|
100
|
+
assert.equal(json.task_id, 'M006-S001-T0005');
|
|
101
101
|
});
|
|
102
102
|
|
|
103
103
|
test('CPT-6: unknown verb throws', () => {
|
|
104
104
|
const root = makeRoot();
|
|
105
105
|
const cap = _capture();
|
|
106
106
|
assert.throws(
|
|
107
|
-
() => subcmd.run(['bogus', '
|
|
107
|
+
() => subcmd.run(['bogus', 'M006-S001-T0001'], { cwd: root, stdout: cap.stub }),
|
|
108
108
|
(err) => err && err.code === 'checkpoint-unknown-verb',
|
|
109
109
|
);
|
|
110
110
|
});
|