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,110 @@
1
+ const fs = require('node:fs');
2
+ const path = require('node:path');
3
+ const { findProjectRoot, NubosPilotError } = require('../../lib/core.cjs');
4
+ const { loadAgent } = require('../../lib/agents.cjs');
5
+ const { resolve: resolveAlias, MODEL_ALIAS_MAP, VALID_TIERS } = require('../../lib/model-profiles.cjs');
6
+
7
+ function _readConfig(cwd) {
8
+ let root;
9
+ try {
10
+ root = findProjectRoot(cwd || process.cwd());
11
+ } catch {
12
+ return {};
13
+ }
14
+ const configPath = path.join(root, '.nubos-pilot', 'config.json');
15
+ if (!fs.existsSync(configPath)) return {};
16
+ try {
17
+ const parsed = JSON.parse(fs.readFileSync(configPath, 'utf-8'));
18
+ return parsed && typeof parsed === 'object' ? parsed : {};
19
+ } catch {
20
+ return {};
21
+ }
22
+ }
23
+
24
+ function resolveFromConfig({ agentOrTier, profileOverride, cwd, format }) {
25
+ const config = _readConfig(cwd);
26
+
27
+ let tier;
28
+ if (VALID_TIERS.includes(agentOrTier)) {
29
+ tier = agentOrTier;
30
+ } else {
31
+ const fm = loadAgent(agentOrTier, cwd);
32
+ tier = fm.tier;
33
+ }
34
+
35
+ const profile = profileOverride || config.model_profile || 'balanced';
36
+ const alias = resolveAlias(tier, profile);
37
+
38
+ let mode;
39
+ if (profile === 'inherit') {
40
+ mode = 'inherit';
41
+ } else if (format === 'omit' || config.resolve_model_ids === 'omit') {
42
+ mode = 'omit';
43
+ } else if (format === 'id' || config.resolve_model_ids === true) {
44
+ mode = 'full-id';
45
+ } else {
46
+ mode = 'alias';
47
+ }
48
+
49
+ let resolved;
50
+ if (mode === 'omit' || mode === 'inherit') {
51
+ resolved = '';
52
+ } else if (mode === 'full-id') {
53
+ const override = config.model_overrides
54
+ && config.model_overrides.tier_map
55
+ && config.model_overrides.tier_map[alias];
56
+ resolved = override || MODEL_ALIAS_MAP[alias] || '';
57
+ } else {
58
+ resolved = alias;
59
+ }
60
+
61
+ return { tier, profile, alias, resolved, mode };
62
+ }
63
+
64
+ function run(argv) {
65
+ const args = Array.isArray(argv) ? argv.slice() : process.argv.slice(3);
66
+ if (args.length === 0 || args[0] === '--help') {
67
+ process.stderr.write(
68
+ 'Usage: np-tools.cjs resolve-model <agent|tier> [--profile P] [--raw] [--format alias|id|omit]\n',
69
+ );
70
+ return 1;
71
+ }
72
+ const agentOrTier = args.shift();
73
+ let profileOverride = null;
74
+ let format = null;
75
+ while (args.length) {
76
+ const flag = args.shift();
77
+ if (flag === '--profile') {
78
+ profileOverride = args.shift();
79
+ } else if (flag === '--format') {
80
+ format = args.shift();
81
+ } else if (flag === '--raw') {
82
+
83
+ }
84
+ }
85
+ try {
86
+ const out = resolveFromConfig({
87
+ agentOrTier,
88
+ profileOverride,
89
+ cwd: process.cwd(),
90
+ format,
91
+ });
92
+ process.stdout.write(out.resolved + '\n');
93
+ return 0;
94
+ } catch (err) {
95
+ if (err && err.name === 'NubosPilotError') {
96
+ process.stderr.write(
97
+ JSON.stringify({ code: err.code, message: err.message, details: err.details }) + '\n',
98
+ );
99
+ } else {
100
+ process.stderr.write(String((err && err.stack) || err) + '\n');
101
+ }
102
+ return 1;
103
+ }
104
+ }
105
+
106
+ module.exports = { run, resolveFromConfig };
107
+
108
+ if (require.main === module) {
109
+ process.exit(run(process.argv.slice(2)));
110
+ }
@@ -0,0 +1,200 @@
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('./resolve-model.cjs');
8
+
9
+ const _sandboxes = [];
10
+
11
+ function _sandbox(config, agents) {
12
+ const root = fs.mkdtempSync(path.join(os.tmpdir(), 'np-resolve-'));
13
+ fs.mkdirSync(path.join(root, '.nubos-pilot'), { recursive: true });
14
+ if (config !== undefined) {
15
+ fs.writeFileSync(
16
+ path.join(root, '.nubos-pilot', 'config.json'),
17
+ JSON.stringify(config),
18
+ 'utf-8',
19
+ );
20
+ }
21
+ if (agents) {
22
+ fs.mkdirSync(path.join(root, 'agents'), { recursive: true });
23
+ for (const [name, content] of Object.entries(agents)) {
24
+ fs.writeFileSync(path.join(root, 'agents', name + '.md'), content, 'utf-8');
25
+ }
26
+ }
27
+ _sandboxes.push(root);
28
+ return root;
29
+ }
30
+
31
+ function _captureStdout(fn) {
32
+ const chunks = [];
33
+ const origOut = process.stdout.write.bind(process.stdout);
34
+ const errChunks = [];
35
+ const origErr = process.stderr.write.bind(process.stderr);
36
+ process.stdout.write = (c) => { chunks.push(String(c)); return true; };
37
+ process.stderr.write = (c) => { errChunks.push(String(c)); return true; };
38
+ let rc;
39
+ try { rc = fn(); } finally {
40
+ process.stdout.write = origOut;
41
+ process.stderr.write = origErr;
42
+ }
43
+ return { stdout: chunks.join(''), stderr: errChunks.join(''), rc };
44
+ }
45
+
46
+ afterEach(() => {
47
+ while (_sandboxes.length) {
48
+ const p = _sandboxes.pop();
49
+ try { fs.rmSync(p, { recursive: true, force: true }); } catch { }
50
+ }
51
+ });
52
+
53
+ const _plannerAgent = [
54
+ '---',
55
+ 'name: np-planner',
56
+ 'description: Test planner agent for resolve-model tests.',
57
+ 'tier: opus',
58
+ 'tools: Read, Write, Bash',
59
+ 'color: green',
60
+ '---',
61
+ '',
62
+ '# Planner (test fixture)',
63
+ '',
64
+ ].join('\n');
65
+
66
+ test('RM-1: tier branch with empty config returns alias mode, default balanced profile', () => {
67
+ const cwd = _sandbox({});
68
+ const out = subcmd.resolveFromConfig({ agentOrTier: 'opus', cwd });
69
+ assert.deepEqual(out, {
70
+ tier: 'opus',
71
+ profile: 'balanced',
72
+ alias: 'opus',
73
+ resolved: 'opus',
74
+ mode: 'alias',
75
+ });
76
+ });
77
+
78
+ test('RM-2: profileOverride=budget drops opus to sonnet per matrix D-01', () => {
79
+ const cwd = _sandbox({});
80
+ const out = subcmd.resolveFromConfig({ agentOrTier: 'opus', profileOverride: 'budget', cwd });
81
+ assert.equal(out.tier, 'opus');
82
+ assert.equal(out.profile, 'budget');
83
+ assert.equal(out.alias, 'sonnet');
84
+ assert.equal(out.resolved, 'sonnet');
85
+ assert.equal(out.mode, 'alias');
86
+ });
87
+
88
+ test('RM-3: resolve_model_ids=true returns full-id from MODEL_ALIAS_MAP', () => {
89
+ const cwd = _sandbox({ resolve_model_ids: true });
90
+ const out = subcmd.resolveFromConfig({ agentOrTier: 'opus', cwd });
91
+ assert.equal(out.mode, 'full-id');
92
+ assert.equal(out.resolved, 'claude-opus-4-7');
93
+ assert.equal(out.alias, 'opus');
94
+ });
95
+
96
+ test('RM-4: resolve_model_ids="omit" returns empty string and mode=omit (Pitfall 3 string check)', () => {
97
+ const cwd = _sandbox({ resolve_model_ids: 'omit' });
98
+ const out = subcmd.resolveFromConfig({ agentOrTier: 'opus', cwd });
99
+ assert.equal(out.mode, 'omit');
100
+ assert.equal(out.resolved, '');
101
+ assert.equal(out.alias, 'opus');
102
+ });
103
+
104
+ test('RM-5: model_profile="inherit" short-circuits to mode=inherit, alias="", resolved=""', () => {
105
+ const cwd = _sandbox({ model_profile: 'inherit' });
106
+ const out = subcmd.resolveFromConfig({ agentOrTier: 'opus', cwd });
107
+ assert.equal(out.profile, 'inherit');
108
+ assert.equal(out.alias, '');
109
+ assert.equal(out.resolved, '');
110
+ assert.equal(out.mode, 'inherit');
111
+ });
112
+
113
+ test('RM-6: agent-name branch reads tier from agents/<name>.md frontmatter', () => {
114
+ const cwd = _sandbox({}, { 'np-planner': _plannerAgent });
115
+ const out = subcmd.resolveFromConfig({ agentOrTier: 'np-planner', cwd });
116
+ assert.equal(out.tier, 'opus');
117
+ assert.equal(out.profile, 'balanced');
118
+ assert.equal(out.alias, 'opus');
119
+ assert.equal(out.resolved, 'opus');
120
+ assert.equal(out.mode, 'alias');
121
+ });
122
+
123
+ test('RM-7: unknown agent name propagates NubosPilotError with code=agent-not-found', () => {
124
+ const cwd = _sandbox({});
125
+ let thrown = null;
126
+ try { subcmd.resolveFromConfig({ agentOrTier: 'nonexistent-agent', cwd }); } catch (e) { thrown = e; }
127
+ assert.ok(thrown);
128
+ assert.equal(thrown.name, 'NubosPilotError');
129
+ assert.equal(thrown.code, 'agent-not-found');
130
+ });
131
+
132
+ test('RM-8: model_overrides.tier_map beats MODEL_ALIAS_MAP in full-id mode (D-02)', () => {
133
+ const cwd = _sandbox({
134
+ resolve_model_ids: true,
135
+ model_overrides: { tier_map: { opus: 'custom-opus-id' } },
136
+ });
137
+ const out = subcmd.resolveFromConfig({ agentOrTier: 'opus', cwd });
138
+ assert.equal(out.mode, 'full-id');
139
+ assert.equal(out.resolved, 'custom-opus-id');
140
+ assert.equal(out.alias, 'opus');
141
+ });
142
+
143
+ test('RM-9: run(["opus","--profile","budget"]) prints "sonnet\\n" to stdout and returns 0', () => {
144
+ const cwd = _sandbox({});
145
+ const origCwd = process.cwd();
146
+ process.chdir(cwd);
147
+ try {
148
+ const cap = _captureStdout(() => subcmd.run(['opus', '--profile', 'budget']));
149
+ assert.equal(cap.stdout, 'sonnet\n');
150
+ assert.equal(cap.rc, 0);
151
+ } finally {
152
+ process.chdir(origCwd);
153
+ }
154
+ });
155
+
156
+ test('RM-10: run(["opus","--format","omit"]) prints empty line and returns 0', () => {
157
+ const cwd = _sandbox({});
158
+ const origCwd = process.cwd();
159
+ process.chdir(cwd);
160
+ try {
161
+ const cap = _captureStdout(() => subcmd.run(['opus', '--format', 'omit']));
162
+ assert.equal(cap.stdout, '\n');
163
+ assert.equal(cap.rc, 0);
164
+ } finally {
165
+ process.chdir(origCwd);
166
+ }
167
+ });
168
+
169
+ test('RM-11: --format=id forces full-id mode regardless of config default', () => {
170
+ const cwd = _sandbox({});
171
+ const out = subcmd.resolveFromConfig({ agentOrTier: 'opus', cwd, format: 'id' });
172
+ assert.equal(out.mode, 'full-id');
173
+ assert.equal(out.resolved, 'claude-opus-4-7');
174
+ });
175
+
176
+ test('RM-12: malformed config.json falls back to defaults without throwing', () => {
177
+ const root = fs.mkdtempSync(path.join(os.tmpdir(), 'np-resolve-'));
178
+ fs.mkdirSync(path.join(root, '.nubos-pilot'), { recursive: true });
179
+ fs.writeFileSync(path.join(root, '.nubos-pilot', 'config.json'), '{not valid json', 'utf-8');
180
+ _sandboxes.push(root);
181
+ const out = subcmd.resolveFromConfig({ agentOrTier: 'opus', cwd: root });
182
+ assert.equal(out.mode, 'alias');
183
+ assert.equal(out.resolved, 'opus');
184
+ assert.equal(out.profile, 'balanced');
185
+ });
186
+
187
+ test('RM-13: run() on NubosPilotError writes JSON error envelope to stderr and returns 1', () => {
188
+ const cwd = _sandbox({});
189
+ const origCwd = process.cwd();
190
+ process.chdir(cwd);
191
+ try {
192
+ const cap = _captureStdout(() => subcmd.run(['nonexistent-agent']));
193
+ assert.equal(cap.rc, 1);
194
+ assert.match(cap.stderr, /agent-not-found/);
195
+ const parsed = JSON.parse(cap.stderr.trim());
196
+ assert.equal(parsed.code, 'agent-not-found');
197
+ } finally {
198
+ process.chdir(origCwd);
199
+ }
200
+ });
@@ -0,0 +1,76 @@
1
+ const path = require('node:path');
2
+
3
+ const { NubosPilotError } = require('../../lib/core.cjs');
4
+ const { readState } = require('../../lib/state.cjs');
5
+ const { readCheckpoint, listCheckpoints } = require('../../lib/checkpoint.cjs');
6
+ const { TASK_ID_RE } = require('../../lib/tasks.cjs');
7
+
8
+ function _safeReadState(cwd) {
9
+ try { return readState(cwd); } catch { return null; }
10
+ }
11
+
12
+ function _validateCheckpointSchema(cp) {
13
+
14
+
15
+ if (!cp || typeof cp !== 'object') return false;
16
+ if (cp.schema_version !== 1) return false;
17
+ if (typeof cp.task_id !== 'string' || !TASK_ID_RE.test(cp.task_id)) return false;
18
+ return true;
19
+ }
20
+
21
+ function run(_args, ctx) {
22
+ const context = ctx || {};
23
+ const cwd = context.cwd || process.cwd();
24
+ const stdout = context.stdout || process.stdout;
25
+
26
+ const state = _safeReadState(cwd);
27
+ const currentTask = state && state.frontmatter ? state.frontmatter.current_task : null;
28
+ const cpFiles = listCheckpoints(cwd);
29
+
30
+ let payload;
31
+ if (currentTask && cpFiles.length > 0) {
32
+ const cp = readCheckpoint(currentTask, cwd);
33
+ if (cp && _validateCheckpointSchema(cp) && cp.status !== 'done') {
34
+ payload = {
35
+ _workflow: 'resume-work',
36
+ status: 'resume',
37
+ task_id: currentTask,
38
+ checkpoint: cp,
39
+ };
40
+ } else if (cp && !_validateCheckpointSchema(cp)) {
41
+ throw new NubosPilotError(
42
+ 'checkpoint-schema-mismatch',
43
+ 'Checkpoint file schema invalid for task ' + currentTask,
44
+ { task: currentTask },
45
+ );
46
+ } else {
47
+
48
+ const orphanIds = cpFiles.map((f) => path.basename(f, '.json'));
49
+ payload = {
50
+ _workflow: 'resume-work',
51
+ status: 'orphan',
52
+ checkpoint_ids: orphanIds,
53
+ current_task: currentTask,
54
+ };
55
+ }
56
+ } else if (cpFiles.length > 0) {
57
+ const orphanIds = cpFiles.map((f) => path.basename(f, '.json'));
58
+ payload = {
59
+ _workflow: 'resume-work',
60
+ status: 'orphan',
61
+ checkpoint_ids: orphanIds,
62
+ current_task: currentTask,
63
+ };
64
+ } else {
65
+ payload = {
66
+ _workflow: 'resume-work',
67
+ status: 'clean',
68
+ message: 'no active work',
69
+ };
70
+ }
71
+
72
+ stdout.write(JSON.stringify(payload));
73
+ return payload;
74
+ }
75
+
76
+ module.exports = { run };
@@ -0,0 +1,91 @@
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
+
7
+ const subcmd = require('./resume-work.cjs');
8
+ const { startTask } = require('../../lib/checkpoint.cjs');
9
+
10
+ const _roots = [];
11
+
12
+ function makeRoot(currentTask) {
13
+ const root = fs.mkdtempSync(path.join(os.tmpdir(), 'np-rw-'));
14
+ fs.mkdirSync(path.join(root, '.nubos-pilot'), { recursive: true });
15
+ const ct = currentTask == null ? 'null' : currentTask;
16
+ fs.writeFileSync(path.join(root, '.nubos-pilot', 'STATE.md'), `---
17
+ schema_version: 2
18
+ milestone: m1
19
+ milestone_name: m1
20
+ current_phase: 6
21
+ current_plan: "06-01"
22
+ current_task: ${ct}
23
+ last_updated: "2026-04-15T00:00:00Z"
24
+ progress:
25
+ total_phases: 0
26
+ completed_phases: 0
27
+ total_plans: 0
28
+ completed_plans: 0
29
+ percent: 0
30
+ session:
31
+ stopped_at: null
32
+ resume_file: null
33
+ last_activity: null
34
+ ---
35
+
36
+ # State
37
+ `, 'utf-8');
38
+ _roots.push(root);
39
+ return root;
40
+ }
41
+
42
+ function _capture() { let b = ''; return { stub: { write: (s) => { b += s; return true; } }, get: () => b }; }
43
+
44
+ after(() => {
45
+ while (_roots.length) {
46
+ const r = _roots.pop();
47
+ try { fs.rmSync(r, { recursive: true, force: true }); } catch {}
48
+ }
49
+ });
50
+
51
+ test('RW-1: clean when no state, no checkpoints', () => {
52
+ const root = makeRoot(null);
53
+ const cap = _capture();
54
+ const p = subcmd.run([], { cwd: root, stdout: cap.stub });
55
+ assert.equal(p.status, 'clean');
56
+ });
57
+
58
+ test('RW-2: resume when current_task matches an in-progress checkpoint', () => {
59
+ const root = makeRoot('06-01-T01');
60
+ startTask({ id: '06-01-T01', phase: 6, plan: '06-01', wave: 1 }, root);
61
+ const cap = _capture();
62
+ const p = subcmd.run([], { cwd: root, stdout: cap.stub });
63
+ assert.equal(p.status, 'resume');
64
+ assert.equal(p.task_id, '06-01-T01');
65
+ assert.equal(p.checkpoint.status, 'in-progress');
66
+ });
67
+
68
+ test('RW-3: orphan when checkpoint files exist but no matching STATE.current_task', () => {
69
+ const root = makeRoot(null);
70
+ startTask({ id: '06-01-T05', phase: 6, plan: '06-01', wave: 1 }, root);
71
+
72
+ const statePath = path.join(root, '.nubos-pilot', 'STATE.md');
73
+ const body = fs.readFileSync(statePath, 'utf-8').replace(/current_task:.*/, 'current_task: null');
74
+ fs.writeFileSync(statePath, body, 'utf-8');
75
+ const cap = _capture();
76
+ const p = subcmd.run([], { cwd: root, stdout: cap.stub });
77
+ assert.equal(p.status, 'orphan');
78
+ assert.ok(p.checkpoint_ids.includes('06-01-T05'));
79
+ });
80
+
81
+ test('RW-4: malformed checkpoint → checkpoint-schema-mismatch (T-06-12)', () => {
82
+ const root = makeRoot('06-01-T09');
83
+ const cpDir = path.join(root, '.nubos-pilot', 'checkpoints');
84
+ fs.mkdirSync(cpDir, { recursive: true });
85
+ fs.writeFileSync(path.join(cpDir, '06-01-T09.json'), JSON.stringify({ schema_version: 99, task_id: '06-01-T09' }), 'utf-8');
86
+ const cap = _capture();
87
+ assert.throws(
88
+ () => subcmd.run([], { cwd: root, stdout: cap.stub }),
89
+ (err) => err && err.code === 'checkpoint-schema-mismatch',
90
+ );
91
+ });
@@ -0,0 +1,48 @@
1
+ const fs = require('node:fs');
2
+ const path = require('node:path');
3
+
4
+ const { NubosPilotError, projectStateDir } = require('../../lib/core.cjs');
5
+ const { TASK_ID_RE, setTaskStatus } = require('../../lib/tasks.cjs');
6
+
7
+ function _findPlanDirForTask(taskId, cwd) {
8
+ const phasesRoot = path.join(projectStateDir(cwd), 'phases');
9
+ let entries;
10
+ try {
11
+ entries = fs.readdirSync(phasesRoot, { withFileTypes: true });
12
+ } catch (err) {
13
+ if (err && err.code === 'ENOENT') return null;
14
+ throw err;
15
+ }
16
+ const padded = taskId.slice(0, 2);
17
+ for (const e of entries) {
18
+ if (!e.isDirectory()) continue;
19
+ if (!(e.name === padded || e.name.startsWith(padded + '-'))) continue;
20
+ const candidate = path.join(phasesRoot, e.name, 'tasks', taskId + '.md');
21
+ if (fs.existsSync(candidate)) return path.join(phasesRoot, e.name);
22
+ }
23
+ return null;
24
+ }
25
+
26
+ function run(args, ctx) {
27
+ const context = ctx || {};
28
+ const cwd = context.cwd || process.cwd();
29
+ const stdout = context.stdout || process.stdout;
30
+ const list = Array.isArray(args) ? args : [];
31
+ const taskId = list[0];
32
+ if (!taskId) {
33
+ throw new NubosPilotError('skip-missing-task-id', 'skip requires a task id', {});
34
+ }
35
+ if (!TASK_ID_RE.test(taskId)) {
36
+ throw new NubosPilotError('skip-invalid-task-id', 'Invalid task id: ' + taskId, { taskId });
37
+ }
38
+ const planDir = _findPlanDirForTask(taskId, cwd);
39
+ if (!planDir) {
40
+ throw new NubosPilotError('task-not-found', 'No task file found for id ' + taskId, { taskId });
41
+ }
42
+ setTaskStatus(taskId, 'skipped', planDir);
43
+ const payload = { ok: true, task_id: taskId, status: 'skipped' };
44
+ stdout.write(JSON.stringify(payload));
45
+ return payload;
46
+ }
47
+
48
+ module.exports = { run };
@@ -0,0 +1,66 @@
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
+
7
+ const subcmd = require('./skip.cjs');
8
+
9
+ const _roots = [];
10
+
11
+ function makeRoot(taskId) {
12
+ const root = fs.mkdtempSync(path.join(os.tmpdir(), 'np-skip-'));
13
+ fs.mkdirSync(path.join(root, '.nubos-pilot'), { recursive: true });
14
+ const phaseDir = path.join(root, '.nubos-pilot', 'phases', '06-demo');
15
+ const tasksDir = path.join(phaseDir, 'tasks');
16
+ fs.mkdirSync(tasksDir, { recursive: true });
17
+ fs.writeFileSync(path.join(tasksDir, taskId + '.md'), [
18
+ '---', `id: ${taskId}`, 'phase: 6', 'plan: "06-01"', 'type: auto',
19
+ 'status: pending', 'tier: sonnet', 'owner: np-executor', 'wave: 1',
20
+ 'depends_on: []', 'files_modified: []', 'autonomous: true',
21
+ 'must_haves:', ' truths: []', '---', '', '# T',
22
+ ].join('\n'), 'utf-8');
23
+ _roots.push(root);
24
+ return root;
25
+ }
26
+
27
+ function _capture() { let b = ''; return { stub: { write: (s) => { b += s; } }, get: () => b }; }
28
+
29
+ after(() => {
30
+ while (_roots.length) {
31
+ const r = _roots.pop();
32
+ try { fs.rmSync(r, { recursive: true, force: true }); } catch {}
33
+ }
34
+ });
35
+
36
+ test('SK-1: skip missing id', () => {
37
+ assert.throws(
38
+ () => subcmd.run([], { cwd: process.cwd(), stdout: _capture().stub }),
39
+ (err) => err && err.code === 'skip-missing-task-id',
40
+ );
41
+ });
42
+
43
+ test('SK-2: skip invalid id', () => {
44
+ assert.throws(
45
+ () => subcmd.run(['nope'], { cwd: process.cwd(), stdout: _capture().stub }),
46
+ (err) => err && err.code === 'skip-invalid-task-id',
47
+ );
48
+ });
49
+
50
+ test('SK-3: skip flips status to skipped', () => {
51
+ const root = makeRoot('06-01-T01');
52
+ const cap = _capture();
53
+ subcmd.run(['06-01-T01'], { cwd: root, stdout: cap.stub });
54
+ const payload = JSON.parse(cap.get());
55
+ assert.equal(payload.status, 'skipped');
56
+ const tf = path.join(root, '.nubos-pilot', 'phases', '06-demo', 'tasks', '06-01-T01.md');
57
+ assert.match(fs.readFileSync(tf, 'utf-8'), /^status: skipped$/m);
58
+ });
59
+
60
+ test('SK-4: skip unknown task → task-not-found', () => {
61
+ const root = makeRoot('06-01-T01');
62
+ assert.throws(
63
+ () => subcmd.run(['06-01-T99'], { cwd: root, stdout: _capture().stub }),
64
+ (err) => err && err.code === 'task-not-found',
65
+ );
66
+ });
@@ -0,0 +1,34 @@
1
+ const { phaseSlug } = require('../../lib/phase.cjs');
2
+
3
+ const MAX_SLUG_LENGTH = 128;
4
+
5
+ function _usage() {
6
+ return 'Usage:\n np-tools.cjs generate-slug "<text>" [--raw]';
7
+ }
8
+
9
+ function run(argv, ctx) {
10
+ const context = ctx || {};
11
+ const stdout = context.stdout || process.stdout;
12
+ const stderr = context.stderr || process.stderr;
13
+ const args = Array.isArray(argv) ? argv.slice() : [];
14
+ if (args.length === 0) {
15
+ stderr.write(_usage() + '\n');
16
+ return 1;
17
+ }
18
+ const raw = args.includes('--raw');
19
+ const text = args.find((a) => !String(a).startsWith('--'));
20
+ if (text == null || String(text).length === 0) {
21
+ stderr.write(_usage() + '\n');
22
+ return 1;
23
+ }
24
+ const slug = phaseSlug(String(text)).slice(0, MAX_SLUG_LENGTH);
25
+ if (raw) stdout.write(slug);
26
+ else stdout.write(slug + '\n');
27
+ return 0;
28
+ }
29
+
30
+ module.exports = { run };
31
+
32
+ if (require.main === module) {
33
+ process.exit(run(process.argv.slice(2)));
34
+ }
@@ -0,0 +1,46 @@
1
+ const { test } = require('node:test');
2
+ const assert = require('node:assert/strict');
3
+ const { Writable } = require('node:stream');
4
+
5
+ const slugCli = require('./slug.cjs');
6
+
7
+ function makeSink() {
8
+ const chunks = [];
9
+ const w = new Writable({
10
+ write(chunk, _enc, cb) { chunks.push(chunk); cb(); },
11
+ });
12
+ w.toString = () => Buffer.concat(chunks.map((c) => Buffer.isBuffer(c) ? c : Buffer.from(String(c)))).toString('utf-8');
13
+ return w;
14
+ }
15
+
16
+ test('SLUG-1: happy path slugifies a phrase', () => {
17
+ const stdout = makeSink();
18
+ const stderr = makeSink();
19
+ const code = slugCli.run(['Hello World Test', '--raw'], { stdout, stderr });
20
+ assert.equal(code, 0);
21
+ assert.equal(stdout.toString(), 'hello-world-test');
22
+ });
23
+
24
+ test('SLUG-2: missing text prints usage', () => {
25
+ const stdout = makeSink();
26
+ const stderr = makeSink();
27
+ const code = slugCli.run([], { stdout, stderr });
28
+ assert.equal(code, 1);
29
+ assert.match(stderr.toString(), /Usage:/);
30
+ });
31
+
32
+ test('SLUG-3: non-raw output has trailing newline', () => {
33
+ const stdout = makeSink();
34
+ const stderr = makeSink();
35
+ const code = slugCli.run(['Fix deploy key auth'], { stdout, stderr });
36
+ assert.equal(code, 0);
37
+ assert.equal(stdout.toString(), 'fix-deploy-key-auth\n');
38
+ });
39
+
40
+ test('SLUG-4: collapses repeated separators + strips leading/trailing', () => {
41
+ const stdout = makeSink();
42
+ const stderr = makeSink();
43
+ const code = slugCli.run([' --FOO BAR!!! ', '--raw'], { stdout, stderr });
44
+ assert.equal(code, 0);
45
+ assert.equal(stdout.toString(), 'foo-bar');
46
+ });
@@ -0,0 +1,16 @@
1
+ const { readState } = require('../../lib/state.cjs');
2
+
3
+ function run(_args, cwd) {
4
+ const useCwd = cwd || process.cwd();
5
+ try {
6
+ const s = readState(useCwd);
7
+ return s.frontmatter;
8
+ } catch (err) {
9
+
10
+ const code = err && err.code ? String(err.code) : 'state-not-found';
11
+ const message = err && err.message ? err.message : 'STATE.md not readable';
12
+ return { error: { code, message, details: err && err.details ? err.details : null } };
13
+ }
14
+ }
15
+
16
+ module.exports = { run };