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
package/bin/install.js ADDED
@@ -0,0 +1,500 @@
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+
4
+ const fs = require('node:fs');
5
+ const path = require('node:path');
6
+ const os = require('node:os');
7
+
8
+ const { atomicWriteFileSync, withFileLock, NubosPilotError } = require('../lib/core.cjs');
9
+ const { askUser: defaultAskUser } = require('../lib/askuser.cjs');
10
+ const manifestMod = require('../lib/install/manifest.cjs');
11
+ const stagingMod = require('../lib/install/staging.cjs');
12
+ const managedBlockMod = require('../lib/install/managed-block.cjs');
13
+ const agentsMdMod = require('../lib/install/agents-md.cjs');
14
+ const codexTomlMod = require('../lib/install/codex-toml.cjs');
15
+ const runtimeDetectMod = require('../lib/install/runtime-detect.cjs');
16
+ const backupMod = require('../lib/install/backup.cjs');
17
+
18
+ const cyan = '\x1b[36m', green = '\x1b[32m', yellow = '\x1b[33m',
19
+ red = '\x1b[31m', dim = '\x1b[2m', reset = '\x1b[0m';
20
+
21
+ const PAYLOAD_SUBPATH = path.join('.claude', 'nubos-pilot');
22
+ const STATE_SUBPATH = '.nubos-pilot';
23
+ const SOURCE_PAYLOAD_DIR = path.join(__dirname, '..', 'templates', 'claude', 'payload');
24
+ const OPENCODE_SUBPATH = path.join('.opencode', 'nubos-pilot');
25
+ const OPENCODE_MANIFEST_PREFIX = '.opencode/nubos-pilot/';
26
+ const SOURCE_OPENCODE_DIR = path.join(__dirname, '..', 'templates', 'opencode', 'payload');
27
+ const OPENCODE_JSON_TEMPLATE = path.join(__dirname, '..', 'templates', 'opencode', 'opencode.json');
28
+
29
+ function _autoAskUser(spec) {
30
+ return Promise.resolve({
31
+ value: spec && spec.default !== undefined ? spec.default : null,
32
+ source: 'auto',
33
+ });
34
+ }
35
+
36
+ const MANAGED_BLOCK_INNER =
37
+ 'This project uses [nubos-pilot](https://github.com/nubos/nubos-pilot)'
38
+ + ' for planning and execution.\n\nRun `npx nubos-pilot doctor`'
39
+ + ' to check install integrity.';
40
+
41
+ const VALID_AGENTS = ['claude', 'codex', 'gemini', 'opencode'];
42
+ const VALID_SCOPES = ['local', 'global'];
43
+
44
+ function parseInstallFlags(args) {
45
+ const flags = { agent: null, scope: null, mcp: false, yes: false };
46
+ const rest = [];
47
+ for (let i = 0; i < args.length; i++) {
48
+ const a = args[i];
49
+ if (a === '--agent' || a === '-a') { flags.agent = args[++i] || null; continue; }
50
+ if (a.startsWith('--agent=')) { flags.agent = a.slice('--agent='.length); continue; }
51
+ if (a === '--scope' || a === '-s') { flags.scope = args[++i] || null; continue; }
52
+ if (a.startsWith('--scope=')) { flags.scope = a.slice('--scope='.length); continue; }
53
+ if (a === '--mcp') { flags.mcp = true; continue; }
54
+ if (a === '--yes' || a === '-y') { flags.yes = true; continue; }
55
+ rest.push(a);
56
+ }
57
+ if (flags.agent !== null && !VALID_AGENTS.includes(flags.agent)) {
58
+ throw new NubosPilotError('invalid-flag',
59
+ '--agent must be one of: ' + VALID_AGENTS.join(', '),
60
+ { flag: '--agent', got: flags.agent });
61
+ }
62
+ if (flags.scope !== null && !VALID_SCOPES.includes(flags.scope)) {
63
+ throw new NubosPilotError('invalid-flag',
64
+ '--scope must be one of: ' + VALID_SCOPES.join(', '),
65
+ { flag: '--scope', got: flags.scope });
66
+ }
67
+ return { flags, rest };
68
+ }
69
+
70
+ function _payloadDirFor(projectRoot, scope) {
71
+ if (scope === 'global') return path.join(os.homedir(), '.claude', 'nubos-pilot');
72
+ return path.join(projectRoot, PAYLOAD_SUBPATH);
73
+ }
74
+
75
+ function _opencodePayloadDirFor(projectRoot, scope) {
76
+ if (scope === 'global') return path.join(os.homedir(), '.config', 'opencode', 'nubos-pilot');
77
+ return path.join(projectRoot, OPENCODE_SUBPATH);
78
+ }
79
+
80
+ function _opencodeManifestPrefix(scope) {
81
+ return scope === 'global'
82
+ ? '~/.config/opencode/nubos-pilot/'
83
+ : OPENCODE_MANIFEST_PREFIX;
84
+ }
85
+
86
+ function _stateDirFor(projectRoot) {
87
+ return path.join(projectRoot, STATE_SUBPATH);
88
+ }
89
+
90
+ function _readExistingScope(projectRoot) {
91
+ const cfgPath = path.join(_stateDirFor(projectRoot), 'config.json');
92
+ if (!fs.existsSync(cfgPath)) return null;
93
+ try {
94
+ const cfg = JSON.parse(fs.readFileSync(cfgPath, 'utf-8'));
95
+ return cfg && cfg.scope ? cfg.scope : null;
96
+ } catch { return null; }
97
+ }
98
+
99
+ function detectMode(projectRoot, scope) {
100
+ const s = scope || _readExistingScope(projectRoot) || 'local';
101
+ const payloadDir = _payloadDirFor(projectRoot, s);
102
+ return manifestMod.readManifest(payloadDir) ? 're-install' : 'init';
103
+ }
104
+
105
+ function _copyTree(src, dst) {
106
+ let entries;
107
+ try {
108
+ entries = fs.readdirSync(src, { withFileTypes: true });
109
+ } catch (err) {
110
+ if (err && err.code === 'ENOENT') return;
111
+ throw err;
112
+ }
113
+ fs.mkdirSync(dst, { recursive: true });
114
+ for (const e of entries) {
115
+ const from = path.join(src, e.name);
116
+ const to = path.join(dst, e.name);
117
+ if (e.isDirectory()) {
118
+ _copyTree(from, to);
119
+ } else if (e.isFile()) {
120
+ fs.copyFileSync(from, to);
121
+ }
122
+
123
+ }
124
+ }
125
+
126
+ async function _runInitQuestions(detectedRuntime, askUser, flags) {
127
+ const f = flags || {};
128
+ const runtime = f.agent || (await askUser({ type: 'select', question: 'Welche Runtime nutzt du?',
129
+ options: VALID_AGENTS, default: detectedRuntime || 'claude' })).value;
130
+ const scope = f.scope || (await askUser({ type: 'select', question: 'Installation scope?',
131
+ options: VALID_SCOPES, default: 'local' })).value;
132
+ const model_profile = (await askUser({ type: 'select', question: 'Model-Profile?',
133
+ options: ['inherit', 'quality', 'balanced', 'budget'], default: 'inherit' })).value;
134
+ const commit_docs = (await askUser({ type: 'confirm', question: 'Commit documentation artefacts?', default: true })).value;
135
+ const branching_strategy = (await askUser({ type: 'select', question: 'Branching strategy?',
136
+ options: ['single-branch', 'phase-branches', 'milestone-branches'], default: 'single-branch' })).value;
137
+ const phase_branch_template = (await askUser({ type: 'input', question: 'Phase branch template?', default: 'phase/{number}-{slug}' })).value;
138
+ const milestone_branch_template = (await askUser({ type: 'input', question: 'Milestone branch template?', default: 'milestone/{name}' })).value;
139
+ const parallelization = (await askUser({ type: 'confirm', question: 'Enable parallelization?', default: true })).value;
140
+ const research = (await askUser({ type: 'confirm', question: 'Enable research step?', default: false })).value;
141
+ const plan_checker = (await askUser({ type: 'confirm', question: 'Enable plan_checker?', default: true })).value;
142
+ const verifier = (await askUser({ type: 'confirm', question: 'Enable verifier?', default: true })).value;
143
+ const response_language = (await askUser({ type: 'input', question: 'Response language (ISO-639 code)?', default: 'en' })).value;
144
+ return { runtime, scope, mcp: !!f.mcp, model_profile, commit_docs, branching_strategy, phase_branch_template,
145
+ milestone_branch_template, parallelization, research, plan_checker, verifier, response_language };
146
+ }
147
+
148
+ function _repairCodexConfig() {
149
+ const codexConfig = path.join(os.homedir(), '.codex', 'config.toml');
150
+ if (!fs.existsSync(codexConfig)) return false;
151
+ let raw;
152
+ try { raw = fs.readFileSync(codexConfig, 'utf-8'); } catch { return false; }
153
+ if (!codexTomlMod.hasTrappedFeatures(raw)) return false;
154
+ const repaired = codexTomlMod.repairTrappedFeatures(raw);
155
+ atomicWriteFileSync(codexConfig, repaired);
156
+ console.error(green + ' [codex] trapped [features] repariert' + reset);
157
+ return true;
158
+ }
159
+
160
+ function _rewriteManagedMarkdown(projectRoot) {
161
+ const claudePath = path.join(projectRoot, 'CLAUDE.md');
162
+ const agentsPath = path.join(projectRoot, 'AGENTS.md');
163
+ const geminiPath = path.join(projectRoot, 'GEMINI.md');
164
+ const claudeExists = fs.existsSync(claudePath);
165
+ if (claudeExists) {
166
+ const content = fs.readFileSync(claudePath, 'utf-8');
167
+ const next = managedBlockMod.rewriteBlock(content, MANAGED_BLOCK_INNER);
168
+ atomicWriteFileSync(claudePath, next);
169
+ }
170
+
171
+ let agentsBase;
172
+ if (fs.existsSync(agentsPath)) {
173
+ agentsBase = fs.readFileSync(agentsPath, 'utf-8');
174
+ } else if (claudeExists) {
175
+ agentsBase = agentsMdMod.generateAgentsMd(fs.readFileSync(claudePath, 'utf-8'), 'codex');
176
+ } else {
177
+ return;
178
+ }
179
+ const agentsNext = managedBlockMod.rewriteBlock(agentsBase, MANAGED_BLOCK_INNER);
180
+ atomicWriteFileSync(agentsPath, agentsNext);
181
+
182
+ let geminiBase;
183
+ if (fs.existsSync(geminiPath)) {
184
+ geminiBase = fs.readFileSync(geminiPath, 'utf-8');
185
+ } else if (claudeExists) {
186
+ geminiBase = agentsMdMod.generateAgentsMd(fs.readFileSync(claudePath, 'utf-8'), 'gemini');
187
+ } else {
188
+ return;
189
+ }
190
+ const geminiNext = managedBlockMod.rewriteBlock(geminiBase, MANAGED_BLOCK_INNER);
191
+ atomicWriteFileSync(geminiPath, geminiNext);
192
+ }
193
+
194
+ async function runInstall(opts) {
195
+ const o = opts || {};
196
+ const projectRoot = o.projectRoot || o.cwd || process.cwd();
197
+ const flags = o.flags || {};
198
+ const mode = o.mode || detectMode(projectRoot, flags.scope);
199
+ const dryRun = !!o.dryRun;
200
+ const askUser = flags.yes ? _autoAskUser : (o.askUser || defaultAskUser);
201
+ const sourceDir = o.sourceDir || SOURCE_PAYLOAD_DIR;
202
+ const stateDir = _stateDirFor(projectRoot);
203
+ fs.mkdirSync(stateDir, { recursive: true });
204
+ return withFileLock(path.join(stateDir, '.install.lock'),
205
+ () => _runInstallLocked({ projectRoot, mode, dryRun, askUser, sourceDir, stateDir, flags }),
206
+ { timeoutMs: 60000 });
207
+ }
208
+
209
+ async function _runInstallLocked(ctx) {
210
+ const { projectRoot, mode, dryRun, askUser, sourceDir, stateDir, flags } = ctx;
211
+ console.error(cyan + '→ nubos-pilot install (mode=' + mode + ')' + reset);
212
+
213
+ const preliminaryScope = (flags && flags.scope) || _readExistingScope(projectRoot) || 'local';
214
+ const preliminaryBase = preliminaryScope === 'global' ? os.homedir() : projectRoot;
215
+ stagingMod.cleanStaleStaging(preliminaryBase);
216
+
217
+ let initConfig = null;
218
+ if (mode === 'init') {
219
+ const det = runtimeDetectMod.detectRuntime({ cwd: projectRoot });
220
+ const config = await _runInitQuestions(det && det.runtime, askUser, flags);
221
+ if (flags && flags.agent) {
222
+ config.runtime = flags.agent;
223
+ config.runtime_source = 'flag';
224
+ } else {
225
+ config.runtime = det && det.runtime ? det.runtime : config.runtime || 'codex';
226
+ config.runtime_source = det && det.source ? det.source : 'asked';
227
+ }
228
+ const configPath = path.join(stateDir, 'config.json');
229
+ if (!dryRun) atomicWriteFileSync(configPath, JSON.stringify(config, null, 2));
230
+ else console.error(dim + 'DRY-RUN: würde schreiben ' + configPath + reset);
231
+ initConfig = config;
232
+ }
233
+
234
+ const resolvedScope = (initConfig && initConfig.scope) || preliminaryScope;
235
+ const payloadBase = resolvedScope === 'global' ? os.homedir() : projectRoot;
236
+ const payloadDir = _payloadDirFor(projectRoot, resolvedScope);
237
+ const oldManifest = manifestMod.readManifest(payloadDir);
238
+ const tmp = stagingMod.stageDir(payloadBase);
239
+ _copyTree(sourceDir, tmp);
240
+ let pkgVersion = '0.0.0';
241
+ try { pkgVersion = String(require('../package.json').version || '0.0.0'); } catch {}
242
+ const newManifest = manifestMod.buildManifest(tmp, pkgVersion);
243
+
244
+ const opencodeTarget = _opencodePayloadDirFor(projectRoot, resolvedScope);
245
+ const opencodeManifestPrefix = _opencodeManifestPrefix(resolvedScope);
246
+ const opencodeTmp = path.join(stateDir, '.opencode.tmp');
247
+ try { fs.rmSync(opencodeTmp, { recursive: true, force: true }); } catch {}
248
+ try {
249
+ let opencodeManifest = null;
250
+ if (fs.existsSync(SOURCE_OPENCODE_DIR)) {
251
+ _copyTree(SOURCE_OPENCODE_DIR, opencodeTmp);
252
+ opencodeManifest = manifestMod.buildManifest(opencodeTmp, pkgVersion);
253
+ for (const rel of Object.keys(opencodeManifest.files)) {
254
+ if (rel.includes('..') || path.isAbsolute(rel)) {
255
+ throw new NubosPilotError('manifest-path-traversal',
256
+ 'Opencode payload contains suspicious path', { rel });
257
+ }
258
+ newManifest.files[opencodeManifestPrefix + rel] = opencodeManifest.files[rel];
259
+ }
260
+ }
261
+ const diff = manifestMod.diffManifests(oldManifest, newManifest);
262
+
263
+ const backupLog = [];
264
+ for (const rel of diff.changed) {
265
+ const existing = path.join(payloadDir, rel);
266
+ if (!fs.existsSync(existing)) continue;
267
+ try {
268
+ const existingHash = manifestMod.fileHashSync(existing);
269
+ const oldHash = (oldManifest && oldManifest.files && oldManifest.files[rel]) || null;
270
+ if (oldHash && existingHash !== oldHash) {
271
+ if (!dryRun) {
272
+ const backedUp = backupMod.backupFile(existing);
273
+ backupLog.push({ rel, backedUp });
274
+ console.error(yellow + ' [conflict] ' + rel + ' → ' + path.basename(backedUp) + reset);
275
+ } else {
276
+ console.error(dim + 'DRY-RUN: würde sichern ' + rel + reset);
277
+ }
278
+ }
279
+ } catch {}
280
+ }
281
+
282
+ if (dryRun) {
283
+ const summary = { mode, dryRun: true,
284
+ scope: resolvedScope,
285
+ wouldWrite: Object.keys(newManifest.files).length,
286
+ wouldBackup: backupLog.length, wouldDelete: diff.stale.length,
287
+ wouldWriteGemini: true,
288
+ wouldWriteOpencodeJson: !fs.existsSync(path.join(projectRoot, 'opencode.json')),
289
+ stale: diff.stale, changed: diff.changed, added: diff.added };
290
+ process.stdout.write(JSON.stringify(summary, null, 2) + '\n');
291
+ try { stagingMod.cleanStaleStaging(payloadBase); } catch {}
292
+ try { fs.rmSync(opencodeTmp, { recursive: true, force: true }); } catch {}
293
+ return summary;
294
+ }
295
+
296
+ if (fs.existsSync(payloadDir) && fs.lstatSync(payloadDir).isSymbolicLink()) {
297
+ try { stagingMod.cleanStaleStaging(payloadBase); } catch {}
298
+ throw new NubosPilotError('target-is-symlink',
299
+ 'Refusing to swap into a symlink target: ' + payloadDir, { payloadDir });
300
+ }
301
+
302
+ stagingMod.finalizeSwap(payloadBase);
303
+ for (const rel of diff.stale) {
304
+ try { fs.unlinkSync(path.join(payloadDir, rel)); } catch {}
305
+ }
306
+
307
+ if (opencodeManifest) {
308
+ const opencodeBak = path.join(stateDir, '.opencode.bak');
309
+ try { fs.rmSync(opencodeBak, { recursive: true, force: true }); } catch {}
310
+ if (fs.existsSync(opencodeTarget)) {
311
+ if (fs.lstatSync(opencodeTarget).isSymbolicLink()) {
312
+ throw new NubosPilotError('target-is-symlink',
313
+ 'Refusing to swap into a symlink target: ' + opencodeTarget,
314
+ { payloadDir: opencodeTarget });
315
+ }
316
+ fs.renameSync(opencodeTarget, opencodeBak);
317
+ }
318
+ const opencodeParent = path.dirname(opencodeTarget);
319
+ if (fs.existsSync(opencodeParent) && fs.lstatSync(opencodeParent).isSymbolicLink()) {
320
+ throw new NubosPilotError('target-is-symlink',
321
+ 'Refusing to install into a symlinked parent: ' + opencodeParent,
322
+ { payloadDir: opencodeParent });
323
+ }
324
+ fs.mkdirSync(opencodeParent, { recursive: true });
325
+ fs.renameSync(opencodeTmp, opencodeTarget);
326
+ try { fs.rmSync(opencodeBak, { recursive: true, force: true }); } catch {}
327
+ const opencodeBase = resolvedScope === 'global' ? os.homedir() : projectRoot;
328
+ for (const rel of diff.stale) {
329
+ if (rel.startsWith(opencodeManifestPrefix)) {
330
+ const relFs = rel.startsWith('~/')
331
+ ? path.join(os.homedir(), rel.slice(2))
332
+ : path.join(opencodeBase, rel);
333
+ try { fs.unlinkSync(relFs); } catch {}
334
+ }
335
+ }
336
+ }
337
+
338
+ _rewriteManagedMarkdown(projectRoot);
339
+
340
+ if (initConfig && initConfig.mcp && !dryRun) {
341
+ try {
342
+ const mcpWriter = require('../lib/install/mcp-writer.cjs');
343
+ const result = mcpWriter.writeMcpConfig({
344
+ runtime: initConfig.runtime,
345
+ scope: initConfig.scope,
346
+ projectRoot,
347
+ });
348
+ console.error(green + ' [mcp] nubos MCP configured → ' + result.path + reset);
349
+ } catch (err) {
350
+ console.error(yellow + ' [mcp] skipped: ' + (err && err.message) + reset);
351
+ }
352
+ }
353
+
354
+ const projectOpencodeJson = path.join(projectRoot, 'opencode.json');
355
+ if (!fs.existsSync(projectOpencodeJson) && fs.existsSync(OPENCODE_JSON_TEMPLATE)) {
356
+ const template = fs.readFileSync(OPENCODE_JSON_TEMPLATE, 'utf-8');
357
+ atomicWriteFileSync(projectOpencodeJson, template);
358
+ }
359
+
360
+ try { _repairCodexConfig(); } catch (err) {
361
+ console.error(yellow + ' [codex] repair skipped: ' + (err && err.message) + reset);
362
+ }
363
+ manifestMod.writeManifest(payloadDir, newManifest);
364
+ console.error(green + '✓ Installation abgeschlossen' + reset);
365
+ return { mode, dryRun: false, written: Object.keys(newManifest.files).length,
366
+ backedUp: backupLog.length, deleted: diff.stale.length };
367
+ } finally {
368
+ try { fs.rmSync(opencodeTmp, { recursive: true, force: true }); } catch {}
369
+ }
370
+ }
371
+
372
+ async function runUninstall(opts) {
373
+ const options = opts || {};
374
+ const cwd = options.cwd || process.cwd();
375
+ const projectRoot = options.projectRoot || cwd;
376
+ const stateDir = _stateDirFor(projectRoot);
377
+ fs.mkdirSync(stateDir, { recursive: true });
378
+ const lockPath = path.join(stateDir, '.install.lock');
379
+ return withFileLock(lockPath, () => _runUninstallLocked(projectRoot),
380
+ { timeoutMs: 60000 });
381
+ }
382
+
383
+ function _runUninstallLocked(projectRoot) {
384
+ const scope = _readExistingScope(projectRoot) || 'local';
385
+ const payloadDir = _payloadDirFor(projectRoot, scope);
386
+ const manifest = manifestMod.readManifest(payloadDir);
387
+ if (!manifest) {
388
+ console.error(dim + 'Keine Installation gefunden' + reset);
389
+ return { uninstalled: false };
390
+ }
391
+
392
+ for (const rel of Object.keys(manifest.files)) {
393
+ if (rel.includes('..') || path.isAbsolute(rel)) {
394
+ throw new NubosPilotError(
395
+ 'manifest-path-traversal',
396
+ 'Manifest contains suspicious path',
397
+ { rel },
398
+ );
399
+ }
400
+ }
401
+
402
+ let removed = 0;
403
+ for (const rel of Object.keys(manifest.files)) {
404
+ const abs = path.join(payloadDir, rel);
405
+ try { fs.unlinkSync(abs); removed++; } catch (err) {
406
+ if (err && err.code !== 'ENOENT') {
407
+ console.error(yellow + ' [uninstall] ' + rel + ' not removed: ' + err.message + reset);
408
+ }
409
+ }
410
+ }
411
+
412
+ try { fs.unlinkSync(path.join(payloadDir, '.manifest.json')); } catch {}
413
+
414
+ try { fs.rmdirSync(payloadDir); } catch { }
415
+
416
+ for (const name of ['CLAUDE.md', 'AGENTS.md', 'GEMINI.md']) {
417
+ const p = path.join(projectRoot, name);
418
+ if (!fs.existsSync(p)) continue;
419
+ const stripped = managedBlockMod.stripBlock(fs.readFileSync(p, 'utf-8'));
420
+ if (!stripped || !stripped.trim()) {
421
+ try { fs.unlinkSync(p); } catch {}
422
+ } else {
423
+ atomicWriteFileSync(p, stripped);
424
+ }
425
+ }
426
+
427
+ const opencodeDir = _opencodePayloadDirFor(projectRoot, scope);
428
+ if (fs.existsSync(opencodeDir)) {
429
+ try { fs.rmSync(opencodeDir, { recursive: true, force: true }); } catch {}
430
+ }
431
+ const opencodeParent = path.dirname(opencodeDir);
432
+ try { fs.rmdirSync(opencodeParent); } catch {}
433
+
434
+ console.error(green + '✓ Uninstall abgeschlossen' + reset);
435
+ let leftovers = [];
436
+ try {
437
+ if (fs.existsSync(payloadDir)) {
438
+ leftovers = fs.readdirSync(payloadDir).filter((f) => /\.bak(\.\d+|\.orphan-)?$/.test(f));
439
+ }
440
+ } catch {}
441
+ if (leftovers.length) {
442
+ console.error(dim + ' User-Backups belassen:' + reset);
443
+ for (const f of leftovers) console.error(dim + ' ' + f + reset);
444
+ }
445
+ return { uninstalled: true, removed, leftoverBaks: leftovers };
446
+ }
447
+
448
+ async function main() {
449
+ const rawArgs = process.argv.slice(2);
450
+ const { flags, rest } = parseInstallFlags(rawArgs);
451
+ const sub = rest[0];
452
+ const cwd = process.cwd();
453
+ switch (sub) {
454
+ case undefined:
455
+ return await runInstall({ cwd, mode: detectMode(cwd), flags });
456
+ case '--dry-run':
457
+ return await runInstall({ cwd, mode: detectMode(cwd), dryRun: true, flags });
458
+ case 'update':
459
+ return await runInstall({ cwd, mode: 'update', flags });
460
+ case 'uninstall':
461
+ return await runUninstall({ cwd, args: rest.slice(1) });
462
+ case 'doctor': {
463
+ const doctor = require('./np-tools/doctor.cjs');
464
+ return await doctor.run(rest.slice(1), { cwd, stdout: process.stdout });
465
+ }
466
+ default:
467
+ process.stderr.write(
468
+ red + 'Unbekanntes Subcommand: ' + sub + reset + '\n',
469
+ );
470
+ process.exit(1);
471
+ return undefined;
472
+ }
473
+ }
474
+
475
+ if (require.main === module) {
476
+ main().catch((err) => {
477
+ if (err && err.code) {
478
+ process.stderr.write(
479
+ JSON.stringify({
480
+ error: {
481
+ code: err.code,
482
+ message: err.message,
483
+ details: err.details || null,
484
+ },
485
+ }) + '\n',
486
+ );
487
+ } else {
488
+ process.stderr.write(((err && err.stack) || String(err)) + '\n');
489
+ }
490
+ process.exit(1);
491
+ });
492
+ }
493
+
494
+ module.exports = {
495
+ runInstall, runUninstall, detectMode, main,
496
+ parseInstallFlags,
497
+ VALID_AGENTS, VALID_SCOPES,
498
+ SOURCE_PAYLOAD_DIR, PAYLOAD_SUBPATH, STATE_SUBPATH,
499
+ _payloadDirFor, _stateDirFor,
500
+ };
@@ -0,0 +1,70 @@
1
+ const COMMANDS = [
2
+ { name: 'next', category: 'Utility', description: 'Print the next actionable step' },
3
+ { name: 'progress', category: 'Utility', description: 'Report % complete across phases and plans' },
4
+ { name: 'state', category: 'Utility', description: 'Print the current project state snapshot' },
5
+ { name: 'help', category: 'Utility', description: 'List available commands' },
6
+ { name: 'init', category: 'Utility', description: 'Dispatcher init payload for workflows' },
7
+
8
+ { name: 'discuss-phase', category: 'Planning', description: 'Adaptive phase-context interview (writes CONTEXT.md)' },
9
+ { name: 'discuss-phase-power', category: 'Planning', description: 'Bulk gray-area question file-UI (power mode)' },
10
+ { name: 'research-phase', category: 'Planning', description: 'Optional phase-level research (WebFetch + MCP; offline fallback)' },
11
+ { name: 'plan-phase', category: 'Planning', description: 'Creates PLAN.md with plan-checker verification loop' },
12
+ { name: 'new-project', category: 'Planning', description: 'Greenfield project init (PROJECT.md + REQUIREMENTS.md + roadmap)' },
13
+ { name: 'new-milestone', category: 'Planning', description: 'Append milestone + first phase to an existing project' },
14
+ { name: 'plan-milestone-gaps', category: 'Planning', description: 'Create corrective phases from audit gaps' },
15
+ { name: 'agent-skills', category: 'Planning', description: 'Print agent_skills config for a given subagent' },
16
+
17
+ { name: 'execute-phase', category: 'Execution', description: 'Wave-based phase execution (emits per-task executor-spawn payloads)' },
18
+ { name: 'execute-plan', category: 'Execution', description: 'Single-plan execution (sub-case of execute-phase)' },
19
+ { name: 'commit-task', category: 'Execution', description: 'Atomic per-task git commit via lib/git.cjs (D-03/D-25 enforced)' },
20
+ { name: 'checkpoint', category: 'Execution', description: 'Per-task crash-safety checkpoint CRUD (start/transition/touch/show)' },
21
+ { name: 'autonomous', category: 'Execution', description: 'In-session gate snapshot for auto-advance loop (ADR-0001, no daemon)' },
22
+ { name: 'verify-work', category: 'Execution', description: 'Two-pass goal-backward verification (D-21/D-22 VERIFICATION.md render/record)' },
23
+ { name: 'add-tests', category: 'Execution', description: 'Persist VERIFICATION Pass-cases as node:test UAT (Sentinel-preserving)' },
24
+ { name: 'pause-work', category: 'Execution', description: 'Stamp STATE.session.stopped_at + resume_file for explicit handoff' },
25
+ { name: 'resume-work', category: 'Execution', description: 'Classify session state (resume | orphan | clean) from STATE + checkpoints' },
26
+
27
+ { name: 'undo', category: 'Execution', description: 'Revert all task commits of a phase or plan via git revert (no history rewrite)' },
28
+ { name: 'undo-task', category: 'Execution', description: 'Revert a single task commit and reset task status to pending' },
29
+ { name: 'reset-slice', category: 'Execution', description: 'Restore working-tree files of the in-flight task and clear current_task (no commit)' },
30
+ { name: 'skip', category: 'Execution', description: 'Mark task status skipped (lifecycle CRUD)' },
31
+ { name: 'park', category: 'Execution', description: 'Mark task status parked (lifecycle CRUD)' },
32
+ { name: 'unpark', category: 'Execution', description: 'Return a parked task to pending (lifecycle CRUD)' },
33
+
34
+ { name: 'doctor', category: 'Install', description: '5-check install-integrity scan (--fix for auto-safe fixes)' },
35
+ { name: 'dispatch', category: 'Install', description: 'State-router: computes next action and delegates via Skill()' },
36
+ { name: 'queue', category: 'Install', description: 'Unified queue across todos/backlog/UAT/unplanned phases' },
37
+ { name: 'triage', category: 'Install', description: 'Interactive per-item triage loop (promote/keep/drop)' },
38
+
39
+ { name: 'resolve-model', category: 'Utility', description: 'Resolve agent/tier to model alias or id (Tier×Profile matrix; consulted at Task-spawn sites by workflows)' },
40
+
41
+ { name: 'metrics', category: 'Utility', description: 'Record JSONL metrics entry (record | now | start-timestamp | end-timestamp)' },
42
+
43
+ { name: 'plan-diff', category: 'Planning', description: 'Render two-part PLAN.md diff (semantic + git) or archive-rejected with reason' },
44
+
45
+ { name: 'ai-integration-phase', category: 'Planning', description: 'AI-SPEC generator with framework-selector + eval-planner (spawns 4 np-agents)' },
46
+ { name: 'ui-phase', category: 'Planning', description: 'UI-SPEC generator with researcher + checker revision loop' },
47
+ { name: 'ui-review', category: 'Review', description: '6-pillar retroactive UI audit on a completed phase' },
48
+ { name: 'eval-review', category: 'Review', description: 'Retroactive eval-coverage audit on a completed phase' },
49
+
50
+ { name: 'askuser', category: 'Utility', description: 'Capability-layer prompt wrapper (reads spec JSON, returns chosen label)' },
51
+ { name: 'commit', category: 'Utility', description: 'Atomic git commit wrapper with gitignore-guard (D-21)' },
52
+ { name: 'config-get', category: 'Utility', description: 'Read value from .nubos-pilot/config.json by dotted key path' },
53
+ { name: 'generate-slug', category: 'Utility', description: 'Slugify text via lib/phase.cjs.phaseSlug (used by add-todo, add-backlog, thread)' },
54
+ { name: 'phase', category: 'Utility', description: 'Phase utilities (next-decimal <base> — used by add-backlog 999.x)' },
55
+ { name: 'stats', category: 'Utility', description: 'Aggregated project stats (roadmap + STATE + git + metrics JSON shape)' },
56
+
57
+ { name: 'code-review', category: 'Review', description: 'Source-file review via np-code-reviewer (depth quick/standard/deep) — writes REVIEW.md sidecar' },
58
+ { name: 'code-review-fix', category: 'Review', description: 'Auto-fix REVIEW.md findings via np-code-fixer (atomic commit per finding)' },
59
+ { name: 'review', category: 'Review', description: 'Cross-AI peer review via 7-CLI fan-out (gemini/claude/codex/coderabbit/opencode/qwen/cursor)' },
60
+ { name: 'secure-phase', category: 'Review', description: 'Threat-mitigation audit via np-security-auditor against PLAN.md threat_model' },
61
+ { name: 'validate-phase', category: 'Review', description: 'Nyquist validation gap-fill via np-nyquist-auditor' },
62
+ { name: 'add-todo', category: 'Capture', description: 'Capture a pending todo to .nubos-pilot/todos/pending/ + increment STATE count' },
63
+ { name: 'note', category: 'Capture', description: 'Capture a free-form note (project default, --global writes to ~/.nubos-pilot/notes/)' },
64
+ { name: 'add-backlog', category: 'Capture', description: 'Append backlog item 999.x to ROADMAP.md + scaffold phase dir' },
65
+ { name: 'thread', category: 'Utility', description: 'Cross-session thread CRUD (create/resume under .nubos-pilot/threads/)' },
66
+ { name: 'session-report', category: 'Utility', description: 'Generate session report from metrics since .last-session pointer' },
67
+ { name: 'cleanup', category: 'Utility', description: 'Archive completed milestones to .nubos-pilot/archive/v<X.Y>/ + collapse ROADMAP <details> block' },
68
+ ];
69
+
70
+ module.exports = { COMMANDS };