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,440 @@
1
+ const { NubosPilotError } = require('../core.cjs');
2
+
3
+ function detectLineEnding(content) {
4
+ if (typeof content !== 'string') {
5
+ throw new NubosPilotError(
6
+ 'codex-toml-invalid-input',
7
+ 'detectLineEnding expects a string',
8
+ { got: typeof content },
9
+ );
10
+ }
11
+ const firstNewlineIndex = content.indexOf('\n');
12
+ if (firstNewlineIndex === -1) return '\n';
13
+ return firstNewlineIndex > 0 && content[firstNewlineIndex - 1] === '\r' ? '\r\n' : '\n';
14
+ }
15
+
16
+ function splitTomlLines(content) {
17
+ const lines = [];
18
+ let start = 0;
19
+ while (start < content.length) {
20
+ const newlineIndex = content.indexOf('\n', start);
21
+ if (newlineIndex === -1) {
22
+ lines.push({
23
+ start,
24
+ end: content.length,
25
+ text: content.slice(start),
26
+ eol: '',
27
+ });
28
+ break;
29
+ }
30
+ const hasCr = newlineIndex > start && content[newlineIndex - 1] === '\r';
31
+ const end = hasCr ? newlineIndex - 1 : newlineIndex;
32
+ lines.push({
33
+ start,
34
+ end,
35
+ text: content.slice(start, end),
36
+ eol: hasCr ? '\r\n' : '\n',
37
+ });
38
+ start = newlineIndex + 1;
39
+ }
40
+ return lines;
41
+ }
42
+
43
+ function isEscapedInBasicString(line, index) {
44
+ let slashCount = 0;
45
+ let cursor = index - 1;
46
+ while (cursor >= 0 && line[cursor] === '\\') {
47
+ slashCount += 1;
48
+ cursor -= 1;
49
+ }
50
+ return slashCount % 2 === 1;
51
+ }
52
+
53
+ function findMultilineBasicStringClose(line, startIndex) {
54
+ let searchIndex = startIndex;
55
+ while (searchIndex < line.length) {
56
+ const closeIndex = line.indexOf('"""', searchIndex);
57
+ if (closeIndex === -1) return -1;
58
+ if (!isEscapedInBasicString(line, closeIndex)) return closeIndex;
59
+ searchIndex = closeIndex + 1;
60
+ }
61
+ return -1;
62
+ }
63
+
64
+ function findTomlCommentStart(line) {
65
+ let i = 0;
66
+ let multilineState = null;
67
+ while (i < line.length) {
68
+ if (multilineState === 'literal') {
69
+ const closeIndex = line.indexOf("'''", i);
70
+ if (closeIndex === -1) return -1;
71
+ i = closeIndex + 3;
72
+ multilineState = null;
73
+ continue;
74
+ }
75
+ if (multilineState === 'basic') {
76
+ const closeIndex = findMultilineBasicStringClose(line, i);
77
+ if (closeIndex === -1) return -1;
78
+ i = closeIndex + 3;
79
+ multilineState = null;
80
+ continue;
81
+ }
82
+ const ch = line[i];
83
+ if (ch === '#') return i;
84
+ if (ch === "'") {
85
+ if (line.startsWith("'''", i)) {
86
+ multilineState = 'literal';
87
+ i += 3;
88
+ continue;
89
+ }
90
+ const close = line.indexOf("'", i + 1);
91
+ if (close === -1) return -1;
92
+ i = close + 1;
93
+ continue;
94
+ }
95
+ if (ch === '"') {
96
+ if (line.startsWith('"""', i)) {
97
+ multilineState = 'basic';
98
+ i += 3;
99
+ continue;
100
+ }
101
+ i += 1;
102
+ while (i < line.length) {
103
+ if (line[i] === '\\') { i += 2; continue; }
104
+ if (line[i] === '"') { i += 1; break; }
105
+ i += 1;
106
+ }
107
+ continue;
108
+ }
109
+ i += 1;
110
+ }
111
+ return -1;
112
+ }
113
+
114
+ function advanceTomlMultilineStringState(line, multilineState) {
115
+ let i = 0;
116
+ let state = multilineState;
117
+ while (i < line.length) {
118
+ if (state === 'literal') {
119
+ const closeIndex = line.indexOf("'''", i);
120
+ if (closeIndex === -1) return state;
121
+ i = closeIndex + 3;
122
+ state = null;
123
+ continue;
124
+ }
125
+ if (state === 'basic') {
126
+ const closeIndex = findMultilineBasicStringClose(line, i);
127
+ if (closeIndex === -1) return state;
128
+ i = closeIndex + 3;
129
+ state = null;
130
+ continue;
131
+ }
132
+ const ch = line[i];
133
+ if (ch === '#') return state;
134
+ if (ch === "'") {
135
+ if (line.startsWith("'''", i)) {
136
+ state = 'literal';
137
+ i += 3;
138
+ continue;
139
+ }
140
+ const close = line.indexOf("'", i + 1);
141
+ if (close === -1) return state;
142
+ i = close + 1;
143
+ continue;
144
+ }
145
+ if (ch === '"') {
146
+ if (line.startsWith('"""', i)) {
147
+ state = 'basic';
148
+ i += 3;
149
+ continue;
150
+ }
151
+ i += 1;
152
+ while (i < line.length) {
153
+ if (line[i] === '\\') { i += 2; continue; }
154
+ if (line[i] === '"') { i += 1; break; }
155
+ i += 1;
156
+ }
157
+ continue;
158
+ }
159
+ i += 1;
160
+ }
161
+ return state;
162
+ }
163
+
164
+ function findTomlAssignmentEquals(line) {
165
+ let i = 0;
166
+ while (i < line.length) {
167
+ const ch = line[i];
168
+ if (ch === '#') return -1;
169
+ if (ch === "'") {
170
+ i += 1;
171
+ while (i < line.length) {
172
+ if (line[i] === "'") { i += 1; break; }
173
+ i += 1;
174
+ }
175
+ continue;
176
+ }
177
+ if (ch === '"') {
178
+ i += 1;
179
+ while (i < line.length) {
180
+ if (line[i] === '\\') { i += 2; continue; }
181
+ if (line[i] === '"') { i += 1; break; }
182
+ i += 1;
183
+ }
184
+ continue;
185
+ }
186
+ if (ch === '=') return i;
187
+ i += 1;
188
+ }
189
+ return -1;
190
+ }
191
+
192
+ function parseTomlKeyPath(keyText) {
193
+ const segments = [];
194
+ let i = 0;
195
+ while (i < keyText.length) {
196
+ while (i < keyText.length && /\s/.test(keyText[i])) i += 1;
197
+ if (i >= keyText.length) break;
198
+ if (keyText[i] === "'" || keyText[i] === '"') {
199
+ const quote = keyText[i];
200
+ let segment = '';
201
+ let closed = false;
202
+ i += 1;
203
+ while (i < keyText.length) {
204
+ if (quote === '"' && keyText[i] === '\\') {
205
+ if (i + 1 >= keyText.length) return null;
206
+ segment += keyText[i + 1];
207
+ i += 2;
208
+ continue;
209
+ }
210
+ if (keyText[i] === quote) { i += 1; closed = true; break; }
211
+ segment += keyText[i];
212
+ i += 1;
213
+ }
214
+ if (!closed) return null;
215
+ segments.push(segment);
216
+ } else {
217
+ const match = keyText.slice(i).match(/^[A-Za-z0-9_-]+/);
218
+ if (!match) return null;
219
+ segments.push(match[0]);
220
+ i += match[0].length;
221
+ }
222
+ while (i < keyText.length && /\s/.test(keyText[i])) i += 1;
223
+ if (i >= keyText.length) break;
224
+ if (keyText[i] !== '.') return null;
225
+ i += 1;
226
+ }
227
+ return segments.length > 0 ? segments : null;
228
+ }
229
+
230
+ function parseTomlBracketHeader(line, array) {
231
+ let i = 0;
232
+ while (i < line.length && /\s/.test(line[i])) i += 1;
233
+ const open = array ? '[[' : '[';
234
+ const close = array ? ']]' : ']';
235
+ if (!line.startsWith(open, i)) return null;
236
+ i += open.length;
237
+ const start = i;
238
+ while (i < line.length) {
239
+ if (line[i] === "'" || line[i] === '"') {
240
+ const quote = line[i];
241
+ i += 1;
242
+ while (i < line.length) {
243
+ if (quote === '"' && line[i] === '\\') { i += 2; continue; }
244
+ if (line[i] === quote) { i += 1; break; }
245
+ i += 1;
246
+ }
247
+ continue;
248
+ }
249
+ if (line.startsWith(close, i)) {
250
+ const rawPath = line.slice(start, i).trim();
251
+ const segments = parseTomlKeyPath(rawPath);
252
+ if (!segments) return null;
253
+ i += close.length;
254
+ while (i < line.length && /\s/.test(line[i])) i += 1;
255
+ if (i < line.length && line[i] !== '#') return null;
256
+ return { path: segments.join('.'), segments, array };
257
+ }
258
+ if (line[i] === '#' || line[i] === '\r' || line[i] === '\n') return null;
259
+ i += 1;
260
+ }
261
+ return null;
262
+ }
263
+
264
+ function parseTomlTableHeader(line) {
265
+ return parseTomlBracketHeader(line, true) || parseTomlBracketHeader(line, false);
266
+ }
267
+
268
+ function parseTomlKey(line) {
269
+ const header = parseTomlTableHeader(line);
270
+ if (header) return null;
271
+ const equalsIndex = findTomlAssignmentEquals(line);
272
+ if (equalsIndex === -1) return null;
273
+ const raw = line.slice(0, equalsIndex).trim();
274
+ const segments = parseTomlKeyPath(raw);
275
+ if (!segments) return null;
276
+ return { raw, segments };
277
+ }
278
+
279
+ function getTomlLineRecords(content) {
280
+ const lines = splitTomlLines(content);
281
+ const records = [];
282
+ let currentTablePath = null;
283
+ let multilineState = null;
284
+ for (const line of lines) {
285
+ const startsInMultilineString = multilineState !== null;
286
+ const record = {
287
+ ...line,
288
+ startsInMultilineString,
289
+ tablePath: currentTablePath,
290
+ tableHeader: null,
291
+ keySegments: null,
292
+ keyRaw: null,
293
+ };
294
+ if (!startsInMultilineString) {
295
+ const header = parseTomlTableHeader(line.text);
296
+ if (header) {
297
+ record.tableHeader = header;
298
+ currentTablePath = header.path;
299
+ } else {
300
+ const key = parseTomlKey(line.text);
301
+ record.keySegments = key ? key.segments : null;
302
+ record.keyRaw = key ? key.raw : null;
303
+ }
304
+ }
305
+ multilineState = advanceTomlMultilineStringState(line.text, multilineState);
306
+ records.push(record);
307
+ }
308
+ return records;
309
+ }
310
+
311
+ function getTomlTableSections(content) {
312
+ const headerLines = getTomlLineRecords(content).filter((record) => record.tableHeader);
313
+ return headerLines.map((record, index) => ({
314
+ path: record.tableHeader.path,
315
+ array: record.tableHeader.array,
316
+ start: record.start,
317
+ headerEnd: record.end + record.eol.length,
318
+ end: index + 1 < headerLines.length ? headerLines[index + 1].start : content.length,
319
+ }));
320
+ }
321
+
322
+ function collapseTomlBlankLines(content) {
323
+ const eol = detectLineEnding(content);
324
+ return content.replace(/(?:\r?\n){3,}/g, eol + eol);
325
+ }
326
+
327
+ function removeContentRanges(content, ranges) {
328
+ const normalizedRanges = ranges
329
+ .filter((range) => range && range.start < range.end)
330
+ .sort((a, b) => a.start - b.start);
331
+ if (normalizedRanges.length === 0) return content;
332
+ const mergedRanges = [{ ...normalizedRanges[0] }];
333
+ for (let i = 1; i < normalizedRanges.length; i += 1) {
334
+ const current = normalizedRanges[i];
335
+ const previous = mergedRanges[mergedRanges.length - 1];
336
+ if (current.start <= previous.end) {
337
+ previous.end = Math.max(previous.end, current.end);
338
+ continue;
339
+ }
340
+ mergedRanges.push({ ...current });
341
+ }
342
+ let cleaned = '';
343
+ let cursor = 0;
344
+ for (const range of mergedRanges) {
345
+ cleaned += content.slice(cursor, range.start);
346
+ cursor = range.end;
347
+ }
348
+ cleaned += content.slice(cursor);
349
+ return cleaned;
350
+ }
351
+
352
+ function _isTrappedRecord(record, featuresSection) {
353
+ if (record.tableHeader || record.startsInMultilineString) return false;
354
+ if (record.tablePath !== 'features') return false;
355
+ if (record.start < featuresSection.headerEnd) return false;
356
+ if (record.end + record.eol.length > featuresSection.end) return false;
357
+ if (!record.keySegments || record.keySegments.length === 0) return false;
358
+ const equalsIndex = findTomlAssignmentEquals(record.text);
359
+ if (equalsIndex === -1) return false;
360
+ const commentStart = findTomlCommentStart(record.text);
361
+ const valueText = record.text
362
+ .slice(equalsIndex + 1, commentStart === -1 ? record.text.length : commentStart)
363
+ .trim();
364
+ if (valueText === 'true' || valueText === 'false') return false;
365
+
366
+ if (valueText.startsWith("'''") || valueText.startsWith('"""')) return false;
367
+ return true;
368
+ }
369
+
370
+ function hasTrappedFeatures(content) {
371
+ if (typeof content !== 'string') {
372
+ throw new NubosPilotError(
373
+ 'codex-toml-invalid-input',
374
+ 'hasTrappedFeatures expects a string',
375
+ { got: typeof content },
376
+ );
377
+ }
378
+ const featuresSection = getTomlTableSections(content)
379
+ .find((section) => !section.array && section.path === 'features');
380
+ if (!featuresSection) return false;
381
+ const records = getTomlLineRecords(content);
382
+ for (const record of records) {
383
+ if (_isTrappedRecord(record, featuresSection)) return true;
384
+ }
385
+ return false;
386
+ }
387
+
388
+ function repairTrappedFeatures(content) {
389
+ if (typeof content !== 'string') {
390
+ throw new NubosPilotError(
391
+ 'codex-toml-invalid-input',
392
+ 'repairTrappedFeatures expects a string',
393
+ { got: typeof content },
394
+ );
395
+ }
396
+ const eol = detectLineEnding(content);
397
+ const featuresSection = getTomlTableSections(content)
398
+ .find((section) => !section.array && section.path === 'features');
399
+ if (!featuresSection) return content;
400
+
401
+ const lineRecords = getTomlLineRecords(content);
402
+ const trappedLines = lineRecords.filter((r) => _isTrappedRecord(r, featuresSection));
403
+ if (trappedLines.length === 0) return content;
404
+
405
+
406
+ const relocatedText = trappedLines.map((r) => r.text).join(eol) + eol;
407
+
408
+ const removalRanges = trappedLines.map((r) => ({
409
+ start: r.start,
410
+ end: r.end + r.eol.length,
411
+ }));
412
+ let cleaned = removeContentRanges(content, removalRanges);
413
+ cleaned = collapseTomlBlankLines(cleaned);
414
+
415
+ const cleanedRecords = getTomlLineRecords(cleaned);
416
+ const cleanedFeaturesHeader = cleanedRecords.find(
417
+ (r) => r.tableHeader && r.tableHeader.path === 'features' && !r.tableHeader.array,
418
+ );
419
+ if (!cleanedFeaturesHeader) return cleaned;
420
+
421
+ const before = cleaned.slice(0, cleanedFeaturesHeader.start);
422
+ const after = cleaned.slice(cleanedFeaturesHeader.start);
423
+ const needsGap = before.length > 0 && !before.endsWith(eol + eol);
424
+ const trailingGap = after.length > 0 && !relocatedText.endsWith(eol + eol) ? eol : '';
425
+ return before + (needsGap ? eol : '') + relocatedText + trailingGap + after;
426
+ }
427
+
428
+ module.exports = {
429
+ hasTrappedFeatures,
430
+ repairTrappedFeatures,
431
+ detectLineEnding,
432
+
433
+
434
+ getTomlLineRecords,
435
+ getTomlTableSections,
436
+ findTomlCommentStart,
437
+ findTomlAssignmentEquals,
438
+ removeContentRanges,
439
+ collapseTomlBlankLines,
440
+ };
@@ -0,0 +1,30 @@
1
+ const BEGIN = '<!-- nubos-pilot:begin v1 -->';
2
+ const END = '<!-- nubos-pilot:end -->';
3
+ const RE = /<!-- nubos-pilot:begin[^>]*-->[\s\S]*?<!-- nubos-pilot:end -->/;
4
+
5
+ function rewriteBlock(content, innerMd) {
6
+ const safe = content == null ? '' : String(content);
7
+ const inner = innerMd == null ? '' : String(innerMd);
8
+ const block =
9
+ BEGIN +
10
+ '\n<!-- do not edit manually — managed by npx nubos-pilot -->\n' +
11
+ inner +
12
+ '\n' +
13
+ END;
14
+ if (RE.test(safe)) return safe.replace(RE, block);
15
+ const sep =
16
+ safe.length === 0 || safe.endsWith('\n\n')
17
+ ? ''
18
+ : safe.endsWith('\n')
19
+ ? '\n'
20
+ : '\n\n';
21
+ return safe + sep + block + '\n';
22
+ }
23
+
24
+ function stripBlock(content) {
25
+ const safe = content == null ? '' : String(content);
26
+ const stripped = safe.replace(RE, '');
27
+ return stripped.replace(/\n{3,}/g, '\n\n').trimEnd() + '\n';
28
+ }
29
+
30
+ module.exports = { rewriteBlock, stripBlock, BEGIN, END };
@@ -0,0 +1,148 @@
1
+ const fs = require('node:fs');
2
+ const path = require('node:path');
3
+ const crypto = require('node:crypto');
4
+ const { atomicWriteFileSync, NubosPilotError } = require('../core.cjs');
5
+
6
+ const MANIFEST_FILENAME = '.manifest.json';
7
+
8
+ function fileHashSync(filePath) {
9
+ const h = crypto.createHash('sha256');
10
+ h.update(fs.readFileSync(filePath));
11
+ return h.digest('hex');
12
+ }
13
+
14
+ function buildManifest(payloadDir, pkgVersion) {
15
+ const files = Object.create(null);
16
+ function walk(dir) {
17
+ let entries;
18
+ try {
19
+ entries = fs.readdirSync(dir, { withFileTypes: true });
20
+ } catch (err) {
21
+ throw new NubosPilotError(
22
+ 'manifest-build-failed',
23
+ 'Cannot read payload directory: ' + dir,
24
+ { dir, cause: err && err.message },
25
+ );
26
+ }
27
+ for (const entry of entries) {
28
+ const full = path.join(dir, entry.name);
29
+ if (entry.isDirectory()) { walk(full); continue; }
30
+ if (!entry.isFile()) continue;
31
+ if (entry.name === MANIFEST_FILENAME) continue;
32
+ const rel = path.relative(payloadDir, full).replace(/\\/g, '/');
33
+ files[rel] = fileHashSync(full);
34
+ }
35
+ }
36
+ walk(payloadDir);
37
+ return {
38
+ version: String(pkgVersion == null ? '' : pkgVersion),
39
+ timestamp: new Date().toISOString(),
40
+ files,
41
+ };
42
+ }
43
+
44
+ function diffManifests(oldM, newM) {
45
+ const oldFiles = (oldM && oldM.files) || {};
46
+ const newFiles = (newM && newM.files) || {};
47
+ const oldKeys = Object.keys(oldFiles);
48
+ const newKeys = Object.keys(newFiles);
49
+ const newSet = new Set(newKeys);
50
+ const oldSet = new Set(oldKeys);
51
+ const stale = oldKeys.filter((k) => !newSet.has(k));
52
+ const added = newKeys.filter((k) => !oldSet.has(k));
53
+ const changed = newKeys.filter(
54
+ (k) => oldSet.has(k) && oldFiles[k] !== newFiles[k],
55
+ );
56
+ return { stale, added, changed };
57
+ }
58
+
59
+ function _validateManifestShape(parsed, manifestPath) {
60
+ if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) {
61
+ throw new NubosPilotError(
62
+ 'manifest-invalid-structure',
63
+ 'Manifest root must be an object',
64
+ { path: manifestPath },
65
+ );
66
+ }
67
+ const files = parsed.files;
68
+ if (files == null || typeof files !== 'object' || Array.isArray(files)) {
69
+ throw new NubosPilotError(
70
+ 'manifest-invalid-structure',
71
+ 'Manifest files map must be an object',
72
+ { path: manifestPath },
73
+ );
74
+ }
75
+ const safeFiles = Object.create(null);
76
+ for (const key of Object.keys(files)) {
77
+ if (key === '__proto__' || key === 'constructor' || key === 'prototype') {
78
+ throw new NubosPilotError(
79
+ 'manifest-invalid-structure',
80
+ 'Refusing prototype-pollution key: ' + key,
81
+ { path: manifestPath, key },
82
+ );
83
+ }
84
+ const value = files[key];
85
+ if (typeof value !== 'string') {
86
+ throw new NubosPilotError(
87
+ 'manifest-invalid-structure',
88
+ 'Manifest entry must be a hash string: ' + key,
89
+ { path: manifestPath, key },
90
+ );
91
+ }
92
+ safeFiles[key] = value;
93
+ }
94
+ return {
95
+ version: parsed.version == null ? '' : String(parsed.version),
96
+ timestamp: parsed.timestamp == null ? '' : String(parsed.timestamp),
97
+ files: safeFiles,
98
+ };
99
+ }
100
+
101
+ function readManifest(payloadDir) {
102
+ const manifestPath = path.join(payloadDir, MANIFEST_FILENAME);
103
+ if (!fs.existsSync(manifestPath)) return null;
104
+ let raw;
105
+ try {
106
+ raw = fs.readFileSync(manifestPath, 'utf-8');
107
+ } catch (err) {
108
+ throw new NubosPilotError(
109
+ 'manifest-parse-failed',
110
+ 'Cannot read manifest: ' + (err && err.message),
111
+ { path: manifestPath },
112
+ );
113
+ }
114
+ let parsed;
115
+ try {
116
+ parsed = JSON.parse(raw);
117
+ } catch (err) {
118
+ throw new NubosPilotError(
119
+ 'manifest-parse-failed',
120
+ 'Manifest JSON invalid: ' + (err && err.message),
121
+ { path: manifestPath },
122
+ );
123
+ }
124
+ return _validateManifestShape(parsed, manifestPath);
125
+ }
126
+
127
+ function writeManifest(payloadDir, manifest) {
128
+ try {
129
+ fs.mkdirSync(payloadDir, { recursive: true });
130
+ } catch (err) {
131
+ throw new NubosPilotError(
132
+ 'manifest-write-failed',
133
+ 'Cannot ensure payload directory: ' + (err && err.message),
134
+ { payloadDir },
135
+ );
136
+ }
137
+ const manifestPath = path.join(payloadDir, MANIFEST_FILENAME);
138
+ const payload = JSON.stringify(manifest, null, 2);
139
+ atomicWriteFileSync(manifestPath, payload);
140
+ }
141
+
142
+ module.exports = {
143
+ buildManifest,
144
+ diffManifests,
145
+ readManifest,
146
+ writeManifest,
147
+ fileHashSync,
148
+ };