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,171 @@
1
+ const fs = require('node:fs');
2
+ const path = require('node:path');
3
+
4
+ const { NubosPilotError, atomicWriteFileSync, withFileLock } = require('../../lib/core.cjs');
5
+ const { getPhase } = require('../../lib/roadmap.cjs');
6
+ const { paddedPhase, phaseSlug, findPhaseDir } = require('../../lib/phase.cjs');
7
+ const { parseVerificationMd } = require('../../lib/verify.cjs');
8
+
9
+ const BEGIN_MARKER = '// >>> np:add-tests begin';
10
+ const END_MARKER = '// <<< np:add-tests end';
11
+
12
+ function _validatePhaseArg(raw) {
13
+ if (raw == null || raw === '' || !/^\d+(\.\d+)?$/.test(String(raw))) {
14
+ throw new NubosPilotError(
15
+ 'add-tests-invalid-phase',
16
+ 'add-tests requires a numeric phase argument',
17
+ { value: raw == null ? '' : String(raw) },
18
+ );
19
+ }
20
+ return String(raw);
21
+ }
22
+
23
+ function _resolveTestTarget(phaseArg, cwd) {
24
+ const phaseN = Number(phaseArg);
25
+ const phase = getPhase(phaseN, cwd);
26
+ const slug = phase.slug || phaseSlug(phase.name);
27
+ const padded = paddedPhase(phaseN);
28
+ let dir = path.resolve(cwd);
29
+ for (let i = 0; i < 10; i++) {
30
+ if (fs.existsSync(path.join(dir, 'package.json'))) {
31
+ return {
32
+ pkg_root: dir,
33
+ target_path: path.join(dir, 'test', 'uat', 'phase-' + padded + '-' + slug + '.test.cjs'),
34
+ padded, slug,
35
+ };
36
+ }
37
+ const parent = path.dirname(dir);
38
+ if (parent === dir) break;
39
+ dir = parent;
40
+ }
41
+
42
+ return {
43
+ pkg_root: path.resolve(cwd),
44
+ target_path: path.join(path.resolve(cwd), 'test', 'uat', 'phase-' + padded + '-' + slug + '.test.cjs'),
45
+ padded, slug,
46
+ };
47
+ }
48
+
49
+ function _loadCases(phaseArg, cwd) {
50
+ const phaseN = Number(phaseArg);
51
+ const padded = paddedPhase(phaseN);
52
+ const phase_dir = findPhaseDir(phaseN, cwd);
53
+ if (!phase_dir) {
54
+ throw new NubosPilotError(
55
+ 'add-tests-phase-dir-missing',
56
+ 'Phase directory not found for phase ' + phaseN,
57
+ { phase: phaseN },
58
+ );
59
+ }
60
+ const vp = path.join(phase_dir, padded + '-VERIFICATION.md');
61
+ if (!fs.existsSync(vp)) {
62
+ throw new NubosPilotError(
63
+ 'add-tests-verification-missing',
64
+ 'VERIFICATION.md not found — run `/np:verify-work ' + phaseArg + '` first',
65
+ { path: vp },
66
+ );
67
+ }
68
+ const all = parseVerificationMd(vp);
69
+ const passes = all.filter((c) => c.status === 'Pass');
70
+ const skips = all.filter((c) => c.status === 'Fail' || c.status === 'Defer');
71
+ return { all, passes, skips, verification_path: vp };
72
+ }
73
+
74
+ function _jsString(s) {
75
+ return "'" + String(s).replace(/\\/g, '\\\\').replace(/'/g, "\\'").replace(/\n/g, '\\n') + "'";
76
+ }
77
+
78
+ function _renderBlock(phasePadded, passes, skips) {
79
+ const date = new Date().toISOString().slice(0, 10);
80
+ const lines = [];
81
+ lines.push(BEGIN_MARKER + ' (phase ' + phasePadded + ', generated ' + date + ')');
82
+ lines.push("const { test } = require('node:test');");
83
+ lines.push("const assert = require('node:assert');");
84
+ lines.push('');
85
+ for (const c of passes) {
86
+ lines.push('test(' + _jsString(c.id + ': ' + c.text) + ', () => {');
87
+ lines.push(' // TODO: implement UAT for ' + c.id);
88
+ lines.push(' assert.ok(true);');
89
+ lines.push('});');
90
+ }
91
+ for (const c of skips) {
92
+ lines.push('test.skip(' + _jsString(c.id + ': ' + c.text) + ', { todo: ' + _jsString('Deferred: ' + c.status) + ' }, () => {});');
93
+ }
94
+ lines.push(END_MARKER);
95
+ return lines.join('\n');
96
+ }
97
+
98
+ function _mergeBlock(existing, block) {
99
+ if (!existing) {
100
+ return block + '\n';
101
+ }
102
+
103
+ const beginIdx = existing.indexOf(BEGIN_MARKER);
104
+ const endIdx = existing.indexOf(END_MARKER);
105
+ if (beginIdx < 0 || endIdx < 0 || endIdx < beginIdx) {
106
+
107
+ return existing.replace(/\n*$/, '\n') + block + '\n';
108
+ }
109
+ const before = existing.slice(0, beginIdx);
110
+ const after = existing.slice(endIdx + END_MARKER.length);
111
+ return before + block + after;
112
+ }
113
+
114
+ function _emitTests(phaseArg, cwd) {
115
+ const { passes, skips } = _loadCases(phaseArg, cwd);
116
+ const target = _resolveTestTarget(phaseArg, cwd);
117
+ fs.mkdirSync(path.dirname(target.target_path), { recursive: true });
118
+ const block = _renderBlock(target.padded, passes, skips);
119
+ return withFileLock(target.target_path, () => {
120
+ let existing = null;
121
+ try { existing = fs.readFileSync(target.target_path, 'utf-8'); } catch { existing = null; }
122
+ const next = _mergeBlock(existing, block);
123
+ atomicWriteFileSync(target.target_path, next);
124
+ return {
125
+ ok: true,
126
+ target_path: target.target_path,
127
+ pass_count: passes.length,
128
+ skip_count: skips.length,
129
+ };
130
+ });
131
+ }
132
+
133
+ function run(args, ctx) {
134
+ const context = ctx || {};
135
+ const cwd = context.cwd || process.cwd();
136
+ const stdout = context.stdout || process.stdout;
137
+ const list = Array.isArray(args) ? args : [];
138
+ const verb = list[0];
139
+
140
+ switch (verb) {
141
+ case 'init': {
142
+ const phaseArg = _validatePhaseArg(list[1]);
143
+ const target = _resolveTestTarget(phaseArg, cwd);
144
+ const { passes, skips, verification_path } = _loadCases(phaseArg, cwd);
145
+ const payload = {
146
+ _workflow: 'add-tests',
147
+ phase: phaseArg,
148
+ target_path: target.target_path,
149
+ verification_path,
150
+ pass_cases: passes,
151
+ skip_cases: skips,
152
+ };
153
+ stdout.write(JSON.stringify(payload, null, 2));
154
+ return payload;
155
+ }
156
+ case 'emit': {
157
+ const phaseArg = _validatePhaseArg(list[1]);
158
+ const result = _emitTests(phaseArg, cwd);
159
+ stdout.write(JSON.stringify(result));
160
+ return result;
161
+ }
162
+ default:
163
+ throw new NubosPilotError(
164
+ 'add-tests-unknown-verb',
165
+ 'add-tests: unknown verb: ' + String(verb),
166
+ { verb },
167
+ );
168
+ }
169
+ }
170
+
171
+ module.exports = { run, BEGIN_MARKER, END_MARKER };
@@ -0,0 +1,122 @@
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('./add-tests.cjs');
9
+
10
+ function _roadmap() {
11
+ return {
12
+ schema_version: 1,
13
+ milestones: [{ id: 'v1.0', name: 'm1', phases: [
14
+ { number: 6, name: 'Execution', slug: 'execution', goal: '', depends_on: [],
15
+ requirements: [], success_criteria: ['a', 'b', 'c'],
16
+ status: 'planned', plans: [] },
17
+ ]}],
18
+ };
19
+ }
20
+
21
+ function _capture() { let b = ''; return { stub: { write: (s) => { b += s; return true; } }, get: () => b }; }
22
+
23
+ function _seedVerification(sandbox) {
24
+ const phaseDir = seedPhaseDir(sandbox, 6, 'execution', {});
25
+ const v = [
26
+ '# Phase 6: Execution - Verification',
27
+ '',
28
+ '**Verified:** 2026-04-15',
29
+ '**Phase Status:** deferred',
30
+ '',
31
+ '## Success Criteria',
32
+ '',
33
+ '### SC-1: First passes',
34
+ '- **Status:** Pass',
35
+ '- **Classified by:** user',
36
+ '- **Evidence:** —',
37
+ '',
38
+ '### SC-2: Second fails',
39
+ '- **Status:** Fail',
40
+ '- **Classified by:** user',
41
+ '- **Evidence:** —',
42
+ '',
43
+ '### SC-3: Deferred',
44
+ '- **Status:** Defer',
45
+ '- **Classified by:** user',
46
+ '- **Evidence:** —',
47
+ '',
48
+ ].join('\n');
49
+ fs.writeFileSync(path.join(phaseDir, '06-VERIFICATION.md'), v, 'utf-8');
50
+
51
+ fs.writeFileSync(path.join(sandbox, 'package.json'), '{"name":"sandbox"}', 'utf-8');
52
+ return phaseDir;
53
+ }
54
+
55
+ afterEach(cleanupAll);
56
+
57
+ test('AT-1: init emits pass_cases (1) and skip_cases (2) categorized', () => {
58
+ const sandbox = makeSandbox();
59
+ seedRoadmapYaml(sandbox, _roadmap());
60
+ _seedVerification(sandbox);
61
+ const cap = _capture();
62
+ const p = subcmd.run(['init', '6'], { cwd: sandbox, stdout: cap.stub });
63
+ assert.equal(p.pass_cases.length, 1);
64
+ assert.equal(p.skip_cases.length, 2);
65
+ assert.ok(p.target_path.endsWith('test/uat/phase-06-execution.test.cjs'));
66
+ });
67
+
68
+ test('AT-2: emit writes node:test file with begin/end sentinels', () => {
69
+ const sandbox = makeSandbox();
70
+ seedRoadmapYaml(sandbox, _roadmap());
71
+ _seedVerification(sandbox);
72
+ const cap = _capture();
73
+ const r = subcmd.run(['emit', '6'], { cwd: sandbox, stdout: cap.stub });
74
+ const body = fs.readFileSync(r.target_path, 'utf-8');
75
+ assert.ok(body.includes('// >>> np:add-tests begin'));
76
+ assert.ok(body.includes('// <<< np:add-tests end'));
77
+ assert.ok(body.includes("test('SC-1: First passes'"));
78
+ assert.ok(body.includes("test.skip('SC-2: Second fails'"));
79
+ assert.ok(body.includes("test.skip('SC-3: Deferred'"));
80
+ });
81
+
82
+ test('AT-3: sentinel preservation — user edits OUTSIDE block survive regeneration', () => {
83
+ const sandbox = makeSandbox();
84
+ seedRoadmapYaml(sandbox, _roadmap());
85
+ _seedVerification(sandbox);
86
+ const cap1 = _capture();
87
+ subcmd.run(['emit', '6'], { cwd: sandbox, stdout: cap1.stub });
88
+ const target = path.join(sandbox, 'test', 'uat', 'phase-06-execution.test.cjs');
89
+
90
+ let body = fs.readFileSync(target, 'utf-8');
91
+ const userTest = "// USER AUTHORED: do not delete\ntest('user: custom case', () => { assert.ok(1); });\n\n";
92
+ body = userTest + body;
93
+ fs.writeFileSync(target, body, 'utf-8');
94
+
95
+ const cap2 = _capture();
96
+ subcmd.run(['emit', '6'], { cwd: sandbox, stdout: cap2.stub });
97
+ const after = fs.readFileSync(target, 'utf-8');
98
+ assert.ok(after.includes('// USER AUTHORED: do not delete'), 'user content lost');
99
+ assert.ok(after.includes("test('user: custom case'"), 'user test lost');
100
+ assert.ok(after.includes('// >>> np:add-tests begin'));
101
+ });
102
+
103
+ test('AT-4: missing VERIFICATION.md → loud error', () => {
104
+ const sandbox = makeSandbox();
105
+ seedRoadmapYaml(sandbox, _roadmap());
106
+ seedPhaseDir(sandbox, 6, 'execution', {});
107
+ fs.writeFileSync(path.join(sandbox, 'package.json'), '{}', 'utf-8');
108
+ const cap = _capture();
109
+ assert.throws(
110
+ () => subcmd.run(['init', '6'], { cwd: sandbox, stdout: cap.stub }),
111
+ (err) => err && err.code === 'add-tests-verification-missing',
112
+ );
113
+ });
114
+
115
+ test('AT-5: unknown verb throws', () => {
116
+ const sandbox = makeSandbox();
117
+ const cap = _capture();
118
+ assert.throws(
119
+ () => subcmd.run(['bogus'], { cwd: sandbox, stdout: cap.stub }),
120
+ (err) => err && err.code === 'add-tests-unknown-verb',
121
+ );
122
+ });
@@ -0,0 +1,108 @@
1
+ const fs = require('node:fs');
2
+ const path = require('node:path');
3
+
4
+ const { projectStateDir, NubosPilotError } = require('../../lib/core.cjs');
5
+ const { phaseSlug } = require('../../lib/phase.cjs');
6
+
7
+ const MAX_DESCRIPTION_LENGTH = 500;
8
+
9
+ function _buildPayload(description, cwd) {
10
+ if (description == null || typeof description !== 'string' || !description.trim()) {
11
+ throw new NubosPilotError(
12
+ 'add-todo-missing-description',
13
+ 'add-todo requires a non-empty description argument',
14
+ { description: description == null ? '' : String(description) },
15
+ );
16
+ }
17
+ if (/\n\n---\n/.test(description)) {
18
+ throw new NubosPilotError(
19
+ 'add-todo-invalid-description',
20
+ 'add-todo description must not contain YAML separator pattern',
21
+ { description },
22
+ );
23
+ }
24
+ if (description.length > MAX_DESCRIPTION_LENGTH) {
25
+ throw new NubosPilotError(
26
+ 'add-todo-description-too-long',
27
+ 'add-todo description must be <= ' + MAX_DESCRIPTION_LENGTH + ' chars',
28
+ { length: description.length },
29
+ );
30
+ }
31
+ const stateDir = projectStateDir(cwd);
32
+ const todosDir = path.join(stateDir, 'todos');
33
+ const pendingDir = path.join(todosDir, 'pending');
34
+ const now = new Date();
35
+ const iso = now.toISOString();
36
+ const date = iso.slice(0, 10);
37
+ const slug = phaseSlug(description);
38
+ if (!slug) {
39
+ throw new NubosPilotError(
40
+ 'add-todo-empty-slug',
41
+ 'add-todo description contains no slug-safe characters: ' + description,
42
+ { description },
43
+ );
44
+ }
45
+ let todo_count = 0;
46
+ try {
47
+ if (fs.existsSync(pendingDir)) {
48
+ todo_count = fs
49
+ .readdirSync(pendingDir)
50
+ .filter((f) => f.endsWith('.md')).length;
51
+ }
52
+ } catch (_err) {
53
+ todo_count = 0;
54
+ }
55
+ const todos_dir_exists = fs.existsSync(todosDir);
56
+ const state_path = path.join(stateDir, 'STATE.md');
57
+ return {
58
+ _workflow: 'add-todo',
59
+ commit_docs: true,
60
+ description,
61
+ slug,
62
+ date,
63
+ timestamp: iso,
64
+ state_dir: stateDir,
65
+ pending_dir: pendingDir,
66
+ todos_dir_exists,
67
+ todo_count,
68
+ state_path,
69
+ todos: [],
70
+ };
71
+ }
72
+
73
+ function _emitError(err, stderr) {
74
+ if (err && err.name === 'NubosPilotError') {
75
+ stderr.write(
76
+ JSON.stringify({ code: err.code, message: err.message, details: err.details }) + '\n',
77
+ );
78
+ } else {
79
+ stderr.write(String((err && err.stack) || err) + '\n');
80
+ }
81
+ }
82
+
83
+ function run(argv, ctx) {
84
+ const context = ctx || {};
85
+ const cwd = context.cwd || process.cwd();
86
+ const stdout = context.stdout || process.stdout;
87
+ const stderr = context.stderr || process.stderr;
88
+ const args = Array.isArray(argv) ? argv.slice() : [];
89
+ const description = args.join(' ').trim();
90
+ if (!description) {
91
+ stderr.write('Usage: np-tools.cjs init add-todo <description>\n');
92
+ return 1;
93
+ }
94
+ try {
95
+ const payload = _buildPayload(description, cwd);
96
+ stdout.write(JSON.stringify(payload, null, 2));
97
+ return 0;
98
+ } catch (err) {
99
+ _emitError(err, stderr);
100
+ return 1;
101
+ }
102
+ }
103
+
104
+ module.exports = { run, _buildPayload };
105
+
106
+ if (require.main === module) {
107
+ process.exit(run(process.argv.slice(2)));
108
+ }
@@ -0,0 +1,112 @@
1
+ const fs = require('node:fs');
2
+ const os = require('node:os');
3
+ const path = require('node:path');
4
+ const { test, afterEach } = require('node:test');
5
+ const assert = require('node:assert/strict');
6
+
7
+ const subcmd = require('./add-todo.cjs');
8
+
9
+ const _sandboxes = [];
10
+
11
+ function makeSandbox() {
12
+ const root = fs.mkdtempSync(path.join(os.tmpdir(), 'np-add-todo-'));
13
+ fs.mkdirSync(path.join(root, '.nubos-pilot'), { recursive: true });
14
+ _sandboxes.push(root);
15
+ return root;
16
+ }
17
+
18
+ function captureStdio(fn) {
19
+ const outChunks = [];
20
+ const errChunks = [];
21
+ const origOut = process.stdout.write.bind(process.stdout);
22
+ const origErr = process.stderr.write.bind(process.stderr);
23
+ process.stdout.write = (c) => { outChunks.push(String(c)); return true; };
24
+ process.stderr.write = (c) => { errChunks.push(String(c)); return true; };
25
+ let rc;
26
+ try { rc = fn(); } finally {
27
+ process.stdout.write = origOut;
28
+ process.stderr.write = origErr;
29
+ }
30
+ return { stdout: outChunks.join(''), stderr: errChunks.join(''), rc };
31
+ }
32
+
33
+ afterEach(() => {
34
+ while (_sandboxes.length) {
35
+ try { fs.rmSync(_sandboxes.pop(), { recursive: true, force: true }); } catch { }
36
+ }
37
+ });
38
+
39
+ test('AT-1: _buildPayload("Fix deploy key auth", sandbox) returns payload with kebab-case slug', () => {
40
+ const cwd = makeSandbox();
41
+ const payload = subcmd._buildPayload('Fix deploy key auth', cwd);
42
+ assert.equal(payload._workflow, 'add-todo');
43
+ assert.equal(payload.slug, 'fix-deploy-key-auth');
44
+ assert.equal(payload.description, 'Fix deploy key auth');
45
+ assert.equal(payload.commit_docs, true);
46
+ assert.match(payload.date, /^\d{4}-\d{2}-\d{2}$/);
47
+ assert.match(payload.timestamp, /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z$/);
48
+ assert.ok(payload.pending_dir.endsWith(path.join('.nubos-pilot', 'todos', 'pending')));
49
+ assert.equal(payload.todo_count, 0);
50
+ assert.equal(payload.todos_dir_exists, false);
51
+ });
52
+
53
+ test('AT-2: _buildPayload("", sandbox) throws NubosPilotError code "add-todo-missing-description"', () => {
54
+ const cwd = makeSandbox();
55
+ assert.throws(
56
+ () => subcmd._buildPayload('', cwd),
57
+ (err) => err && err.name === 'NubosPilotError' && err.code === 'add-todo-missing-description',
58
+ );
59
+ assert.throws(
60
+ () => subcmd._buildPayload(' ', cwd),
61
+ (err) => err && err.name === 'NubosPilotError' && err.code === 'add-todo-missing-description',
62
+ );
63
+ });
64
+
65
+ test('AT-3: _buildPayload(501-char string, sandbox) throws code "add-todo-description-too-long"', () => {
66
+ const cwd = makeSandbox();
67
+ const longDesc = 'x'.repeat(501);
68
+ assert.throws(
69
+ () => subcmd._buildPayload(longDesc, cwd),
70
+ (err) => err && err.name === 'NubosPilotError' && err.code === 'add-todo-description-too-long',
71
+ );
72
+ });
73
+
74
+ test('AT-4: payload.date matches YYYY-MM-DD and prefix of payload.timestamp', () => {
75
+ const cwd = makeSandbox();
76
+ const payload = subcmd._buildPayload('Refactor the router', cwd);
77
+ assert.equal(payload.date.length, 10);
78
+ assert.equal(payload.timestamp.slice(0, 10), payload.date);
79
+ });
80
+
81
+ test('AT-5: run([]) writes usage to stderr and returns 1', () => {
82
+ const cap = captureStdio(() => subcmd.run([]));
83
+ assert.equal(cap.rc, 1);
84
+ assert.match(cap.stderr, /Usage:\s+np-tools\.cjs init add-todo/);
85
+ });
86
+
87
+ test('AT-6: run(["Fix deploy key auth"]) emits JSON with slug on stdout and exits 0', () => {
88
+ const cwd = makeSandbox();
89
+ const orig = process.cwd();
90
+ process.chdir(cwd);
91
+ try {
92
+ const cap = captureStdio(() => subcmd.run(['Fix', 'deploy', 'key', 'auth']));
93
+ assert.equal(cap.rc, 0, 'stderr: ' + cap.stderr);
94
+ const parsed = JSON.parse(cap.stdout);
95
+ assert.equal(parsed.slug, 'fix-deploy-key-auth');
96
+ assert.equal(parsed._workflow, 'add-todo');
97
+ } finally {
98
+ process.chdir(orig);
99
+ }
100
+ });
101
+
102
+ test('AT-7: todo_count reflects existing .md files in pending/', () => {
103
+ const cwd = makeSandbox();
104
+ const pending = path.join(cwd, '.nubos-pilot', 'todos', 'pending');
105
+ fs.mkdirSync(pending, { recursive: true });
106
+ fs.writeFileSync(path.join(pending, '2026-04-01-first.md'), '---\n---\n', 'utf-8');
107
+ fs.writeFileSync(path.join(pending, '2026-04-02-second.md'), '---\n---\n', 'utf-8');
108
+ fs.writeFileSync(path.join(pending, 'ignore-me.txt'), 'x', 'utf-8');
109
+ const payload = subcmd._buildPayload('Something new', cwd);
110
+ assert.equal(payload.todo_count, 2);
111
+ assert.equal(payload.todos_dir_exists, true);
112
+ });
@@ -0,0 +1,14 @@
1
+ const { getAgentSkills } = require('../../lib/agents.cjs');
2
+
3
+ function run(args, ctx) {
4
+ const context = ctx || {};
5
+ const stdout = context.stdout || process.stdout;
6
+ const cwd = context.cwd || process.cwd();
7
+ const name = Array.isArray(args) ? args[0] : undefined;
8
+ if (!name) { stdout.write('{}\n'); return; }
9
+ let skills = [];
10
+ try { skills = getAgentSkills(name, cwd); } catch { skills = []; }
11
+ stdout.write(JSON.stringify(skills) + '\n');
12
+ }
13
+
14
+ module.exports = { run };
@@ -0,0 +1,42 @@
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, cleanupAll } = require('../../tests/helpers/fixture.cjs');
7
+ const subcmd = require('./agent-skills.cjs');
8
+
9
+ function _capture() {
10
+ let buf = '';
11
+ const stub = { write: (s) => { buf += s; return true; } };
12
+ return { stub, get: () => buf };
13
+ }
14
+
15
+ afterEach(cleanupAll);
16
+
17
+ test('AS-1: run([name]) prints JSON.stringify(getAgentSkills(name))', () => {
18
+ const sandbox = makeSandbox();
19
+ fs.writeFileSync(
20
+ path.join(sandbox, '.nubos-pilot', 'config.json'),
21
+ JSON.stringify({ agent_skills: { 'np-planner': ['s1', 's2'] } }),
22
+ 'utf-8',
23
+ );
24
+ const cap = _capture();
25
+ subcmd.run(['np-planner'], { cwd: sandbox, stdout: cap.stub });
26
+ const out = cap.get().trim();
27
+ assert.deepEqual(JSON.parse(out), ['s1', 's2']);
28
+ });
29
+
30
+ test('AS-2: run([]) prints {} (empty usage response)', () => {
31
+ const sandbox = makeSandbox();
32
+ const cap = _capture();
33
+ subcmd.run([], { cwd: sandbox, stdout: cap.stub });
34
+ assert.equal(cap.get().trim(), '{}');
35
+ });
36
+
37
+ test('AS-3: run([nonexistent]) prints [] (never throws)', () => {
38
+ const sandbox = makeSandbox();
39
+ const cap = _capture();
40
+ assert.doesNotThrow(() => subcmd.run(['nobody'], { cwd: sandbox, stdout: cap.stub }));
41
+ assert.equal(cap.get().trim(), '[]');
42
+ });
@@ -0,0 +1,109 @@
1
+ const fs = require('node:fs');
2
+ const path = require('node:path');
3
+
4
+ const { NubosPilotError } = require('../../lib/core.cjs');
5
+ const { getPhase } = require('../../lib/roadmap.cjs');
6
+ const { paddedPhase, phaseSlug, findPhaseDir } = require('../../lib/phase.cjs');
7
+ const { detect } = require('../../lib/runtime/index.cjs');
8
+
9
+ function _validatePhaseArg(raw) {
10
+ if (raw == null || raw === '') {
11
+ throw new NubosPilotError(
12
+ 'ai-integration-phase-invalid-arg',
13
+ 'ai-integration-phase requires a phase number argument',
14
+ { value: raw == null ? '' : String(raw) },
15
+ );
16
+ }
17
+ const s = String(raw);
18
+ if (!/^\d+(\.\d+)?$/.test(s)) {
19
+ throw new NubosPilotError(
20
+ 'ai-integration-phase-invalid-arg',
21
+ 'Invalid phase number: ' + s,
22
+ { value: s },
23
+ );
24
+ }
25
+ return s;
26
+ }
27
+
28
+ function _resolvePhaseDir(phaseArg, cwd, slug) {
29
+ const hit = findPhaseDir(phaseArg, cwd);
30
+ if (hit) return hit;
31
+ const padded = paddedPhase(phaseArg);
32
+ return path.join(path.resolve(cwd), '.nubos-pilot', 'phases', padded + '-' + slug);
33
+ }
34
+
35
+ function _buildPayload(phaseArg, cwd) {
36
+ let phase;
37
+ try {
38
+ phase = getPhase(phaseArg, cwd);
39
+ } catch (err) {
40
+ if (err && err.code === 'phase-not-found') {
41
+ throw new NubosPilotError(
42
+ 'ai-integration-phase-not-found',
43
+ 'Phase ' + phaseArg + ' not found in roadmap',
44
+ { number: phaseArg },
45
+ );
46
+ }
47
+ throw err;
48
+ }
49
+
50
+ const padded = paddedPhase(phaseArg);
51
+ const slug = phase.slug || phaseSlug(phase.name);
52
+ const phase_dir = _resolvePhaseDir(phaseArg, cwd, slug);
53
+ const ai_spec_path = path.join(phase_dir, padded + '-AI-SPEC.md');
54
+ const has_ai_spec = fs.existsSync(ai_spec_path);
55
+ const template_path = path.join(path.resolve(cwd), 'templates', 'AI-SPEC.md');
56
+ const { runtime } = detect({ cwd });
57
+
58
+ return {
59
+ _workflow: 'ai-integration-phase',
60
+ phase: phaseArg,
61
+ padded,
62
+ phase_dir,
63
+ ai_spec_path,
64
+ has_ai_spec,
65
+ template_path,
66
+ agents: {
67
+ framework_selector: 'np-framework-selector',
68
+ ai_researcher: 'np-ai-researcher',
69
+ domain_researcher: 'np-domain-researcher',
70
+ eval_planner: 'np-eval-planner',
71
+ },
72
+ runtime,
73
+ };
74
+ }
75
+
76
+ function _emitError(err, stderr) {
77
+ if (err && err.name === 'NubosPilotError') {
78
+ stderr.write(
79
+ JSON.stringify({ code: err.code, message: err.message, details: err.details }) + '\n',
80
+ );
81
+ } else {
82
+ stderr.write(String((err && err.stack) || err) + '\n');
83
+ }
84
+ }
85
+
86
+ function run(args, ctx) {
87
+ const context = ctx || {};
88
+ const cwd = context.cwd || process.cwd();
89
+ const stdout = context.stdout || process.stdout;
90
+ const stderr = context.stderr || process.stderr;
91
+ const list = Array.isArray(args) ? args : [];
92
+
93
+ if (list[0] == null || list[0] === '') {
94
+ stderr.write('Usage: np-tools.cjs init ai-integration-phase <phase>\n');
95
+ return 1;
96
+ }
97
+
98
+ try {
99
+ const phaseArg = _validatePhaseArg(list[0]);
100
+ const payload = _buildPayload(phaseArg, cwd);
101
+ stdout.write(JSON.stringify(payload, null, 2));
102
+ return 0;
103
+ } catch (err) {
104
+ _emitError(err, stderr);
105
+ return 1;
106
+ }
107
+ }
108
+
109
+ module.exports = { run, _buildPayload };