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,127 @@
1
+ const fs = require('node:fs');
2
+ const path = require('node:path');
3
+ const os = require('node:os');
4
+ const { atomicWriteFileSync, NubosPilotError } = require('../core.cjs');
5
+
6
+ const DEFAULT_MCP_NAME = 'nubos';
7
+ const DEFAULT_MCP_URL = 'https://mcp.nubos.cloud/mcp/nubos';
8
+ const DEFAULT_MCP_TOKEN = '4f81676a6be2f069d55769e877d133dba44a98ea263e24b5dfecd7fb7ed448ba';
9
+
10
+ function _resolveTarget(runtime, scope, projectRoot, home) {
11
+ const h = home || os.homedir();
12
+ if (runtime === 'claude') {
13
+ return scope === 'global'
14
+ ? { kind: 'claude-json', path: path.join(h, '.claude.json') }
15
+ : { kind: 'claude-json', path: path.join(projectRoot, '.mcp.json') };
16
+ }
17
+ if (runtime === 'codex') {
18
+ return { kind: 'codex-toml', path: path.join(h, '.codex', 'config.toml') };
19
+ }
20
+ if (runtime === 'gemini') {
21
+ return scope === 'global'
22
+ ? { kind: 'gemini-json', path: path.join(h, '.gemini', 'settings.json') }
23
+ : { kind: 'gemini-json', path: path.join(projectRoot, '.gemini', 'settings.json') };
24
+ }
25
+ if (runtime === 'opencode') {
26
+ return scope === 'global'
27
+ ? { kind: 'opencode-json', path: path.join(h, '.config', 'opencode', 'opencode.json') }
28
+ : { kind: 'opencode-json', path: path.join(projectRoot, 'opencode.json') };
29
+ }
30
+ throw new NubosPilotError('unknown-runtime',
31
+ 'Unknown runtime for MCP config: ' + runtime, { runtime });
32
+ }
33
+
34
+ function _readJson(filePath) {
35
+ if (!fs.existsSync(filePath)) return {};
36
+ try { return JSON.parse(fs.readFileSync(filePath, 'utf-8')); }
37
+ catch { return {}; }
38
+ }
39
+
40
+ function _writeJson(target, nextObj, dryRun) {
41
+ if (dryRun) return { path: target.path, wouldWrite: true };
42
+ fs.mkdirSync(path.dirname(target.path), { recursive: true });
43
+ atomicWriteFileSync(target.path, JSON.stringify(nextObj, null, 2) + '\n');
44
+ return { path: target.path, wrote: true };
45
+ }
46
+
47
+ function _claudeWriter({ target, token, url, name, dryRun }) {
48
+ const existing = _readJson(target.path);
49
+ const servers = existing.mcpServers || {};
50
+ servers[name] = {
51
+ type: 'http',
52
+ url,
53
+ headers: { Authorization: 'Bearer ' + token },
54
+ };
55
+ existing.mcpServers = servers;
56
+ return _writeJson(target, existing, dryRun);
57
+ }
58
+
59
+ function _geminiWriter({ target, token, url, name, dryRun }) {
60
+ const existing = _readJson(target.path);
61
+ const servers = existing.mcpServers || {};
62
+ servers[name] = {
63
+ httpUrl: url,
64
+ headers: { Authorization: 'Bearer ' + token },
65
+ };
66
+ existing.mcpServers = servers;
67
+ return _writeJson(target, existing, dryRun);
68
+ }
69
+
70
+ function _opencodeWriter({ target, token, url, name, dryRun }) {
71
+ const existing = _readJson(target.path);
72
+ if (!existing.$schema) existing.$schema = 'https://opencode.ai/config.json';
73
+ const mcp = existing.mcp || {};
74
+ mcp[name] = {
75
+ type: 'remote',
76
+ url,
77
+ headers: { Authorization: 'Bearer ' + token },
78
+ };
79
+ existing.mcp = mcp;
80
+ return _writeJson(target, existing, dryRun);
81
+ }
82
+
83
+ function _codexWriter({ target, token, url, name, dryRun }) {
84
+ const existing = fs.existsSync(target.path)
85
+ ? fs.readFileSync(target.path, 'utf-8') : '';
86
+ const header = '[mcp_servers.' + name + ']';
87
+ if (existing.includes(header)) {
88
+ return { path: target.path, wrote: false, reason: 'already-configured' };
89
+ }
90
+ const prefix = existing && !existing.endsWith('\n') ? '\n' : '';
91
+ const block = prefix
92
+ + '\n' + header + '\n'
93
+ + 'command = "npx"\n'
94
+ + 'args = ["-y", "mcp-remote", "' + url + '", "--header", "Authorization: Bearer ' + token + '"]\n';
95
+ if (dryRun) return { path: target.path, wouldWrite: true };
96
+ fs.mkdirSync(path.dirname(target.path), { recursive: true });
97
+ atomicWriteFileSync(target.path, existing + block);
98
+ return { path: target.path, wrote: true };
99
+ }
100
+
101
+ const WRITERS = {
102
+ 'claude-json': _claudeWriter,
103
+ 'gemini-json': _geminiWriter,
104
+ 'opencode-json': _opencodeWriter,
105
+ 'codex-toml': _codexWriter,
106
+ };
107
+
108
+ function writeMcpConfig(opts) {
109
+ const o = opts || {};
110
+ if (!o.runtime) throw new NubosPilotError('missing-arg', 'runtime is required');
111
+ if (!o.projectRoot) throw new NubosPilotError('missing-arg', 'projectRoot is required');
112
+ const scope = o.scope || 'local';
113
+ const token = o.token || process.env.NUBOS_MCP_TOKEN || DEFAULT_MCP_TOKEN;
114
+ const url = o.url || process.env.NUBOS_MCP_URL || DEFAULT_MCP_URL;
115
+ const name = o.name || DEFAULT_MCP_NAME;
116
+ const target = _resolveTarget(o.runtime, scope, o.projectRoot, o.home);
117
+ const writer = WRITERS[target.kind];
118
+ return writer({ target, token, url, name, dryRun: !!o.dryRun });
119
+ }
120
+
121
+ module.exports = {
122
+ writeMcpConfig,
123
+ DEFAULT_MCP_NAME,
124
+ DEFAULT_MCP_URL,
125
+ DEFAULT_MCP_TOKEN,
126
+ _resolveTarget,
127
+ };
@@ -0,0 +1,44 @@
1
+ const fs = require('node:fs');
2
+ const path = require('node:path');
3
+ const { execFileSync } = require('node:child_process');
4
+ const { detect: runtimeDetect } = require('../runtime/index.cjs');
5
+
6
+ const DEFAULT_PATH_PROBES = ['claude', 'codex', 'gemini'];
7
+
8
+ function _defaultPathProbe(bin) {
9
+ try {
10
+ execFileSync('which', [bin], { stdio: 'ignore' });
11
+ return bin;
12
+ } catch {
13
+ return null;
14
+ }
15
+ }
16
+
17
+ function detectRuntime(opts) {
18
+ const options = opts || {};
19
+ const cwd = options.cwd || process.cwd();
20
+ const pathProbe =
21
+ typeof options.pathProbe === 'function' ? options.pathProbe : _defaultPathProbe;
22
+
23
+ const primary = runtimeDetect({ cwd });
24
+ if (primary && primary.source !== 'default') {
25
+ return primary;
26
+ }
27
+
28
+ for (const bin of DEFAULT_PATH_PROBES) {
29
+ let hit = null;
30
+ try { hit = pathProbe(bin); } catch { hit = null; }
31
+ if (hit) return { runtime: bin, source: 'path' };
32
+ }
33
+
34
+ if (fs.existsSync(path.join(cwd, '.claude'))) {
35
+ return { runtime: 'claude', source: 'disk' };
36
+ }
37
+ if (fs.existsSync(path.join(cwd, '.codex'))) {
38
+ return { runtime: 'codex', source: 'disk' };
39
+ }
40
+
41
+ return { runtime: 'generic-readline', source: 'default' };
42
+ }
43
+
44
+ module.exports = { detectRuntime };
@@ -0,0 +1,149 @@
1
+ const fs = require('node:fs');
2
+ const path = require('node:path');
3
+ const { NubosPilotError } = require('../core.cjs');
4
+
5
+ const STAGING_SUBPATH = path.join('.claude', 'nubos-pilot.tmp');
6
+ const TARGET_SUBPATH = path.join('.claude', 'nubos-pilot');
7
+ const CLAUDE_SUBPATH = '.claude';
8
+
9
+ function _resolveStagingPath(projectRoot) {
10
+ return path.join(projectRoot, STAGING_SUBPATH);
11
+ }
12
+
13
+ function _resolveTargetPath(projectRoot) {
14
+ return path.join(projectRoot, TARGET_SUBPATH);
15
+ }
16
+
17
+ function stageDir(projectRoot) {
18
+ const tmp = _resolveStagingPath(projectRoot);
19
+ try {
20
+ fs.mkdirSync(tmp, { recursive: true });
21
+ } catch (err) {
22
+ throw new NubosPilotError(
23
+ 'staging-mkdir-failed',
24
+ 'Kann Staging-Verzeichnis nicht anlegen: ' + (err && err.message),
25
+ { tmp },
26
+ );
27
+ }
28
+ return tmp;
29
+ }
30
+
31
+ function cleanStaleStaging(projectRoot) {
32
+ const tmp = _resolveStagingPath(projectRoot);
33
+ if (!fs.existsSync(tmp)) return false;
34
+ try {
35
+ fs.rmSync(tmp, { recursive: true, force: true });
36
+ } catch (err) {
37
+ throw new NubosPilotError(
38
+ 'staging-clean-failed',
39
+ 'Kann verwaistes Staging nicht entfernen: ' + (err && err.message),
40
+ { tmp },
41
+ );
42
+ }
43
+ return true;
44
+ }
45
+
46
+ function _dirIsEmpty(dir) {
47
+ try {
48
+ const entries = fs.readdirSync(dir);
49
+ return entries.length === 0;
50
+ } catch {
51
+ return true;
52
+ }
53
+ }
54
+
55
+ function finalizeSwap(projectRoot) {
56
+ const tmp = _resolveStagingPath(projectRoot);
57
+ const target = _resolveTargetPath(projectRoot);
58
+ const claudeDir = path.join(projectRoot, CLAUDE_SUBPATH);
59
+ if (!fs.existsSync(tmp)) {
60
+ throw new NubosPilotError(
61
+ 'staging-swap-failed',
62
+ 'Kein Staging-Verzeichnis zum Finalisieren gefunden',
63
+ { phase: 'pre-check', tmp, target },
64
+ );
65
+ }
66
+
67
+ try {
68
+ fs.mkdirSync(claudeDir, { recursive: true });
69
+ } catch (err) {
70
+ throw new NubosPilotError(
71
+ 'staging-swap-failed',
72
+ 'Kann .claude/ Parent-Dir nicht anlegen: ' + (err && err.message),
73
+ { phase: 'mkdir-parent', tmp, target },
74
+ );
75
+ }
76
+
77
+ let oldPath = null;
78
+ const targetExists = fs.existsSync(target);
79
+ if (targetExists && !_dirIsEmpty(target)) {
80
+ oldPath = path.join(claudeDir, 'nubos-pilot.old-' + Date.now());
81
+ try {
82
+ fs.renameSync(target, oldPath);
83
+ console.error(
84
+ ' [staging] alte Installation nach ' +
85
+ path.basename(oldPath) +
86
+ ' verschoben',
87
+ );
88
+ } catch (err) {
89
+ throw new NubosPilotError(
90
+ 'staging-swap-failed',
91
+ 'Kann alte Installation nicht beiseite schieben: ' + (err && err.message),
92
+ { phase: 'rename-old', tmp, target, old: oldPath },
93
+ );
94
+ }
95
+ } else if (targetExists) {
96
+ try {
97
+ fs.rmdirSync(target);
98
+ } catch (err) {
99
+ throw new NubosPilotError(
100
+ 'staging-swap-failed',
101
+ 'Kann leeres Ziel-Dir nicht entfernen: ' + (err && err.message),
102
+ { phase: 'remove-empty-target', tmp, target },
103
+ );
104
+ }
105
+ }
106
+
107
+ try {
108
+ fs.renameSync(tmp, target);
109
+ console.error(
110
+ ' [staging] neue Installation nach ' +
111
+ path.basename(target) +
112
+ ' geswapt',
113
+ );
114
+ } catch (err) {
115
+ if (oldPath) {
116
+ try { fs.renameSync(oldPath, target); } catch {}
117
+ }
118
+ throw new NubosPilotError(
119
+ 'staging-swap-failed',
120
+ 'Kann Staging nicht finalisieren: ' + (err && err.message),
121
+ { phase: 'rename-in', tmp, target, old: oldPath },
122
+ );
123
+ }
124
+
125
+ if (oldPath) {
126
+ try {
127
+ fs.rmSync(oldPath, { recursive: true, force: true });
128
+ console.error(
129
+ ' [staging] alte Installation (' +
130
+ path.basename(oldPath) +
131
+ ') entfernt',
132
+ );
133
+ } catch (err) {
134
+ throw new NubosPilotError(
135
+ 'staging-swap-failed',
136
+ 'Kann alte Installation nicht aufräumen: ' + (err && err.message),
137
+ { phase: 'cleanup-old', tmp, target, old: oldPath },
138
+ );
139
+ }
140
+ }
141
+
142
+ return target;
143
+ }
144
+
145
+ module.exports = {
146
+ stageDir,
147
+ finalizeSwap,
148
+ cleanStaleStaging,
149
+ };
@@ -0,0 +1,229 @@
1
+ const fs = require('node:fs');
2
+ const path = require('node:path');
3
+ const readline = require('node:readline');
4
+ const { NubosPilotError, findProjectRoot } = require('./core.cjs');
5
+
6
+ const SAFE_PHASE_RE = /^[A-Za-z0-9._-]+$/;
7
+
8
+ function _zeroPhaseShape(phase) {
9
+ return {
10
+ phase,
11
+ record_count: 0,
12
+ total_tokens_in: null,
13
+ total_tokens_out: null,
14
+ partial_tokens: false,
15
+ avg_duration_ms_by_tier: {},
16
+ avg_duration_ms_by_agent: {},
17
+ retry_count_sum: 0,
18
+ error_count: 0,
19
+ error_rate: 0,
20
+ agents_seen: [],
21
+ first_record_at: null,
22
+ last_record_at: null,
23
+ };
24
+ }
25
+
26
+ function _readJsonlLines(filePath, onRecord) {
27
+ return new Promise((resolve, reject) => {
28
+ const stream = fs.createReadStream(filePath, { encoding: 'utf-8' });
29
+ stream.on('error', (err) => {
30
+ if (err && err.code === 'ENOENT') { resolve(); return; }
31
+ reject(err);
32
+ });
33
+ const rl = readline.createInterface({ input: stream, crlfDelay: Infinity });
34
+ rl.on('line', (raw) => {
35
+ const line = String(raw).trim();
36
+ if (!line) return;
37
+ try {
38
+ const rec = JSON.parse(line);
39
+ onRecord(rec);
40
+ } catch (_err) {
41
+ process.stderr.write('metrics-aggregate: skipping malformed JSONL line in ' + filePath + '\n');
42
+ }
43
+ });
44
+ rl.on('close', () => resolve());
45
+ rl.on('error', (err) => reject(err));
46
+ });
47
+ }
48
+
49
+ function _metricsDir(cwd) {
50
+ const root = findProjectRoot(cwd || process.cwd());
51
+ return path.join(root, '.nubos-pilot', 'metrics');
52
+ }
53
+
54
+ function _aggregateFromRecords(records, phase) {
55
+ const shape = _zeroPhaseShape(phase);
56
+ let tokensInSum = 0;
57
+ let tokensOutSum = 0;
58
+ let anyNull = false;
59
+ let anyValue = false;
60
+ const durByTier = {};
61
+ const countByTier = {};
62
+ const durByAgent = {};
63
+ const countByAgent = {};
64
+ const agentsSet = new Set();
65
+ for (const r of records) {
66
+ if (!r || typeof r !== 'object') continue;
67
+ shape.record_count += 1;
68
+ if (r.agent) agentsSet.add(String(r.agent));
69
+ if (typeof r.retry_count === 'number') shape.retry_count_sum += r.retry_count;
70
+ if (r.status && r.status !== 'ok') shape.error_count += 1;
71
+ if (r.started_at) {
72
+ if (!shape.first_record_at || r.started_at < shape.first_record_at) shape.first_record_at = r.started_at;
73
+ if (!shape.last_record_at || r.started_at > shape.last_record_at) shape.last_record_at = r.started_at;
74
+ }
75
+ if (r.tokens_in === null || r.tokens_out === null) {
76
+ anyNull = true;
77
+ } else {
78
+ anyValue = true;
79
+ tokensInSum += Number(r.tokens_in) || 0;
80
+ tokensOutSum += Number(r.tokens_out) || 0;
81
+ }
82
+ const d = typeof r.duration_ms === 'number' ? r.duration_ms : null;
83
+ if (d !== null) {
84
+ if (r.tier) {
85
+ durByTier[r.tier] = (durByTier[r.tier] || 0) + d;
86
+ countByTier[r.tier] = (countByTier[r.tier] || 0) + 1;
87
+ }
88
+ if (r.agent) {
89
+ durByAgent[r.agent] = (durByAgent[r.agent] || 0) + d;
90
+ countByAgent[r.agent] = (countByAgent[r.agent] || 0) + 1;
91
+ }
92
+ }
93
+ }
94
+ shape.agents_seen = Array.from(agentsSet).sort();
95
+ if (anyValue && anyNull) {
96
+ shape.total_tokens_in = tokensInSum;
97
+ shape.total_tokens_out = tokensOutSum;
98
+ shape.partial_tokens = true;
99
+ } else if (anyValue) {
100
+ shape.total_tokens_in = tokensInSum;
101
+ shape.total_tokens_out = tokensOutSum;
102
+ shape.partial_tokens = false;
103
+ } else {
104
+ shape.total_tokens_in = null;
105
+ shape.total_tokens_out = null;
106
+ shape.partial_tokens = false;
107
+ }
108
+ shape.error_rate = shape.record_count > 0 ? shape.error_count / shape.record_count : 0;
109
+ for (const t of Object.keys(durByTier)) {
110
+ shape.avg_duration_ms_by_tier[t] = durByTier[t] / countByTier[t];
111
+ }
112
+ for (const a of Object.keys(durByAgent)) {
113
+ shape.avg_duration_ms_by_agent[a] = durByAgent[a] / countByAgent[a];
114
+ }
115
+ return shape;
116
+ }
117
+
118
+ async function aggregatePhase(phase, opts) {
119
+ const phaseStr = String(phase);
120
+ if (!SAFE_PHASE_RE.test(phaseStr)) {
121
+ throw new NubosPilotError(
122
+ 'metrics-invalid-phase',
123
+ 'metrics-aggregate phase must match /^[A-Za-z0-9._-]+$/: ' + phaseStr,
124
+ { phase: phaseStr },
125
+ );
126
+ }
127
+ let dir;
128
+ try {
129
+ dir = _metricsDir(opts && opts.cwd);
130
+ } catch (err) {
131
+ if (err && err.code === 'not-in-project') return _zeroPhaseShape(phaseStr);
132
+ throw err;
133
+ }
134
+ const filePath = path.join(dir, 'phase-' + phaseStr + '.jsonl');
135
+ const records = [];
136
+ await _readJsonlLines(filePath, (r) => records.push(r));
137
+ return _aggregateFromRecords(records, phaseStr);
138
+ }
139
+
140
+ async function aggregateSession(sinceIso, opts) {
141
+ let dir;
142
+ try {
143
+ dir = _metricsDir(opts && opts.cwd);
144
+ } catch (err) {
145
+ if (err && err.code === 'not-in-project') {
146
+ return {
147
+ since_iso: sinceIso || null,
148
+ record_count: 0,
149
+ by_phase: {},
150
+ total_tokens_in: null,
151
+ total_tokens_out: null,
152
+ partial_tokens: false,
153
+ total_duration_ms: 0,
154
+ error_count: 0,
155
+ phases_touched: [],
156
+ };
157
+ }
158
+ throw err;
159
+ }
160
+ let files;
161
+ try {
162
+ files = fs.readdirSync(dir);
163
+ } catch (err) {
164
+ if (err && err.code === 'ENOENT') {
165
+ return {
166
+ since_iso: sinceIso || null,
167
+ record_count: 0,
168
+ by_phase: {},
169
+ total_tokens_in: null,
170
+ total_tokens_out: null,
171
+ partial_tokens: false,
172
+ total_duration_ms: 0,
173
+ error_count: 0,
174
+ phases_touched: [],
175
+ };
176
+ }
177
+ throw err;
178
+ }
179
+ const relevant = files.filter((f) => /^(phase-.+|meta)\.jsonl$/.test(f)).sort();
180
+ const byPhaseRecords = {};
181
+ for (const f of relevant) {
182
+ const key = f === 'meta.jsonl' ? 'meta' : f.slice('phase-'.length, -('.jsonl'.length));
183
+ byPhaseRecords[key] = byPhaseRecords[key] || [];
184
+ await _readJsonlLines(path.join(dir, f), (r) => {
185
+ if (sinceIso && r && r.started_at && r.started_at < sinceIso) return;
186
+ byPhaseRecords[key].push(r);
187
+ });
188
+ }
189
+ const by_phase = {};
190
+ let record_count = 0;
191
+ let total_duration_ms = 0;
192
+ let error_count = 0;
193
+ let tokensIn = 0;
194
+ let tokensOut = 0;
195
+ let anyNull = false;
196
+ let anyValue = false;
197
+ for (const key of Object.keys(byPhaseRecords)) {
198
+ const agg = _aggregateFromRecords(byPhaseRecords[key], key);
199
+ by_phase[key] = agg;
200
+ record_count += agg.record_count;
201
+ error_count += agg.error_count;
202
+ for (const r of byPhaseRecords[key]) {
203
+ if (!r || typeof r !== 'object') continue;
204
+ if (typeof r.duration_ms === 'number') total_duration_ms += r.duration_ms;
205
+ if (r.tokens_in === null || r.tokens_out === null) {
206
+ anyNull = true;
207
+ } else {
208
+ anyValue = true;
209
+ tokensIn += Number(r.tokens_in) || 0;
210
+ tokensOut += Number(r.tokens_out) || 0;
211
+ }
212
+ }
213
+ }
214
+ const total_tokens_in = anyValue ? tokensIn : null;
215
+ const total_tokens_out = anyValue ? tokensOut : null;
216
+ return {
217
+ since_iso: sinceIso || null,
218
+ record_count,
219
+ by_phase,
220
+ total_tokens_in,
221
+ total_tokens_out,
222
+ partial_tokens: anyValue && anyNull,
223
+ total_duration_ms,
224
+ error_count,
225
+ phases_touched: Object.keys(by_phase).sort(),
226
+ };
227
+ }
228
+
229
+ module.exports = { aggregatePhase, aggregateSession, _readJsonlLines };