nubos-pilot 0.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.
Files changed (273) hide show
  1. package/agents/np-ai-researcher.md +140 -0
  2. package/agents/np-code-fixer.md +363 -0
  3. package/agents/np-code-reviewer.md +351 -0
  4. package/agents/np-domain-researcher.md +136 -0
  5. package/agents/np-eval-auditor.md +167 -0
  6. package/agents/np-eval-planner.md +153 -0
  7. package/agents/np-executor.md +72 -0
  8. package/agents/np-framework-selector.md +171 -0
  9. package/agents/np-nyquist-auditor.md +185 -0
  10. package/agents/np-plan-checker.md +165 -0
  11. package/agents/np-planner.md +199 -0
  12. package/agents/np-researcher.md +150 -0
  13. package/agents/np-security-auditor.md +206 -0
  14. package/agents/np-ui-auditor.md +369 -0
  15. package/agents/np-ui-checker.md +192 -0
  16. package/agents/np-ui-researcher.md +324 -0
  17. package/agents/np-verifier.md +79 -0
  18. package/bin/check-coverage.cjs +40 -0
  19. package/bin/check-workflows.cjs +171 -0
  20. package/bin/check-workflows.test.cjs +208 -0
  21. package/bin/install.js +500 -0
  22. package/bin/np-tools/_commands.cjs +70 -0
  23. package/bin/np-tools/add-tests.cjs +171 -0
  24. package/bin/np-tools/add-tests.test.cjs +122 -0
  25. package/bin/np-tools/add-todo.cjs +108 -0
  26. package/bin/np-tools/add-todo.test.cjs +112 -0
  27. package/bin/np-tools/agent-skills.cjs +14 -0
  28. package/bin/np-tools/agent-skills.test.cjs +42 -0
  29. package/bin/np-tools/ai-integration-phase.cjs +109 -0
  30. package/bin/np-tools/ai-integration-phase.test.cjs +123 -0
  31. package/bin/np-tools/askuser.cjs +53 -0
  32. package/bin/np-tools/askuser.test.cjs +49 -0
  33. package/bin/np-tools/autonomous.cjs +69 -0
  34. package/bin/np-tools/autonomous.test.cjs +74 -0
  35. package/bin/np-tools/checkpoint.cjs +101 -0
  36. package/bin/np-tools/checkpoint.test.cjs +119 -0
  37. package/bin/np-tools/code-review.cjs +133 -0
  38. package/bin/np-tools/code-review.test.cjs +96 -0
  39. package/bin/np-tools/commit-task.cjs +120 -0
  40. package/bin/np-tools/commit-task.test.cjs +160 -0
  41. package/bin/np-tools/commit.cjs +103 -0
  42. package/bin/np-tools/commit.test.cjs +93 -0
  43. package/bin/np-tools/config.cjs +101 -0
  44. package/bin/np-tools/config.test.cjs +71 -0
  45. package/bin/np-tools/discuss-phase-power.cjs +265 -0
  46. package/bin/np-tools/discuss-phase-power.test.cjs +242 -0
  47. package/bin/np-tools/discuss-phase.cjs +132 -0
  48. package/bin/np-tools/discuss-phase.test.cjs +148 -0
  49. package/bin/np-tools/dispatch.cjs +116 -0
  50. package/bin/np-tools/doctor.cjs +242 -0
  51. package/bin/np-tools/eval-review.cjs +116 -0
  52. package/bin/np-tools/eval-review.test.cjs +123 -0
  53. package/bin/np-tools/execute-phase.cjs +182 -0
  54. package/bin/np-tools/execute-phase.test.cjs +116 -0
  55. package/bin/np-tools/execute-plan.cjs +124 -0
  56. package/bin/np-tools/execute-plan.test.cjs +82 -0
  57. package/bin/np-tools/help.cjs +28 -0
  58. package/bin/np-tools/help.test.cjs +29 -0
  59. package/bin/np-tools/init-dispatch.test.cjs +91 -0
  60. package/bin/np-tools/metrics.cjs +97 -0
  61. package/bin/np-tools/metrics.test.cjs +188 -0
  62. package/bin/np-tools/new-milestone.cjs +288 -0
  63. package/bin/np-tools/new-milestone.test.cjs +166 -0
  64. package/bin/np-tools/new-project.cjs +284 -0
  65. package/bin/np-tools/new-project.test.cjs +165 -0
  66. package/bin/np-tools/next.cjs +7 -0
  67. package/bin/np-tools/next.test.cjs +30 -0
  68. package/bin/np-tools/park.cjs +48 -0
  69. package/bin/np-tools/park.test.cjs +50 -0
  70. package/bin/np-tools/pause-work.cjs +24 -0
  71. package/bin/np-tools/pause-work.test.cjs +74 -0
  72. package/bin/np-tools/phase.cjs +71 -0
  73. package/bin/np-tools/phase.test.cjs +81 -0
  74. package/bin/np-tools/plan-diff.cjs +57 -0
  75. package/bin/np-tools/plan-diff.test.cjs +134 -0
  76. package/bin/np-tools/plan-milestone-gaps.cjs +115 -0
  77. package/bin/np-tools/plan-milestone-gaps.test.cjs +122 -0
  78. package/bin/np-tools/plan-phase.cjs +350 -0
  79. package/bin/np-tools/plan-phase.test.cjs +263 -0
  80. package/bin/np-tools/progress.cjs +7 -0
  81. package/bin/np-tools/progress.test.cjs +44 -0
  82. package/bin/np-tools/queue.cjs +213 -0
  83. package/bin/np-tools/research-phase.cjs +144 -0
  84. package/bin/np-tools/research-phase.test.cjs +154 -0
  85. package/bin/np-tools/reset-slice.cjs +17 -0
  86. package/bin/np-tools/reset-slice.test.cjs +96 -0
  87. package/bin/np-tools/resolve-model.cjs +110 -0
  88. package/bin/np-tools/resolve-model.test.cjs +200 -0
  89. package/bin/np-tools/resume-work.cjs +76 -0
  90. package/bin/np-tools/resume-work.test.cjs +91 -0
  91. package/bin/np-tools/skip.cjs +48 -0
  92. package/bin/np-tools/skip.test.cjs +66 -0
  93. package/bin/np-tools/slug.cjs +34 -0
  94. package/bin/np-tools/slug.test.cjs +46 -0
  95. package/bin/np-tools/state.cjs +16 -0
  96. package/bin/np-tools/state.test.cjs +40 -0
  97. package/bin/np-tools/stats.cjs +151 -0
  98. package/bin/np-tools/stats.test.cjs +118 -0
  99. package/bin/np-tools/triage.cjs +128 -0
  100. package/bin/np-tools/ui-phase.cjs +108 -0
  101. package/bin/np-tools/ui-phase.test.cjs +121 -0
  102. package/bin/np-tools/ui-review.cjs +108 -0
  103. package/bin/np-tools/ui-review.test.cjs +120 -0
  104. package/bin/np-tools/undo-task.cjs +31 -0
  105. package/bin/np-tools/undo-task.test.cjs +117 -0
  106. package/bin/np-tools/undo.cjs +43 -0
  107. package/bin/np-tools/undo.test.cjs +120 -0
  108. package/bin/np-tools/unpark.cjs +48 -0
  109. package/bin/np-tools/unpark.test.cjs +50 -0
  110. package/bin/np-tools/verify-work.cjs +186 -0
  111. package/bin/np-tools/verify-work.test.cjs +97 -0
  112. package/docs/adr/0001-no-daemon-invariant.md +82 -0
  113. package/docs/adr/0002-zero-runtime-dependencies.md +90 -0
  114. package/docs/adr/0003-max-six-unit-types.md +85 -0
  115. package/docs/adr/0004-atomic-commit-per-unit.md +102 -0
  116. package/docs/adr/0005-three-orthogonal-file-trees.md +98 -0
  117. package/docs/adr/0006-yaml-dependency-amendment.md +60 -0
  118. package/docs/adr/README.md +27 -0
  119. package/docs/agent-frontmatter-schema.md +84 -0
  120. package/docs/phase-artifact-schemas.md +292 -0
  121. package/docs/phase-directory-layout.md +82 -0
  122. package/lib/__tests__/README.md +1 -0
  123. package/lib/agents.cjs +98 -0
  124. package/lib/agents.test.cjs +286 -0
  125. package/lib/askuser.cjs +36 -0
  126. package/lib/askuser.test.cjs +310 -0
  127. package/lib/checkpoint.cjs +135 -0
  128. package/lib/checkpoint.test.cjs +184 -0
  129. package/lib/core.cjs +165 -0
  130. package/lib/core.test.cjs +405 -0
  131. package/lib/fixtures/README.md +1 -0
  132. package/lib/fixtures/phase-tree/README.md +1 -0
  133. package/lib/fixtures/plans/cycle/PLAN.md +16 -0
  134. package/lib/fixtures/plans/cycle/tasks/T-01.md +20 -0
  135. package/lib/fixtures/plans/cycle/tasks/T-02.md +20 -0
  136. package/lib/fixtures/plans/cycle/tasks/T-03.md +20 -0
  137. package/lib/fixtures/plans/linear/PLAN.md +16 -0
  138. package/lib/fixtures/plans/linear/tasks/T-01.md +20 -0
  139. package/lib/fixtures/plans/linear/tasks/T-02.md +20 -0
  140. package/lib/fixtures/plans/linear/tasks/T-03.md +20 -0
  141. package/lib/fixtures/plans/parallel/PLAN.md +16 -0
  142. package/lib/fixtures/plans/parallel/tasks/T-01.md +20 -0
  143. package/lib/fixtures/plans/parallel/tasks/T-02.md +20 -0
  144. package/lib/fixtures/plans/parallel/tasks/T-03.md +20 -0
  145. package/lib/fixtures/plans/wave-conflict/PLAN.md +16 -0
  146. package/lib/fixtures/plans/wave-conflict/tasks/T-01.md +20 -0
  147. package/lib/fixtures/plans/wave-conflict/tasks/T-02.md +20 -0
  148. package/lib/fixtures/roadmap/ROADMAP-malformed.md +3 -0
  149. package/lib/fixtures/roadmap/ROADMAP-minimal.md +51 -0
  150. package/lib/fixtures/roadmap/roadmap-malformed.yaml +7 -0
  151. package/lib/fixtures/roadmap/roadmap-minimal.yaml +40 -0
  152. package/lib/fixtures/roadmap/roadmap-ten-phases.yaml +101 -0
  153. package/lib/fixtures/templates/phase-context.md +6 -0
  154. package/lib/fixtures/templates/plan-skeleton.md +6 -0
  155. package/lib/frontmatter.cjs +251 -0
  156. package/lib/frontmatter.test.cjs +177 -0
  157. package/lib/gaps.cjs +197 -0
  158. package/lib/gaps.test.cjs +200 -0
  159. package/lib/git.cjs +207 -0
  160. package/lib/git.test.cjs +305 -0
  161. package/lib/install/agents-md.cjs +77 -0
  162. package/lib/install/backup.cjs +70 -0
  163. package/lib/install/codex-toml.cjs +440 -0
  164. package/lib/install/managed-block.cjs +30 -0
  165. package/lib/install/manifest.cjs +148 -0
  166. package/lib/install/mcp-writer.cjs +127 -0
  167. package/lib/install/runtime-detect.cjs +44 -0
  168. package/lib/install/staging.cjs +149 -0
  169. package/lib/metrics-aggregate.cjs +229 -0
  170. package/lib/metrics-aggregate.test.cjs +192 -0
  171. package/lib/metrics.cjs +120 -0
  172. package/lib/metrics.test.cjs +182 -0
  173. package/lib/model-aliases.regression.test.cjs +16 -0
  174. package/lib/model-profiles.cjs +42 -0
  175. package/lib/model-profiles.test.cjs +61 -0
  176. package/lib/next.cjs +236 -0
  177. package/lib/next.test.cjs +194 -0
  178. package/lib/phase.cjs +95 -0
  179. package/lib/phase.test.cjs +189 -0
  180. package/lib/plan-checker-contract.test.cjs +72 -0
  181. package/lib/plan-diff.cjs +173 -0
  182. package/lib/plan-diff.test.cjs +217 -0
  183. package/lib/plan.cjs +85 -0
  184. package/lib/plan.test.cjs +263 -0
  185. package/lib/progress.cjs +95 -0
  186. package/lib/progress.test.cjs +116 -0
  187. package/lib/researcher-contract.test.cjs +61 -0
  188. package/lib/roadmap-render.cjs +206 -0
  189. package/lib/roadmap-render.test.cjs +121 -0
  190. package/lib/roadmap.cjs +416 -0
  191. package/lib/roadmap.test.cjs +371 -0
  192. package/lib/runtime/_contract.test.cjs +61 -0
  193. package/lib/runtime/_readline.cjs +119 -0
  194. package/lib/runtime/_readline.test.cjs +126 -0
  195. package/lib/runtime/claude.cjs +48 -0
  196. package/lib/runtime/claude.test.cjs +101 -0
  197. package/lib/runtime/codex.cjs +35 -0
  198. package/lib/runtime/codex.test.cjs +114 -0
  199. package/lib/runtime/gemini.cjs +35 -0
  200. package/lib/runtime/gemini.test.cjs +109 -0
  201. package/lib/runtime/index.cjs +49 -0
  202. package/lib/runtime/index.test.cjs +181 -0
  203. package/lib/runtime/opencode.cjs +35 -0
  204. package/lib/runtime/opencode.test.cjs +124 -0
  205. package/lib/state.cjs +205 -0
  206. package/lib/state.test.cjs +264 -0
  207. package/lib/surface-audit.test.cjs +46 -0
  208. package/lib/tasks.cjs +327 -0
  209. package/lib/tasks.test.cjs +389 -0
  210. package/lib/template.cjs +66 -0
  211. package/lib/template.test.cjs +159 -0
  212. package/lib/undo.cjs +179 -0
  213. package/lib/undo.test.cjs +261 -0
  214. package/lib/verify.cjs +116 -0
  215. package/lib/verify.test.cjs +187 -0
  216. package/np-tools.cjs +303 -0
  217. package/package.json +39 -0
  218. package/templates/AI-SPEC.md +90 -0
  219. package/templates/CONTEXT.md +32 -0
  220. package/templates/PLAN.md +69 -0
  221. package/templates/PROJECT.md +60 -0
  222. package/templates/REQUIREMENTS.md +38 -0
  223. package/templates/SECURITY.md +61 -0
  224. package/templates/UI-SPEC.md +64 -0
  225. package/templates/VALIDATION.md +76 -0
  226. package/templates/claude/payload/README.md +11 -0
  227. package/templates/opencode/opencode.json +6 -0
  228. package/templates/opencode/payload/AGENTS.md +9 -0
  229. package/workflows/add-backlog.md +212 -0
  230. package/workflows/add-tests.md +69 -0
  231. package/workflows/add-todo.md +222 -0
  232. package/workflows/ai-integration-phase.md +230 -0
  233. package/workflows/autonomous.md +94 -0
  234. package/workflows/cleanup.md +325 -0
  235. package/workflows/code-review-fix.md +435 -0
  236. package/workflows/code-review.md +447 -0
  237. package/workflows/discuss-phase-assumptions.md +269 -0
  238. package/workflows/discuss-phase-power.md +139 -0
  239. package/workflows/discuss-phase.md +386 -0
  240. package/workflows/dispatch.md +9 -0
  241. package/workflows/doctor.md +10 -0
  242. package/workflows/eval-review.md +243 -0
  243. package/workflows/execute-phase.md +142 -0
  244. package/workflows/execute-plan.md +82 -0
  245. package/workflows/help.md +8 -0
  246. package/workflows/new-milestone.md +166 -0
  247. package/workflows/new-project.md +213 -0
  248. package/workflows/next.md +8 -0
  249. package/workflows/note.md +244 -0
  250. package/workflows/park.md +29 -0
  251. package/workflows/pause-work.md +34 -0
  252. package/workflows/plan-milestone-gaps.md +233 -0
  253. package/workflows/plan-phase.md +351 -0
  254. package/workflows/progress.md +8 -0
  255. package/workflows/queue.md +9 -0
  256. package/workflows/research-phase.md +327 -0
  257. package/workflows/reset-slice.md +39 -0
  258. package/workflows/resume-work.md +79 -0
  259. package/workflows/review.md +489 -0
  260. package/workflows/secure-phase.md +209 -0
  261. package/workflows/session-report.md +243 -0
  262. package/workflows/skip.md +29 -0
  263. package/workflows/state.md +7 -0
  264. package/workflows/stats.md +170 -0
  265. package/workflows/thread.md +214 -0
  266. package/workflows/triage.md +9 -0
  267. package/workflows/ui-phase.md +246 -0
  268. package/workflows/ui-review.md +222 -0
  269. package/workflows/undo-task.md +42 -0
  270. package/workflows/undo.md +55 -0
  271. package/workflows/unpark.md +29 -0
  272. package/workflows/validate-phase.md +231 -0
  273. package/workflows/verify-work.md +83 -0
@@ -0,0 +1,116 @@
1
+ const { test, afterEach } = require('node:test');
2
+ const assert = require('node:assert/strict');
3
+ const fs = require('node:fs');
4
+ const path = require('node:path');
5
+
6
+ const { makeSandbox, seedRoadmapYaml, seedPhaseDir, cleanupAll } =
7
+ require('../../tests/helpers/fixture.cjs');
8
+ const subcmd = require('./execute-phase.cjs');
9
+
10
+ function _baseRoadmap() {
11
+ return {
12
+ schema_version: 1,
13
+ milestones: [{
14
+ id: 'v1.0', name: 'first', phases: [
15
+ { number: 6, name: 'Execution', slug: 'execution', goal: 'ship', depends_on: [],
16
+ requirements: ['EXEC-01'], success_criteria: ['tasks commit'], status: 'planned', plans: [] },
17
+ ],
18
+ }],
19
+ };
20
+ }
21
+
22
+ function _capture() {
23
+ let buf = '';
24
+ const stub = { write: (s) => { buf += s; return true; } };
25
+ return { stub, get: () => buf };
26
+ }
27
+
28
+ function _seedPhaseWithPlan(sandbox) {
29
+ const phaseDir = seedPhaseDir(sandbox, 6, 'execution', {
30
+ '06-01-PLAN.md': '---\nphase: 6\nplan: 01\n---\n# plan\n',
31
+ });
32
+ const tasksDir = path.join(phaseDir, 'tasks');
33
+ fs.mkdirSync(tasksDir, { recursive: true });
34
+ const taskMd = [
35
+ '---',
36
+ 'id: 06-01-T01',
37
+ 'phase: 6',
38
+ 'plan: "06-01"',
39
+ 'type: auto',
40
+ 'status: pending',
41
+ 'tier: sonnet',
42
+ 'owner: np-executor',
43
+ 'wave: 1',
44
+ 'depends_on: []',
45
+ 'files_modified:',
46
+ ' - src/a.ts',
47
+ 'autonomous: true',
48
+ 'must_haves:',
49
+ ' truths: []',
50
+ '---',
51
+ '',
52
+ '# Task',
53
+ ].join('\n');
54
+ fs.writeFileSync(path.join(tasksDir, '06-01-T01.md'), taskMd, 'utf-8');
55
+ return phaseDir;
56
+ }
57
+
58
+ afterEach(cleanupAll);
59
+
60
+ test('EP-1: init emits payload with waves + executor_tier=sonnet + phase_dir', () => {
61
+ const sandbox = makeSandbox();
62
+ seedRoadmapYaml(sandbox, _baseRoadmap());
63
+ const phaseDir = _seedPhaseWithPlan(sandbox);
64
+ const cap = _capture();
65
+ subcmd.run(['init', '6'], { cwd: sandbox, stdout: cap.stub });
66
+ const raw = cap.get().trim();
67
+ assert.ok(!raw.startsWith('@file:'));
68
+ const p = JSON.parse(raw);
69
+ assert.equal(p._workflow, 'execute-phase');
70
+ assert.equal(p.phase, '6');
71
+ assert.equal(p.padded, '06');
72
+ assert.equal(p.phase_dir, phaseDir);
73
+ assert.equal(p.executor_tier, 'sonnet');
74
+ assert.ok(Array.isArray(p.plans));
75
+ assert.ok(p.plans.length >= 1);
76
+ assert.ok(Array.isArray(p.plans[0].waves));
77
+ });
78
+
79
+ test('EP-2: init without phase arg throws validation', () => {
80
+ const sandbox = makeSandbox();
81
+ const cap = _capture();
82
+ assert.throws(
83
+ () => subcmd.run(['init'], { cwd: sandbox, stdout: cap.stub }),
84
+ (err) => err && err.code === 'execute-phase-invalid-phase-arg',
85
+ );
86
+ });
87
+
88
+ test('EP-3: init rejects non-numeric phase', () => {
89
+ const sandbox = makeSandbox();
90
+ const cap = _capture();
91
+ assert.throws(
92
+ () => subcmd.run(['init', 'abc'], { cwd: sandbox, stdout: cap.stub }),
93
+ (err) => err && err.code === 'execute-phase-invalid-phase-arg',
94
+ );
95
+ });
96
+
97
+ test('EP-4: execute-task verb emits task payload with files_modified', () => {
98
+ const sandbox = makeSandbox();
99
+ seedRoadmapYaml(sandbox, _baseRoadmap());
100
+ _seedPhaseWithPlan(sandbox);
101
+ const cap = _capture();
102
+ subcmd.run(['execute-task', '6', '06-01-T01'], { cwd: sandbox, stdout: cap.stub });
103
+ const p = JSON.parse(cap.get().trim());
104
+ assert.equal(p.task_id, '06-01-T01');
105
+ assert.deepEqual(p.files_modified, ['src/a.ts']);
106
+ assert.equal(p.executor_tier, 'sonnet');
107
+ });
108
+
109
+ test('EP-5: unknown verb throws', () => {
110
+ const sandbox = makeSandbox();
111
+ const cap = _capture();
112
+ assert.throws(
113
+ () => subcmd.run(['bogus'], { cwd: sandbox, stdout: cap.stub }),
114
+ (err) => err && err.code === 'execute-phase-unknown-verb',
115
+ );
116
+ });
@@ -0,0 +1,124 @@
1
+ const fs = require('node:fs');
2
+ const path = require('node:path');
3
+ const os = require('node:os');
4
+ const crypto = require('node:crypto');
5
+
6
+ const {
7
+ NubosPilotError,
8
+ projectStateDir,
9
+ } = require('../../lib/core.cjs');
10
+ const { getPhase } = require('../../lib/roadmap.cjs');
11
+ const { paddedPhase, phaseSlug, findPhaseDir } = require('../../lib/phase.cjs');
12
+ const { listPlans, parsePlan } = require('../../lib/plan.cjs');
13
+ const { loadTaskGraph } = require('../../lib/tasks.cjs');
14
+ const { getAgentSkills } = require('../../lib/agents.cjs');
15
+
16
+ const INLINE_THRESHOLD_BYTES = 16 * 1024;
17
+ const PLAN_ID_RE = /^\d{2}-\d{2}$/;
18
+
19
+ function _safeSkills(name, cwd) {
20
+ try { return getAgentSkills(name, cwd); } catch { return []; }
21
+ }
22
+
23
+ function _emit(payload, stdout, cwd) {
24
+ const json = JSON.stringify(payload, null, 2);
25
+ if (Buffer.byteLength(json, 'utf-8') <= INLINE_THRESHOLD_BYTES) {
26
+ stdout.write(json);
27
+ return;
28
+ }
29
+ let tmpDir;
30
+ try {
31
+ tmpDir = path.join(projectStateDir(cwd), '.tmp');
32
+ fs.mkdirSync(tmpDir, { recursive: true });
33
+ } catch { tmpDir = os.tmpdir(); }
34
+ const suffix = process.pid + '-' + crypto.randomBytes(4).toString('hex');
35
+ const tmpPath = path.join(tmpDir, 'init-execute-plan-' + suffix + '.json');
36
+ fs.writeFileSync(tmpPath, json, 'utf-8');
37
+ stdout.write('@file:' + tmpPath);
38
+ }
39
+
40
+ function _resolvePhaseDir(n, cwd, slug) {
41
+ const hit = findPhaseDir(n, cwd);
42
+ if (hit) return hit;
43
+ const padded = paddedPhase(n);
44
+ let stateDir;
45
+ try { stateDir = projectStateDir(cwd); } catch { stateDir = path.join(path.resolve(cwd), '.nubos-pilot'); }
46
+ return path.join(stateDir, 'phases', padded + '-' + slug);
47
+ }
48
+
49
+ function _initPayload(planId, cwd) {
50
+ const [phaseStr, planStr] = planId.split('-');
51
+ const phaseNum = Number(phaseStr);
52
+ const phase = getPhase(phaseNum, cwd);
53
+ const padded = paddedPhase(phaseNum);
54
+ const slug = phase.slug || phaseSlug(phase.name);
55
+ const phase_dir = _resolvePhaseDir(phaseNum, cwd, slug);
56
+ const all = listPlans(phase_dir);
57
+ const target = all.find((p) => path.basename(p) === `${planId}-PLAN.md`);
58
+ if (!target) {
59
+ throw new NubosPilotError(
60
+ 'execute-plan-not-found',
61
+ 'Plan ' + planId + ' not found in phase ' + phaseNum,
62
+ { planId, phase: phaseNum, searched: all },
63
+ );
64
+ }
65
+ const parsed = parsePlan(target);
66
+ const planDir = path.dirname(target);
67
+ const tg = loadTaskGraph(planDir);
68
+ return {
69
+ _workflow: 'execute-plan',
70
+ plan_id: planId,
71
+ phase: phaseStr,
72
+ padded,
73
+ plan_number: planStr,
74
+ phase_dir,
75
+ phase_name: phase.name,
76
+ plan_path: target,
77
+ plan_frontmatter: parsed.frontmatter,
78
+ tasks_dir: path.join(planDir, 'tasks'),
79
+ task_count: tg.tasks.length,
80
+ waves: tg.waves,
81
+ warnings: tg.warnings,
82
+ executor_tier: 'sonnet',
83
+ agent_skills: { executor: _safeSkills('np-executor', cwd) },
84
+ };
85
+ }
86
+
87
+ function run(args, ctx) {
88
+ const context = ctx || {};
89
+ const cwd = context.cwd || process.cwd();
90
+ const stdout = context.stdout || process.stdout;
91
+ const list = Array.isArray(args) ? args : [];
92
+ const verb = list[0];
93
+
94
+ switch (verb) {
95
+ case 'init': {
96
+ const planId = list[1];
97
+ if (!planId) {
98
+ throw new NubosPilotError(
99
+ 'execute-plan-missing-id',
100
+ 'execute-plan requires a plan id (e.g. 06-01)',
101
+ {},
102
+ );
103
+ }
104
+ if (!PLAN_ID_RE.test(planId)) {
105
+ throw new NubosPilotError(
106
+ 'execute-plan-invalid-id',
107
+ 'Invalid plan id format: ' + planId + ' (expected NN-NN)',
108
+ { planId },
109
+ );
110
+ }
111
+ const payload = _initPayload(planId, cwd);
112
+ _emit(payload, stdout, cwd);
113
+ return payload;
114
+ }
115
+ default:
116
+ throw new NubosPilotError(
117
+ 'execute-plan-unknown-verb',
118
+ 'execute-plan: unknown verb: ' + String(verb),
119
+ { verb },
120
+ );
121
+ }
122
+ }
123
+
124
+ module.exports = { run, INLINE_THRESHOLD_BYTES };
@@ -0,0 +1,82 @@
1
+ const { test, afterEach } = require('node:test');
2
+ const assert = require('node:assert/strict');
3
+ const fs = require('node:fs');
4
+ const path = require('node:path');
5
+
6
+ const { makeSandbox, seedRoadmapYaml, seedPhaseDir, cleanupAll } =
7
+ require('../../tests/helpers/fixture.cjs');
8
+ const subcmd = require('./execute-plan.cjs');
9
+
10
+ function _baseRoadmap() {
11
+ return {
12
+ schema_version: 1,
13
+ milestones: [{
14
+ id: 'v1.0', name: 'm1', phases: [
15
+ { number: 6, name: 'Execution', slug: 'execution', goal: 'ship', depends_on: [],
16
+ requirements: [], success_criteria: [], status: 'planned', plans: [] },
17
+ ],
18
+ }],
19
+ };
20
+ }
21
+
22
+ function _capture() { let b = ''; return { stub: { write: (s) => { b += s; return true; } }, get: () => b }; }
23
+
24
+ function _seedTwoPlans(sandbox) {
25
+ const phaseDir = seedPhaseDir(sandbox, 6, 'execution', {
26
+ '06-01-PLAN.md': '---\nphase: 6\nplan: 01\n---\n# p1\n',
27
+ '06-02-PLAN.md': '---\nphase: 6\nplan: 02\n---\n# p2\n',
28
+ });
29
+ for (const id of ['06-01', '06-02']) {
30
+ const tasksDir = path.join(phaseDir, 'tasks');
31
+ }
32
+
33
+
34
+
35
+ return phaseDir;
36
+ }
37
+
38
+ afterEach(cleanupAll);
39
+
40
+ test('XP-1: init <plan-id> emits single-plan payload', () => {
41
+ const sandbox = makeSandbox();
42
+ seedRoadmapYaml(sandbox, _baseRoadmap());
43
+ _seedTwoPlans(sandbox);
44
+ const cap = _capture();
45
+ subcmd.run(['init', '06-01'], { cwd: sandbox, stdout: cap.stub });
46
+ const p = JSON.parse(cap.get().trim());
47
+ assert.equal(p._workflow, 'execute-plan');
48
+ assert.equal(p.plan_id, '06-01');
49
+ assert.equal(p.phase, '06');
50
+ assert.equal(p.padded, '06');
51
+ assert.ok(p.plan_path.endsWith('06-01-PLAN.md'));
52
+ assert.equal(p.executor_tier, 'sonnet');
53
+ });
54
+
55
+ test('XP-2: invalid plan-id format rejected', () => {
56
+ const sandbox = makeSandbox();
57
+ const cap = _capture();
58
+ assert.throws(
59
+ () => subcmd.run(['init', 'bogus'], { cwd: sandbox, stdout: cap.stub }),
60
+ (err) => err && err.code === 'execute-plan-invalid-id',
61
+ );
62
+ });
63
+
64
+ test('XP-3: unknown plan throws', () => {
65
+ const sandbox = makeSandbox();
66
+ seedRoadmapYaml(sandbox, _baseRoadmap());
67
+ _seedTwoPlans(sandbox);
68
+ const cap = _capture();
69
+ assert.throws(
70
+ () => subcmd.run(['init', '06-99'], { cwd: sandbox, stdout: cap.stub }),
71
+ (err) => err && err.code === 'execute-plan-not-found',
72
+ );
73
+ });
74
+
75
+ test('XP-4: init without plan-id throws', () => {
76
+ const sandbox = makeSandbox();
77
+ const cap = _capture();
78
+ assert.throws(
79
+ () => subcmd.run(['init'], { cwd: sandbox, stdout: cap.stub }),
80
+ (err) => err && err.code === 'execute-plan-missing-id',
81
+ );
82
+ });
@@ -0,0 +1,28 @@
1
+ const { COMMANDS } = require('./_commands.cjs');
2
+
3
+ function _renderText(commands) {
4
+ const byCat = new Map();
5
+ for (const c of commands) {
6
+ if (!byCat.has(c.category)) byCat.set(c.category, []);
7
+ byCat.get(c.category).push(c);
8
+ }
9
+ const lines = [];
10
+ for (const [cat, items] of byCat) {
11
+ lines.push(cat);
12
+ for (const c of items) {
13
+ lines.push(' ' + c.name.padEnd(10) + c.description);
14
+ }
15
+ lines.push('');
16
+ }
17
+ return lines.join('\n').replace(/\n+$/, '\n');
18
+ }
19
+
20
+ function run(args) {
21
+ const list = Array.isArray(args) ? args : [];
22
+ if (list.includes('--json')) {
23
+ return { commands: COMMANDS.slice() };
24
+ }
25
+ return { text: _renderText(COMMANDS) };
26
+ }
27
+
28
+ module.exports = { run };
@@ -0,0 +1,29 @@
1
+ const test = require('node:test');
2
+ const assert = require('node:assert/strict');
3
+
4
+ const helpCmd = require('./help.cjs');
5
+
6
+ test('HELP-CMD-1: run([]) returns rendered text grouped by category with all 5 names', () => {
7
+ const out = helpCmd.run([]);
8
+ assert.ok(out && typeof out.text === 'string');
9
+ for (const name of ['next', 'progress', 'state', 'help', 'init']) {
10
+ assert.match(out.text, new RegExp('\\b' + name + '\\b'));
11
+ }
12
+ });
13
+
14
+ test('HELP-CMD-2: run([--json]) returns { commands: [...] } with all registered entries', () => {
15
+ const out = helpCmd.run(['--json']);
16
+ assert.ok(Array.isArray(out.commands));
17
+
18
+
19
+ const names = out.commands.map((c) => c.name);
20
+ for (const n of ['help', 'init', 'next', 'progress', 'state']) {
21
+ assert.ok(names.includes(n), 'expected utility command: ' + n);
22
+ }
23
+ const planning = out.commands.filter((c) => c.category === 'Planning');
24
+ assert.ok(planning.length >= 8, 'expected ≥8 Planning commands, got ' + planning.length);
25
+ for (const c of out.commands) {
26
+ assert.ok(typeof c.category === 'string' && c.category.length > 0);
27
+ assert.ok(typeof c.description === 'string' && c.description.length > 0);
28
+ }
29
+ });
@@ -0,0 +1,91 @@
1
+ const { test } = require('node:test');
2
+ const assert = require('node:assert/strict');
3
+ const { spawnSync } = require('node:child_process');
4
+ const path = require('node:path');
5
+ const fs = require('node:fs');
6
+ const os = require('node:os');
7
+
8
+ const WORKFLOWS = ['ai-integration-phase', 'ui-phase', 'ui-review', 'eval-review'];
9
+ const REPO_ROOT = path.resolve(__dirname, '..', '..');
10
+ const NP_TOOLS = path.join(REPO_ROOT, 'np-tools.cjs');
11
+
12
+ for (const wf of WORKFLOWS) {
13
+ test(`DISP-${wf}: dispatcher routes argv past the verb-gate`, () => {
14
+
15
+
16
+ const tmp = fs.mkdtempSync(path.join(os.tmpdir(), 'np-dispatch-'));
17
+ const res = spawnSync('node', [NP_TOOLS, 'init', wf, '99'], {
18
+ cwd: tmp,
19
+ encoding: 'utf-8',
20
+ });
21
+ fs.rmSync(tmp, { recursive: true, force: true });
22
+
23
+ assert.doesNotMatch(
24
+ res.stderr,
25
+ /^Usage: np-tools\.cjs init/m,
26
+ `expected no Usage error on stderr for ${wf}, got: ${res.stderr}`,
27
+ );
28
+
29
+
30
+ });
31
+ }
32
+
33
+ const TOP_LEVEL_PHASE10_KEYS = [
34
+ 'askuser',
35
+ 'commit',
36
+ 'config-get',
37
+ 'dispatch',
38
+ 'doctor',
39
+ 'generate-slug',
40
+ 'metrics',
41
+ 'phase',
42
+ 'plan-diff',
43
+ 'queue',
44
+ 'resolve-model',
45
+ 'stats',
46
+ 'triage',
47
+ ];
48
+
49
+ test('TD-1: Phase 10 topLevelCommands routes metrics/resolve-model/plan-diff and 10 siblings', () => {
50
+ const np = require('../../np-tools.cjs');
51
+ assert.ok(np.topLevelCommands && typeof np.topLevelCommands === 'object');
52
+ for (const key of TOP_LEVEL_PHASE10_KEYS) {
53
+ const mod = np.topLevelCommands[key];
54
+ assert.ok(mod, `topLevelCommands[${key}] missing`);
55
+ assert.equal(
56
+ typeof mod.run,
57
+ 'function',
58
+ `topLevelCommands[${key}].run must be a function`,
59
+ );
60
+ }
61
+ });
62
+
63
+ test('TD-2: Phase 10 initWorkflows exposes code-review init entry', () => {
64
+ const np = require('../../np-tools.cjs');
65
+ assert.ok(np.initWorkflows && typeof np.initWorkflows === 'object');
66
+ const mod = np.initWorkflows['code-review'];
67
+ assert.ok(mod, 'initWorkflows[code-review] missing');
68
+ assert.equal(typeof mod.run, 'function');
69
+ });
70
+
71
+ test('TD-3: unknown topLevelCommand still returns unknown-command envelope', () => {
72
+ const tmp = fs.mkdtempSync(path.join(os.tmpdir(), 'np-dispatch-unknown-'));
73
+ const res = spawnSync('node', [NP_TOOLS, 'definitely-unknown-xyz'], {
74
+ cwd: tmp,
75
+ encoding: 'utf-8',
76
+ });
77
+ fs.rmSync(tmp, { recursive: true, force: true });
78
+ assert.notEqual(res.status, 0);
79
+ assert.match(res.stderr, /"code":\s*"unknown-command"/);
80
+ });
81
+
82
+ test('TD-4: metrics now subcommand prints ISO timestamp on stdout', () => {
83
+ const tmp = fs.mkdtempSync(path.join(os.tmpdir(), 'np-dispatch-metrics-'));
84
+ const res = spawnSync('node', [NP_TOOLS, 'metrics', 'now'], {
85
+ cwd: tmp,
86
+ encoding: 'utf-8',
87
+ });
88
+ fs.rmSync(tmp, { recursive: true, force: true });
89
+ assert.equal(res.status, 0);
90
+ assert.match(res.stdout.trim(), /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}/);
91
+ });
@@ -0,0 +1,97 @@
1
+ const fs = require('node:fs');
2
+ const { appendRecord, buildRecord } = require('../../lib/metrics.cjs');
3
+
4
+ function _usage() {
5
+ return [
6
+ 'Usage:',
7
+ ' np-tools.cjs metrics record --agent X --tier haiku|sonnet|opus --resolved-model M \\',
8
+ ' --phase P --plan PL --task T \\',
9
+ ' --started ISO --ended ISO \\',
10
+ ' --tokens-in N --tokens-out M \\',
11
+ ' --status ok|error|timeout --runtime claude|codex|gemini|opencode \\',
12
+ ' [--retry-count N] [--error-code C --error-message MSG]',
13
+ ' np-tools.cjs metrics record --json @file:/path/to/record.json',
14
+ ' np-tools.cjs metrics now | start-timestamp | end-timestamp',
15
+ ].join('\n');
16
+ }
17
+
18
+ function _parseArgs(argv) {
19
+ const out = { retry_count: 0, tokens_in: null, tokens_out: null, error: null };
20
+ let errorCode = null;
21
+ let errorMessage = null;
22
+ let jsonPtr = null;
23
+ let i = 0;
24
+ while (i < argv.length) {
25
+ const flag = argv[i++];
26
+ const val = argv[i++];
27
+ switch (flag) {
28
+ case '--agent': out.agent = val; break;
29
+ case '--tier': out.tier = val; break;
30
+ case '--resolved-model': out.resolved_model = val; break;
31
+ case '--phase': out.phase = val; break;
32
+ case '--plan': out.plan = val; break;
33
+ case '--task': out.task = val; break;
34
+ case '--started': out.started_at = val; break;
35
+ case '--ended': out.ended_at = val; break;
36
+ case '--tokens-in': out.tokens_in = parseInt(val, 10); break;
37
+ case '--tokens-out': out.tokens_out = parseInt(val, 10); break;
38
+ case '--retry-count': out.retry_count = parseInt(val, 10); break;
39
+ case '--status': out.status = val; break;
40
+ case '--runtime': out.runtime = val; break;
41
+ case '--error-code': errorCode = val; break;
42
+ case '--error-message': errorMessage = val; break;
43
+ case '--json': jsonPtr = val; break;
44
+ default: break;
45
+ }
46
+ }
47
+ if (errorCode || errorMessage) {
48
+ out.error = { code: errorCode || 'unknown', message: errorMessage || '' };
49
+ }
50
+ return { parsed: out, jsonPtr };
51
+ }
52
+
53
+ function _readJsonPtr(ptr) {
54
+ const prefix = '@file:';
55
+ const filePath = ptr.startsWith(prefix) ? ptr.slice(prefix.length) : ptr;
56
+ const raw = fs.readFileSync(filePath, 'utf-8');
57
+ return JSON.parse(raw);
58
+ }
59
+
60
+ function run(argv) {
61
+ const args = Array.isArray(argv) ? argv.slice() : process.argv.slice(3);
62
+ const sub = args.shift();
63
+
64
+ if (sub === 'now' || sub === 'start-timestamp' || sub === 'end-timestamp') {
65
+ process.stdout.write(new Date().toISOString() + '\n');
66
+ return 0;
67
+ }
68
+
69
+ if (sub === 'record') {
70
+ try {
71
+ const { parsed, jsonPtr } = _parseArgs(args);
72
+ const input = jsonPtr ? _readJsonPtr(jsonPtr) : parsed;
73
+ const record = buildRecord(input);
74
+ const file = appendRecord(record, { cwd: process.cwd() });
75
+ process.stdout.write(file + '\n');
76
+ return 0;
77
+ } catch (err) {
78
+ if (err && err.name === 'NubosPilotError') {
79
+ process.stderr.write(
80
+ JSON.stringify({ code: err.code, message: err.message, details: err.details }) + '\n',
81
+ );
82
+ } else {
83
+ process.stderr.write(String((err && err.stack) || err) + '\n');
84
+ }
85
+ return 1;
86
+ }
87
+ }
88
+
89
+ process.stderr.write(_usage() + '\n');
90
+ return 1;
91
+ }
92
+
93
+ module.exports = { run };
94
+
95
+ if (require.main === module) {
96
+ process.exit(run(process.argv.slice(2)));
97
+ }