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,213 @@
1
+ 'use strict';
2
+
3
+ const fs = require('node:fs');
4
+ const path = require('node:path');
5
+
6
+ const { parseRoadmap } = require('../../lib/roadmap.cjs');
7
+ const { listPlans } = require('../../lib/plan.cjs');
8
+
9
+ const MAX_TITLE_LEN = 80;
10
+ const STATE_DIR_NAME = '.nubos-pilot';
11
+
12
+ function _stateDir(cwd) {
13
+
14
+
15
+ return path.join(path.resolve(cwd), STATE_DIR_NAME);
16
+ }
17
+
18
+ function _truncate(s) {
19
+ if (typeof s !== 'string') return '';
20
+ const noHtmlComment = s.replace(/<!--[\s\S]*?-->/g, '').trim();
21
+ if (noHtmlComment.length <= MAX_TITLE_LEN) return noHtmlComment;
22
+ return noHtmlComment.slice(0, MAX_TITLE_LEN - 1) + '…';
23
+ }
24
+
25
+ function _readFirstH1(file) {
26
+ let raw;
27
+ try { raw = fs.readFileSync(file, 'utf-8'); } catch { return path.basename(file); }
28
+ const lines = raw.split('\n');
29
+ for (const line of lines) {
30
+ if (line.startsWith('# ')) return _truncate(line.slice(2));
31
+ }
32
+ return path.basename(file);
33
+ }
34
+
35
+ function _collectTodos(stateDir) {
36
+ const todosDir = path.join(stateDir, 'todos', 'pending');
37
+ if (!fs.existsSync(todosDir)) return [];
38
+ const items = [];
39
+ let entries;
40
+ try { entries = fs.readdirSync(todosDir); } catch { return []; }
41
+ for (const name of entries) {
42
+ if (!name.endsWith('.md')) continue;
43
+ const full = path.join(todosDir, name);
44
+ items.push({
45
+ source: 'todo',
46
+ id: name,
47
+ title: _readFirstH1(full),
48
+ path: full,
49
+ });
50
+ }
51
+ return items;
52
+ }
53
+
54
+ function _safeParseRoadmap(cwd) {
55
+ try { return parseRoadmap(cwd); } catch { return null; }
56
+ }
57
+
58
+ function _paddedPhase(n) {
59
+ const s = String(n);
60
+ if (s.length >= 2) return s;
61
+ return '0' + s;
62
+ }
63
+
64
+ function _collectBacklog(roadmap) {
65
+ if (!roadmap || !Array.isArray(roadmap.phases)) return [];
66
+ const items = [];
67
+ for (const p of roadmap.phases) {
68
+ const num = Number(p.number);
69
+ if (!Number.isFinite(num) || num < 999) continue;
70
+ items.push({
71
+ source: 'backlog',
72
+ id: String(p.number),
73
+ title: _truncate(p.goal || p.name || String(p.number)),
74
+ });
75
+ }
76
+ return items;
77
+ }
78
+
79
+ function _collectUat(roadmap, cwd) {
80
+ if (!roadmap || !Array.isArray(roadmap.phases)) return [];
81
+ const items = [];
82
+ const phaseParentDirs = [
83
+ path.join(path.resolve(cwd), STATE_DIR_NAME, 'phases'),
84
+ path.join(path.resolve(cwd), '.planning', 'phases'),
85
+ ];
86
+ for (const p of roadmap.phases) {
87
+ const padded = _paddedPhase(p.number);
88
+ const candidateFiles = [];
89
+ for (const parent of phaseParentDirs) {
90
+ if (!fs.existsSync(parent)) continue;
91
+ let dirs;
92
+ try { dirs = fs.readdirSync(parent); } catch { continue; }
93
+ const match = dirs.find((d) => d.startsWith(padded + '-'));
94
+ if (!match) continue;
95
+ const phaseDir = path.join(parent, match);
96
+ candidateFiles.push(
97
+ path.join(phaseDir, padded + '-VERIFICATION.md'),
98
+ path.join(phaseDir, padded + '-UAT.md'),
99
+ );
100
+ }
101
+ for (const file of candidateFiles) {
102
+ if (!fs.existsSync(file)) continue;
103
+ let raw;
104
+ try { raw = fs.readFileSync(file, 'utf-8'); } catch { continue; }
105
+ const lines = raw.split('\n');
106
+ for (const line of lines) {
107
+ if (!/^- \[ \]/.test(line)) continue;
108
+ const title = _truncate(line.replace(/^- \[ \]\s*/, ''));
109
+ const shortKey = (title || '').slice(0, 40).replace(/\s+/g, '-') || 'item';
110
+ items.push({
111
+ source: 'uat',
112
+ id: padded + ':' + shortKey,
113
+ title,
114
+ file,
115
+ });
116
+ }
117
+ }
118
+ }
119
+ return items;
120
+ }
121
+
122
+ function _collectUnplanned(roadmap, cwd) {
123
+ if (!roadmap || !Array.isArray(roadmap.phases)) return [];
124
+ const items = [];
125
+ const phaseParentDirs = [
126
+ path.join(path.resolve(cwd), STATE_DIR_NAME, 'phases'),
127
+ path.join(path.resolve(cwd), '.planning', 'phases'),
128
+ ];
129
+ for (const p of roadmap.phases) {
130
+ const num = Number(p.number);
131
+ if (!Number.isFinite(num) || num >= 999) continue;
132
+ const padded = _paddedPhase(p.number);
133
+ let phaseDir = null;
134
+ for (const parent of phaseParentDirs) {
135
+ if (!fs.existsSync(parent)) continue;
136
+ let dirs;
137
+ try { dirs = fs.readdirSync(parent); } catch { continue; }
138
+ const match = dirs.find((d) => d.startsWith(padded + '-'));
139
+ if (match) { phaseDir = path.join(parent, match); break; }
140
+ }
141
+ if (!phaseDir) {
142
+ items.push({
143
+ source: 'unplanned-phase',
144
+ id: String(p.number),
145
+ title: _truncate(p.goal || p.name || String(p.number)),
146
+ });
147
+ continue;
148
+ }
149
+ const plans = listPlans(phaseDir);
150
+ if (plans.length === 0) {
151
+ items.push({
152
+ source: 'unplanned-phase',
153
+ id: String(p.number),
154
+ title: _truncate(p.goal || p.name || String(p.number)),
155
+ });
156
+ }
157
+ }
158
+ return items;
159
+ }
160
+
161
+ function _fallbackMdRoadmap(cwd) {
162
+ const candidates = [
163
+ path.join(path.resolve(cwd), STATE_DIR_NAME, 'ROADMAP.md'),
164
+ path.join(path.resolve(cwd), '.planning', 'ROADMAP.md'),
165
+ ];
166
+ for (const file of candidates) {
167
+ if (!fs.existsSync(file)) continue;
168
+ let raw;
169
+ try { raw = fs.readFileSync(file, 'utf-8'); } catch { continue; }
170
+ const lines = raw.split('\n');
171
+ const phases = [];
172
+ let current = null;
173
+ for (const line of lines) {
174
+ const header = line.match(/^##\s+Phase\s+(\d+)/);
175
+ if (header) {
176
+ if (current) phases.push(current);
177
+ current = { number: header[1], goal: '', name: '' };
178
+ continue;
179
+ }
180
+ if (current && !current.goal) {
181
+ const g = line.match(/^\s*-\s*goal:\s*(.+)$/);
182
+ if (g) current.goal = g[1].trim();
183
+ }
184
+ }
185
+ if (current) phases.push(current);
186
+ if (phases.length > 0) return { phases };
187
+ }
188
+ return null;
189
+ }
190
+
191
+ function run(args, ctx) {
192
+ const context = ctx || {};
193
+ const cwd = context.cwd || process.cwd();
194
+ const stdout = context.stdout || process.stdout;
195
+
196
+ const stateDir = _stateDir(cwd);
197
+ const items = [];
198
+
199
+ items.push(..._collectTodos(stateDir));
200
+
201
+ const roadmap = _safeParseRoadmap(cwd) || _fallbackMdRoadmap(cwd);
202
+ if (roadmap) {
203
+ items.push(..._collectBacklog(roadmap));
204
+ items.push(..._collectUat(roadmap, cwd));
205
+ items.push(..._collectUnplanned(roadmap, cwd));
206
+ }
207
+
208
+ const payload = { items };
209
+ stdout.write(JSON.stringify(payload));
210
+ return payload;
211
+ }
212
+
213
+ module.exports = { run };
@@ -0,0 +1,144 @@
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 { NubosPilotError, projectStateDir } = require('../../lib/core.cjs');
7
+
8
+ const INLINE_THRESHOLD_BYTES = 16 * 1024;
9
+
10
+ function _parsePhaseArg(raw) {
11
+ if (raw == null || raw === '') {
12
+ throw new NubosPilotError(
13
+ 'research-invalid-phase-arg',
14
+ 'research-phase requires a phase number argument',
15
+ { value: raw == null ? '' : String(raw) },
16
+ );
17
+ }
18
+ const n = Number(raw);
19
+ if (!Number.isInteger(n) || n < 0) {
20
+ throw new NubosPilotError(
21
+ 'research-invalid-phase-arg',
22
+ 'research-phase argument must be a non-negative integer',
23
+ { value: String(raw) },
24
+ );
25
+ }
26
+ return n;
27
+ }
28
+
29
+ function _paddedPhase(n) {
30
+ return String(n).padStart(2, '0');
31
+ }
32
+
33
+ function _findPhaseDir(cwd, padded) {
34
+
35
+
36
+ let phasesRoot;
37
+ try {
38
+ phasesRoot = path.join(projectStateDir(cwd), 'phases');
39
+ } catch (err) {
40
+ if (!err || err.code !== 'not-in-project') throw err;
41
+ phasesRoot = path.join(path.resolve(cwd), '.planning', 'phases');
42
+ }
43
+ try {
44
+ const entries = fs.readdirSync(phasesRoot, { withFileTypes: true });
45
+ const hit = entries
46
+ .filter((e) => e.isDirectory())
47
+ .map((e) => e.name)
48
+ .find((name) => name === padded || name.startsWith(padded + '-'));
49
+ if (hit) return path.join(phasesRoot, hit);
50
+ } catch (err) {
51
+ if (err && err.code !== 'ENOENT') throw err;
52
+ }
53
+
54
+
55
+
56
+ return path.join(phasesRoot, padded);
57
+ }
58
+
59
+ function _readRoadmapPhase(cwd, phaseNumber) {
60
+ const { getPhase } = require('../../lib/roadmap.cjs');
61
+ try {
62
+ return getPhase(phaseNumber, cwd);
63
+ } catch (err) {
64
+ if (err && err.name === 'NubosPilotError' && err.code === 'phase-not-found') {
65
+ throw new NubosPilotError(
66
+ 'research-phase-not-found',
67
+ 'Phase ' + phaseNumber + ' not found in roadmap',
68
+ { number: phaseNumber },
69
+ );
70
+ }
71
+ throw err;
72
+ }
73
+ }
74
+
75
+ function _toolsAvailable() {
76
+ return {
77
+ WebFetch: process.env.NP_TOOLS_WEBFETCH === '1',
78
+ Context7: process.env.NP_TOOLS_CONTEXT7 === '1',
79
+ };
80
+ }
81
+
82
+ function _agentSkills(cwd) {
83
+ try {
84
+ const agents = require('../../lib/agents.cjs');
85
+ if (typeof agents.getAgentSkills === 'function') {
86
+ return { researcher: agents.getAgentSkills('np-researcher', cwd) };
87
+ }
88
+ } catch (_err) { }
89
+ return { researcher: null };
90
+ }
91
+
92
+ function _emit(payload, stdout, cwd) {
93
+ const json = JSON.stringify(payload, null, 2);
94
+ if (Buffer.byteLength(json, 'utf-8') <= INLINE_THRESHOLD_BYTES) {
95
+ stdout.write(json);
96
+ return;
97
+ }
98
+ let tmpDir;
99
+ try {
100
+ tmpDir = path.join(projectStateDir(cwd), '.tmp');
101
+ fs.mkdirSync(tmpDir, { recursive: true });
102
+ } catch (_err) {
103
+ tmpDir = os.tmpdir();
104
+ }
105
+ const suffix = process.pid + '-' + crypto.randomBytes(4).toString('hex');
106
+ const tmpPath = path.join(tmpDir, 'init-research-phase-' + suffix + '.json');
107
+ fs.writeFileSync(tmpPath, json, 'utf-8');
108
+ stdout.write('@file:' + tmpPath);
109
+ }
110
+
111
+ function run(args, ctx) {
112
+ const context = ctx || {};
113
+ const cwd = context.cwd || process.cwd();
114
+ const stdout = context.stdout || process.stdout;
115
+
116
+ const phase = _parsePhaseArg((args || [])[0]);
117
+ const padded = _paddedPhase(phase);
118
+ const phaseInfo = _readRoadmapPhase(cwd, phase);
119
+ const phase_dir = _findPhaseDir(cwd, padded);
120
+
121
+ const researchPath = path.join(phase_dir, padded + '-RESEARCH.md');
122
+ let has_research = false;
123
+ try {
124
+ has_research = fs.statSync(researchPath).isFile();
125
+ } catch (_err) { has_research = false; }
126
+
127
+ const payload = {
128
+ _workflow: 'research-phase',
129
+ phase,
130
+ padded,
131
+ phase_dir,
132
+ goal: phaseInfo.goal || '',
133
+ requirements: Array.isArray(phaseInfo.requirements)
134
+ ? phaseInfo.requirements.slice()
135
+ : [],
136
+ has_research,
137
+ tools_available: _toolsAvailable(),
138
+ agent_skills: _agentSkills(cwd),
139
+ };
140
+ _emit(payload, stdout, cwd);
141
+ return payload;
142
+ }
143
+
144
+ module.exports = { run, INLINE_THRESHOLD_BYTES, _parsePhaseArg };
@@ -0,0 +1,154 @@
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('./research-phase.cjs');
9
+
10
+ function _baseRoadmap() {
11
+ return {
12
+ schema_version: 1,
13
+ milestones: [
14
+ {
15
+ id: 'v1.0',
16
+ name: 'first',
17
+ phases: [
18
+ {
19
+ number: 3,
20
+ name: 'Three',
21
+ slug: 'three',
22
+ goal: 'Goal of phase 3',
23
+ depends_on: [],
24
+ requirements: ['R-1', 'R-2'],
25
+ success_criteria: ['SC-1'],
26
+ status: 'pending',
27
+ plans: [],
28
+ },
29
+ {
30
+ number: 5,
31
+ name: 'Five',
32
+ slug: 'five-planning',
33
+ goal: 'Goal of phase 5',
34
+ depends_on: [4],
35
+ requirements: ['PLAN-03'],
36
+ success_criteria: [],
37
+ status: 'pending',
38
+ plans: [],
39
+ },
40
+ ],
41
+ },
42
+ ],
43
+ };
44
+ }
45
+
46
+ function _captureStdout() {
47
+ let buf = '';
48
+ const stub = { write: (s) => { buf += s; return true; } };
49
+ return { stub, get: () => buf };
50
+ }
51
+
52
+ function _clearEnv() {
53
+ delete process.env.NP_TOOLS_WEBFETCH;
54
+ delete process.env.NP_TOOLS_CONTEXT7;
55
+ }
56
+
57
+ afterEach(() => {
58
+ _clearEnv();
59
+ cleanupAll();
60
+ });
61
+
62
+ test('RP-1: run(["3"]) on phase 3 returns payload with all required keys', () => {
63
+ const sandbox = makeSandbox();
64
+ seedRoadmapYaml(sandbox, _baseRoadmap());
65
+ seedPhaseDir(sandbox, 3, 'three', {});
66
+ const cap = _captureStdout();
67
+ subcmd.run(['3'], { cwd: sandbox, stdout: cap.stub });
68
+ const payload = JSON.parse(cap.get().trim());
69
+ assert.equal(payload.phase, 3);
70
+ assert.equal(payload.padded, '03');
71
+ assert.ok(payload.phase_dir.endsWith(path.join('phases', '03-three')));
72
+ assert.equal(payload.goal, 'Goal of phase 3');
73
+ assert.deepEqual(payload.requirements, ['R-1', 'R-2']);
74
+ assert.equal(payload.has_research, false);
75
+ assert.equal(typeof payload.tools_available, 'object');
76
+ assert.equal(typeof payload.tools_available.WebFetch, 'boolean');
77
+ assert.equal(typeof payload.tools_available.Context7, 'boolean');
78
+ assert.ok('agent_skills' in payload);
79
+ });
80
+
81
+ test('RP-2: has_research=true iff {phase_dir}/{padded}-RESEARCH.md exists', () => {
82
+ const sandbox = makeSandbox();
83
+ seedRoadmapYaml(sandbox, _baseRoadmap());
84
+ seedPhaseDir(sandbox, 3, 'three', { '03-RESEARCH.md': '# Research stub\n' });
85
+ const cap = _captureStdout();
86
+ subcmd.run(['3'], { cwd: sandbox, stdout: cap.stub });
87
+ const payload = JSON.parse(cap.get().trim());
88
+ assert.equal(payload.has_research, true);
89
+ });
90
+
91
+ test('RP-3: tools_available defaults to {false,false} when env vars absent', () => {
92
+ const sandbox = makeSandbox();
93
+ seedRoadmapYaml(sandbox, _baseRoadmap());
94
+ seedPhaseDir(sandbox, 5, 'five-planning', {});
95
+ _clearEnv();
96
+ const cap = _captureStdout();
97
+ subcmd.run(['5'], { cwd: sandbox, stdout: cap.stub });
98
+ const payload = JSON.parse(cap.get().trim());
99
+ assert.equal(payload.tools_available.WebFetch, false);
100
+ assert.equal(payload.tools_available.Context7, false);
101
+ });
102
+
103
+ test('RP-4: NP_TOOLS_WEBFETCH=1 and NP_TOOLS_CONTEXT7=1 flip both booleans', () => {
104
+ const sandbox = makeSandbox();
105
+ seedRoadmapYaml(sandbox, _baseRoadmap());
106
+ seedPhaseDir(sandbox, 5, 'five-planning', {});
107
+ process.env.NP_TOOLS_WEBFETCH = '1';
108
+ process.env.NP_TOOLS_CONTEXT7 = '1';
109
+ const cap = _captureStdout();
110
+ subcmd.run(['5'], { cwd: sandbox, stdout: cap.stub });
111
+ const payload = JSON.parse(cap.get().trim());
112
+ assert.equal(payload.tools_available.WebFetch, true);
113
+ assert.equal(payload.tools_available.Context7, true);
114
+ });
115
+
116
+ test('RP-5: missing phase number throws research-phase-not-found', () => {
117
+ const sandbox = makeSandbox();
118
+ seedRoadmapYaml(sandbox, _baseRoadmap());
119
+ const cap = _captureStdout();
120
+ assert.throws(
121
+ () => subcmd.run(['99'], { cwd: sandbox, stdout: cap.stub }),
122
+ (err) => err.name === 'NubosPilotError' && err.code === 'research-phase-not-found',
123
+ );
124
+ });
125
+
126
+ test('RP-6: non-integer arg throws research-invalid-phase-arg', () => {
127
+ const sandbox = makeSandbox();
128
+ seedRoadmapYaml(sandbox, _baseRoadmap());
129
+ const cap = _captureStdout();
130
+ assert.throws(
131
+ () => subcmd.run(['bad'], { cwd: sandbox, stdout: cap.stub }),
132
+ (err) => err.name === 'NubosPilotError' && err.code === 'research-invalid-phase-arg',
133
+ );
134
+ });
135
+
136
+ test('RP-7: oversized payload emits @file: pointer', () => {
137
+ const sandbox = makeSandbox();
138
+
139
+ const big = _baseRoadmap();
140
+ const huge = Array.from({ length: 2000 }, (_, i) => 'REQ-' + i + '-very-long-requirement-identifier-padded');
141
+ big.milestones[0].phases[0].requirements = huge;
142
+ seedRoadmapYaml(sandbox, big);
143
+ seedPhaseDir(sandbox, 3, 'three', {});
144
+ const cap = _captureStdout();
145
+ subcmd.run(['3'], { cwd: sandbox, stdout: cap.stub });
146
+ const out = cap.get().trim();
147
+ assert.ok(out.startsWith('@file:'), 'large payload produced @file: pointer');
148
+ const tmpPath = out.slice('@file:'.length);
149
+ const body = fs.readFileSync(tmpPath, 'utf-8');
150
+ const payload = JSON.parse(body);
151
+ assert.equal(payload.phase, 3);
152
+ assert.ok(payload.requirements.length >= 2000);
153
+ fs.unlinkSync(tmpPath);
154
+ });
@@ -0,0 +1,17 @@
1
+ const { resetSlice } = require('../../lib/undo.cjs');
2
+
3
+ function run(_args, ctx) {
4
+ const context = ctx || {};
5
+ const cwd = context.cwd || process.cwd();
6
+ const stdout = context.stdout || process.stdout;
7
+ const result = resetSlice(cwd);
8
+ const payload = {
9
+ ok: true,
10
+ task_id: result.task_id,
11
+ restored_paths: result.restored_paths,
12
+ };
13
+ stdout.write(JSON.stringify(payload));
14
+ return payload;
15
+ }
16
+
17
+ module.exports = { run };
@@ -0,0 +1,96 @@
1
+ const { test, after } = require('node:test');
2
+ const assert = require('node:assert/strict');
3
+ const fs = require('node:fs');
4
+ const path = require('node:path');
5
+ const os = require('node:os');
6
+ const { execFileSync } = require('node:child_process');
7
+
8
+ const subcmd = require('./reset-slice.cjs');
9
+ const cp = require('../../lib/checkpoint.cjs');
10
+
11
+ const _repos = [];
12
+
13
+ function makeRepo() {
14
+ const root = fs.mkdtempSync(path.join(os.tmpdir(), 'np-rs-'));
15
+ execFileSync('git', ['init', '-q', '-b', 'main', root], { stdio: 'pipe' });
16
+ execFileSync('git', ['-C', root, 'config', 'user.email', 'test@nubos.local']);
17
+ execFileSync('git', ['-C', root, 'config', 'user.name', 'nubos-test']);
18
+ execFileSync('git', ['-C', root, 'commit', '--allow-empty', '-q', '-m', 'init'], { stdio: 'pipe' });
19
+ fs.mkdirSync(path.join(root, '.nubos-pilot'), { recursive: true });
20
+ fs.writeFileSync(path.join(root, '.nubos-pilot', 'STATE.md'), `---
21
+ schema_version: 2
22
+ milestone: m1
23
+ milestone_name: m1
24
+ current_phase: 6
25
+ current_plan: "06-01"
26
+ current_task: null
27
+ last_updated: "2026-04-15T00:00:00Z"
28
+ progress:
29
+ total_phases: 0
30
+ completed_phases: 0
31
+ total_plans: 0
32
+ completed_plans: 0
33
+ percent: 0
34
+ session:
35
+ stopped_at: null
36
+ resume_file: null
37
+ last_activity: null
38
+ ---
39
+
40
+ # State
41
+ `, 'utf-8');
42
+ _repos.push(root);
43
+ return root;
44
+ }
45
+
46
+ function _capture() { let b = ''; return { stub: { write: (s) => { b += s; } }, get: () => b }; }
47
+
48
+ after(() => {
49
+ while (_repos.length) {
50
+ const r = _repos.pop();
51
+ try { fs.rmSync(r, { recursive: true, force: true }); } catch {}
52
+ }
53
+ });
54
+
55
+ test('RS-1: reset-slice with no current_task → undo-dirty-tree', () => {
56
+ const root = makeRepo();
57
+ assert.throws(
58
+ () => subcmd.run([], { cwd: root, stdout: _capture().stub }),
59
+ (err) => err && err.code === 'undo-dirty-tree',
60
+ );
61
+ });
62
+
63
+ test('RS-2: reset-slice restores files and emits payload', () => {
64
+ const root = makeRepo();
65
+
66
+ const phaseDir = path.join(root, '.nubos-pilot', 'phases', '06-demo');
67
+ const tasksDir = path.join(phaseDir, 'tasks');
68
+ fs.mkdirSync(tasksDir, { recursive: true });
69
+ fs.writeFileSync(path.join(tasksDir, '06-01-T05.md'), [
70
+ '---', 'id: 06-01-T05', 'phase: 6', 'plan: "06-01"', 'type: auto',
71
+ 'status: in-progress', 'tier: sonnet', 'owner: np-executor', 'wave: 1',
72
+ 'depends_on: []', 'files_modified:', ' - src/r.ts',
73
+ 'autonomous: true', 'must_haves:', ' truths: []', '---', '', '# T',
74
+ ].join('\n'), 'utf-8');
75
+
76
+ fs.mkdirSync(path.join(root, 'src'), { recursive: true });
77
+ fs.writeFileSync(path.join(root, 'src', 'r.ts'), 'baseline\n', 'utf-8');
78
+ const prev = process.cwd();
79
+ process.chdir(root);
80
+ try {
81
+ execFileSync('git', ['add', 'src/r.ts'], { stdio: 'pipe' });
82
+ execFileSync('git', ['commit', '-q', '-m', 'baseline'], { stdio: 'pipe' });
83
+ cp.startTask({ id: '06-01-T05', phase: 6, plan: '06-01', wave: 1 }, root);
84
+ cp.writeCheckpoint('06-01-T05', { files_touched: ['src/r.ts'] }, root);
85
+ fs.writeFileSync(path.join(root, 'src', 'r.ts'), 'dirty\n', 'utf-8');
86
+ const cap = _capture();
87
+ subcmd.run([], { cwd: root, stdout: cap.stub });
88
+ const payload = JSON.parse(cap.get());
89
+ assert.equal(payload.ok, true);
90
+ assert.equal(payload.task_id, '06-01-T05');
91
+ assert.deepEqual(payload.restored_paths, ['src/r.ts']);
92
+ assert.equal(fs.readFileSync(path.join(root, 'src', 'r.ts'), 'utf-8'), 'baseline\n');
93
+ } finally {
94
+ process.chdir(prev);
95
+ }
96
+ });