nubos-pilot 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (273) hide show
  1. package/agents/np-ai-researcher.md +140 -0
  2. package/agents/np-code-fixer.md +363 -0
  3. package/agents/np-code-reviewer.md +351 -0
  4. package/agents/np-domain-researcher.md +136 -0
  5. package/agents/np-eval-auditor.md +167 -0
  6. package/agents/np-eval-planner.md +153 -0
  7. package/agents/np-executor.md +72 -0
  8. package/agents/np-framework-selector.md +171 -0
  9. package/agents/np-nyquist-auditor.md +185 -0
  10. package/agents/np-plan-checker.md +165 -0
  11. package/agents/np-planner.md +199 -0
  12. package/agents/np-researcher.md +150 -0
  13. package/agents/np-security-auditor.md +206 -0
  14. package/agents/np-ui-auditor.md +369 -0
  15. package/agents/np-ui-checker.md +192 -0
  16. package/agents/np-ui-researcher.md +324 -0
  17. package/agents/np-verifier.md +79 -0
  18. package/bin/check-coverage.cjs +40 -0
  19. package/bin/check-workflows.cjs +171 -0
  20. package/bin/check-workflows.test.cjs +208 -0
  21. package/bin/install.js +500 -0
  22. package/bin/np-tools/_commands.cjs +70 -0
  23. package/bin/np-tools/add-tests.cjs +171 -0
  24. package/bin/np-tools/add-tests.test.cjs +122 -0
  25. package/bin/np-tools/add-todo.cjs +108 -0
  26. package/bin/np-tools/add-todo.test.cjs +112 -0
  27. package/bin/np-tools/agent-skills.cjs +14 -0
  28. package/bin/np-tools/agent-skills.test.cjs +42 -0
  29. package/bin/np-tools/ai-integration-phase.cjs +109 -0
  30. package/bin/np-tools/ai-integration-phase.test.cjs +123 -0
  31. package/bin/np-tools/askuser.cjs +53 -0
  32. package/bin/np-tools/askuser.test.cjs +49 -0
  33. package/bin/np-tools/autonomous.cjs +69 -0
  34. package/bin/np-tools/autonomous.test.cjs +74 -0
  35. package/bin/np-tools/checkpoint.cjs +101 -0
  36. package/bin/np-tools/checkpoint.test.cjs +119 -0
  37. package/bin/np-tools/code-review.cjs +133 -0
  38. package/bin/np-tools/code-review.test.cjs +96 -0
  39. package/bin/np-tools/commit-task.cjs +120 -0
  40. package/bin/np-tools/commit-task.test.cjs +160 -0
  41. package/bin/np-tools/commit.cjs +103 -0
  42. package/bin/np-tools/commit.test.cjs +93 -0
  43. package/bin/np-tools/config.cjs +101 -0
  44. package/bin/np-tools/config.test.cjs +71 -0
  45. package/bin/np-tools/discuss-phase-power.cjs +265 -0
  46. package/bin/np-tools/discuss-phase-power.test.cjs +242 -0
  47. package/bin/np-tools/discuss-phase.cjs +132 -0
  48. package/bin/np-tools/discuss-phase.test.cjs +148 -0
  49. package/bin/np-tools/dispatch.cjs +116 -0
  50. package/bin/np-tools/doctor.cjs +242 -0
  51. package/bin/np-tools/eval-review.cjs +116 -0
  52. package/bin/np-tools/eval-review.test.cjs +123 -0
  53. package/bin/np-tools/execute-phase.cjs +182 -0
  54. package/bin/np-tools/execute-phase.test.cjs +116 -0
  55. package/bin/np-tools/execute-plan.cjs +124 -0
  56. package/bin/np-tools/execute-plan.test.cjs +82 -0
  57. package/bin/np-tools/help.cjs +28 -0
  58. package/bin/np-tools/help.test.cjs +29 -0
  59. package/bin/np-tools/init-dispatch.test.cjs +91 -0
  60. package/bin/np-tools/metrics.cjs +97 -0
  61. package/bin/np-tools/metrics.test.cjs +188 -0
  62. package/bin/np-tools/new-milestone.cjs +288 -0
  63. package/bin/np-tools/new-milestone.test.cjs +166 -0
  64. package/bin/np-tools/new-project.cjs +284 -0
  65. package/bin/np-tools/new-project.test.cjs +165 -0
  66. package/bin/np-tools/next.cjs +7 -0
  67. package/bin/np-tools/next.test.cjs +30 -0
  68. package/bin/np-tools/park.cjs +48 -0
  69. package/bin/np-tools/park.test.cjs +50 -0
  70. package/bin/np-tools/pause-work.cjs +24 -0
  71. package/bin/np-tools/pause-work.test.cjs +74 -0
  72. package/bin/np-tools/phase.cjs +71 -0
  73. package/bin/np-tools/phase.test.cjs +81 -0
  74. package/bin/np-tools/plan-diff.cjs +57 -0
  75. package/bin/np-tools/plan-diff.test.cjs +134 -0
  76. package/bin/np-tools/plan-milestone-gaps.cjs +115 -0
  77. package/bin/np-tools/plan-milestone-gaps.test.cjs +122 -0
  78. package/bin/np-tools/plan-phase.cjs +350 -0
  79. package/bin/np-tools/plan-phase.test.cjs +263 -0
  80. package/bin/np-tools/progress.cjs +7 -0
  81. package/bin/np-tools/progress.test.cjs +44 -0
  82. package/bin/np-tools/queue.cjs +213 -0
  83. package/bin/np-tools/research-phase.cjs +144 -0
  84. package/bin/np-tools/research-phase.test.cjs +154 -0
  85. package/bin/np-tools/reset-slice.cjs +17 -0
  86. package/bin/np-tools/reset-slice.test.cjs +96 -0
  87. package/bin/np-tools/resolve-model.cjs +110 -0
  88. package/bin/np-tools/resolve-model.test.cjs +200 -0
  89. package/bin/np-tools/resume-work.cjs +76 -0
  90. package/bin/np-tools/resume-work.test.cjs +91 -0
  91. package/bin/np-tools/skip.cjs +48 -0
  92. package/bin/np-tools/skip.test.cjs +66 -0
  93. package/bin/np-tools/slug.cjs +34 -0
  94. package/bin/np-tools/slug.test.cjs +46 -0
  95. package/bin/np-tools/state.cjs +16 -0
  96. package/bin/np-tools/state.test.cjs +40 -0
  97. package/bin/np-tools/stats.cjs +151 -0
  98. package/bin/np-tools/stats.test.cjs +118 -0
  99. package/bin/np-tools/triage.cjs +128 -0
  100. package/bin/np-tools/ui-phase.cjs +108 -0
  101. package/bin/np-tools/ui-phase.test.cjs +121 -0
  102. package/bin/np-tools/ui-review.cjs +108 -0
  103. package/bin/np-tools/ui-review.test.cjs +120 -0
  104. package/bin/np-tools/undo-task.cjs +31 -0
  105. package/bin/np-tools/undo-task.test.cjs +117 -0
  106. package/bin/np-tools/undo.cjs +43 -0
  107. package/bin/np-tools/undo.test.cjs +120 -0
  108. package/bin/np-tools/unpark.cjs +48 -0
  109. package/bin/np-tools/unpark.test.cjs +50 -0
  110. package/bin/np-tools/verify-work.cjs +186 -0
  111. package/bin/np-tools/verify-work.test.cjs +97 -0
  112. package/docs/adr/0001-no-daemon-invariant.md +82 -0
  113. package/docs/adr/0002-zero-runtime-dependencies.md +90 -0
  114. package/docs/adr/0003-max-six-unit-types.md +85 -0
  115. package/docs/adr/0004-atomic-commit-per-unit.md +102 -0
  116. package/docs/adr/0005-three-orthogonal-file-trees.md +98 -0
  117. package/docs/adr/0006-yaml-dependency-amendment.md +60 -0
  118. package/docs/adr/README.md +27 -0
  119. package/docs/agent-frontmatter-schema.md +84 -0
  120. package/docs/phase-artifact-schemas.md +292 -0
  121. package/docs/phase-directory-layout.md +82 -0
  122. package/lib/__tests__/README.md +1 -0
  123. package/lib/agents.cjs +98 -0
  124. package/lib/agents.test.cjs +286 -0
  125. package/lib/askuser.cjs +36 -0
  126. package/lib/askuser.test.cjs +310 -0
  127. package/lib/checkpoint.cjs +135 -0
  128. package/lib/checkpoint.test.cjs +184 -0
  129. package/lib/core.cjs +165 -0
  130. package/lib/core.test.cjs +405 -0
  131. package/lib/fixtures/README.md +1 -0
  132. package/lib/fixtures/phase-tree/README.md +1 -0
  133. package/lib/fixtures/plans/cycle/PLAN.md +16 -0
  134. package/lib/fixtures/plans/cycle/tasks/T-01.md +20 -0
  135. package/lib/fixtures/plans/cycle/tasks/T-02.md +20 -0
  136. package/lib/fixtures/plans/cycle/tasks/T-03.md +20 -0
  137. package/lib/fixtures/plans/linear/PLAN.md +16 -0
  138. package/lib/fixtures/plans/linear/tasks/T-01.md +20 -0
  139. package/lib/fixtures/plans/linear/tasks/T-02.md +20 -0
  140. package/lib/fixtures/plans/linear/tasks/T-03.md +20 -0
  141. package/lib/fixtures/plans/parallel/PLAN.md +16 -0
  142. package/lib/fixtures/plans/parallel/tasks/T-01.md +20 -0
  143. package/lib/fixtures/plans/parallel/tasks/T-02.md +20 -0
  144. package/lib/fixtures/plans/parallel/tasks/T-03.md +20 -0
  145. package/lib/fixtures/plans/wave-conflict/PLAN.md +16 -0
  146. package/lib/fixtures/plans/wave-conflict/tasks/T-01.md +20 -0
  147. package/lib/fixtures/plans/wave-conflict/tasks/T-02.md +20 -0
  148. package/lib/fixtures/roadmap/ROADMAP-malformed.md +3 -0
  149. package/lib/fixtures/roadmap/ROADMAP-minimal.md +51 -0
  150. package/lib/fixtures/roadmap/roadmap-malformed.yaml +7 -0
  151. package/lib/fixtures/roadmap/roadmap-minimal.yaml +40 -0
  152. package/lib/fixtures/roadmap/roadmap-ten-phases.yaml +101 -0
  153. package/lib/fixtures/templates/phase-context.md +6 -0
  154. package/lib/fixtures/templates/plan-skeleton.md +6 -0
  155. package/lib/frontmatter.cjs +251 -0
  156. package/lib/frontmatter.test.cjs +177 -0
  157. package/lib/gaps.cjs +197 -0
  158. package/lib/gaps.test.cjs +200 -0
  159. package/lib/git.cjs +207 -0
  160. package/lib/git.test.cjs +305 -0
  161. package/lib/install/agents-md.cjs +77 -0
  162. package/lib/install/backup.cjs +70 -0
  163. package/lib/install/codex-toml.cjs +440 -0
  164. package/lib/install/managed-block.cjs +30 -0
  165. package/lib/install/manifest.cjs +148 -0
  166. package/lib/install/mcp-writer.cjs +127 -0
  167. package/lib/install/runtime-detect.cjs +44 -0
  168. package/lib/install/staging.cjs +149 -0
  169. package/lib/metrics-aggregate.cjs +229 -0
  170. package/lib/metrics-aggregate.test.cjs +192 -0
  171. package/lib/metrics.cjs +120 -0
  172. package/lib/metrics.test.cjs +182 -0
  173. package/lib/model-aliases.regression.test.cjs +16 -0
  174. package/lib/model-profiles.cjs +42 -0
  175. package/lib/model-profiles.test.cjs +61 -0
  176. package/lib/next.cjs +236 -0
  177. package/lib/next.test.cjs +194 -0
  178. package/lib/phase.cjs +95 -0
  179. package/lib/phase.test.cjs +189 -0
  180. package/lib/plan-checker-contract.test.cjs +72 -0
  181. package/lib/plan-diff.cjs +173 -0
  182. package/lib/plan-diff.test.cjs +217 -0
  183. package/lib/plan.cjs +85 -0
  184. package/lib/plan.test.cjs +263 -0
  185. package/lib/progress.cjs +95 -0
  186. package/lib/progress.test.cjs +116 -0
  187. package/lib/researcher-contract.test.cjs +61 -0
  188. package/lib/roadmap-render.cjs +206 -0
  189. package/lib/roadmap-render.test.cjs +121 -0
  190. package/lib/roadmap.cjs +416 -0
  191. package/lib/roadmap.test.cjs +371 -0
  192. package/lib/runtime/_contract.test.cjs +61 -0
  193. package/lib/runtime/_readline.cjs +119 -0
  194. package/lib/runtime/_readline.test.cjs +126 -0
  195. package/lib/runtime/claude.cjs +48 -0
  196. package/lib/runtime/claude.test.cjs +101 -0
  197. package/lib/runtime/codex.cjs +35 -0
  198. package/lib/runtime/codex.test.cjs +114 -0
  199. package/lib/runtime/gemini.cjs +35 -0
  200. package/lib/runtime/gemini.test.cjs +109 -0
  201. package/lib/runtime/index.cjs +49 -0
  202. package/lib/runtime/index.test.cjs +181 -0
  203. package/lib/runtime/opencode.cjs +35 -0
  204. package/lib/runtime/opencode.test.cjs +124 -0
  205. package/lib/state.cjs +205 -0
  206. package/lib/state.test.cjs +264 -0
  207. package/lib/surface-audit.test.cjs +46 -0
  208. package/lib/tasks.cjs +327 -0
  209. package/lib/tasks.test.cjs +389 -0
  210. package/lib/template.cjs +66 -0
  211. package/lib/template.test.cjs +159 -0
  212. package/lib/undo.cjs +179 -0
  213. package/lib/undo.test.cjs +261 -0
  214. package/lib/verify.cjs +116 -0
  215. package/lib/verify.test.cjs +187 -0
  216. package/np-tools.cjs +303 -0
  217. package/package.json +39 -0
  218. package/templates/AI-SPEC.md +90 -0
  219. package/templates/CONTEXT.md +32 -0
  220. package/templates/PLAN.md +69 -0
  221. package/templates/PROJECT.md +60 -0
  222. package/templates/REQUIREMENTS.md +38 -0
  223. package/templates/SECURITY.md +61 -0
  224. package/templates/UI-SPEC.md +64 -0
  225. package/templates/VALIDATION.md +76 -0
  226. package/templates/claude/payload/README.md +11 -0
  227. package/templates/opencode/opencode.json +6 -0
  228. package/templates/opencode/payload/AGENTS.md +9 -0
  229. package/workflows/add-backlog.md +212 -0
  230. package/workflows/add-tests.md +69 -0
  231. package/workflows/add-todo.md +222 -0
  232. package/workflows/ai-integration-phase.md +230 -0
  233. package/workflows/autonomous.md +94 -0
  234. package/workflows/cleanup.md +325 -0
  235. package/workflows/code-review-fix.md +435 -0
  236. package/workflows/code-review.md +447 -0
  237. package/workflows/discuss-phase-assumptions.md +269 -0
  238. package/workflows/discuss-phase-power.md +139 -0
  239. package/workflows/discuss-phase.md +386 -0
  240. package/workflows/dispatch.md +9 -0
  241. package/workflows/doctor.md +10 -0
  242. package/workflows/eval-review.md +243 -0
  243. package/workflows/execute-phase.md +142 -0
  244. package/workflows/execute-plan.md +82 -0
  245. package/workflows/help.md +8 -0
  246. package/workflows/new-milestone.md +166 -0
  247. package/workflows/new-project.md +213 -0
  248. package/workflows/next.md +8 -0
  249. package/workflows/note.md +244 -0
  250. package/workflows/park.md +29 -0
  251. package/workflows/pause-work.md +34 -0
  252. package/workflows/plan-milestone-gaps.md +233 -0
  253. package/workflows/plan-phase.md +351 -0
  254. package/workflows/progress.md +8 -0
  255. package/workflows/queue.md +9 -0
  256. package/workflows/research-phase.md +327 -0
  257. package/workflows/reset-slice.md +39 -0
  258. package/workflows/resume-work.md +79 -0
  259. package/workflows/review.md +489 -0
  260. package/workflows/secure-phase.md +209 -0
  261. package/workflows/session-report.md +243 -0
  262. package/workflows/skip.md +29 -0
  263. package/workflows/state.md +7 -0
  264. package/workflows/stats.md +170 -0
  265. package/workflows/thread.md +214 -0
  266. package/workflows/triage.md +9 -0
  267. package/workflows/ui-phase.md +246 -0
  268. package/workflows/ui-review.md +222 -0
  269. package/workflows/undo-task.md +42 -0
  270. package/workflows/undo.md +55 -0
  271. package/workflows/unpark.md +29 -0
  272. package/workflows/validate-phase.md +231 -0
  273. package/workflows/verify-work.md +83 -0
@@ -0,0 +1,171 @@
1
+ #!/usr/bin/env node
2
+ const fs = require('node:fs');
3
+ const path = require('node:path');
4
+
5
+ const BARE_ASKUSER_RE = /AskUserQuestion/;
6
+ const DIRECT_READ_RE = /(cat\s+\.nubos-pilot|readFileSync\s*\(\s*['"][^'"]*\.nubos-pilot|\bRead\s*\(\s*['"][^'"]*\.nubos-pilot)/;
7
+
8
+ const BARE_READLINE_RE = /readline\.createInterface/;
9
+
10
+ const ALLOW_RE = /\$?\(?\s*node\s+np-tools\.cjs\s+\w+/;
11
+
12
+ const SPAWN_SITE_RE = /(Task\s*\(|Spawn\s+agent=)/;
13
+ const METRICS_RECORD_RE = /np-tools\.cjs\s+metrics\s+record\b/;
14
+ const METRICS_COVERAGE_WINDOW = 30;
15
+
16
+ const INSTALL_SCRIPT = 'bin/install.js';
17
+ const LIB_INSTALL_DIR = 'lib/install/';
18
+ const INSTALLER_SCAN_PATHS = [INSTALL_SCRIPT, LIB_INSTALL_DIR];
19
+
20
+ function _walk(dir, acc, opts) {
21
+ const extFilter = (opts && opts.ext) || ['.md'];
22
+ let entries;
23
+ try {
24
+ entries = fs.readdirSync(dir, { withFileTypes: true });
25
+ } catch (err) {
26
+ if (err && err.code === 'ENOENT') return acc;
27
+ throw err;
28
+ }
29
+ for (const e of entries) {
30
+ const abs = path.join(dir, e.name);
31
+ if (e.isDirectory()) _walk(abs, acc, opts);
32
+ else if (e.isFile() && extFilter.some((x) => e.name.endsWith(x))) acc.push(abs);
33
+ }
34
+ return acc;
35
+ }
36
+
37
+ function _scanFiles(files) {
38
+ const out = [];
39
+ for (const file of files) {
40
+ const raw = fs.readFileSync(file, 'utf-8');
41
+ const isCode = file.endsWith('.js') || file.endsWith('.cjs') || file.endsWith('.mjs');
42
+ const lines = raw.split(/\r?\n/);
43
+ for (let i = 0; i < lines.length; i++) {
44
+ const line = lines[i];
45
+ if (ALLOW_RE.test(line)) continue;
46
+ if (BARE_ASKUSER_RE.test(line)) {
47
+ out.push({ file, line: i + 1, pattern: 'AskUserQuestion' });
48
+ }
49
+ if (!isCode && DIRECT_READ_RE.test(line)) {
50
+ out.push({ file, line: i + 1, pattern: 'direct-read .nubos-pilot' });
51
+ }
52
+ if (isCode && BARE_READLINE_RE.test(line)) {
53
+ out.push({ file, line: i + 1, pattern: 'readline.createInterface' });
54
+ }
55
+ }
56
+ }
57
+ return out;
58
+ }
59
+
60
+ function _scanMetricsCoverage(files) {
61
+ const warnings = [];
62
+ const FENCE_RE = /^```/;
63
+ const BASH_FENCE_RE = /^```(\s*$|\s*(bash|sh|shell|zsh)\b)/i;
64
+ for (const file of files) {
65
+ if (!file.endsWith('.md')) continue;
66
+ const raw = fs.readFileSync(file, 'utf-8');
67
+ const lines = raw.split(/\r?\n/);
68
+
69
+
70
+
71
+ let inAnyFence = false;
72
+ let inBashFence = false;
73
+ for (let i = 0; i < lines.length; i++) {
74
+ const line = lines[i];
75
+ if (FENCE_RE.test(line)) {
76
+ if (inAnyFence) {
77
+ inAnyFence = false;
78
+ inBashFence = false;
79
+ } else {
80
+ inAnyFence = true;
81
+ inBashFence = BASH_FENCE_RE.test(line);
82
+ }
83
+ continue;
84
+ }
85
+ if (!inBashFence) continue;
86
+ if (!SPAWN_SITE_RE.test(line)) continue;
87
+ const windowEnd = Math.min(lines.length, i + 1 + METRICS_COVERAGE_WINDOW);
88
+ const windowBody = lines.slice(i, windowEnd).join('\n');
89
+ if (!METRICS_RECORD_RE.test(windowBody)) {
90
+ warnings.push({
91
+ file,
92
+ line: i + 1,
93
+ pattern: 'workflow-missing-metrics',
94
+ message:
95
+ 'Task/Spawn site without `np-tools.cjs metrics record` within ' +
96
+ METRICS_COVERAGE_WINDOW +
97
+ ' lines (D-06).',
98
+ });
99
+ }
100
+ }
101
+ }
102
+ return warnings;
103
+ }
104
+
105
+ function _scan(dir) {
106
+ return _scanFiles(_walk(dir, [], { ext: ['.md'] }));
107
+ }
108
+
109
+ function _scanInstallerSurface(cwd) {
110
+ const root = cwd || process.cwd();
111
+ const files = [];
112
+ for (const rel of INSTALLER_SCAN_PATHS) {
113
+ const abs = path.resolve(root, rel);
114
+ let st;
115
+ try { st = fs.statSync(abs); } catch { continue; }
116
+ if (st.isFile()) files.push(abs);
117
+ else if (st.isDirectory()) _walk(abs, files, { ext: ['.js', '.cjs', '.mjs'] });
118
+ }
119
+ return _scanFiles(files);
120
+ }
121
+
122
+ function checkWorkflows(dir) {
123
+ const target = dir || 'nubos-pilot/workflows';
124
+ const violations = [];
125
+ const warnings = [];
126
+ let workflowFiles = [];
127
+ try {
128
+ const st = fs.statSync(target);
129
+ if (st.isDirectory()) {
130
+ workflowFiles = _walk(target, [], { ext: ['.md'] });
131
+ violations.push(..._scanFiles(workflowFiles));
132
+ warnings.push(..._scanMetricsCoverage(workflowFiles));
133
+ }
134
+ } catch { }
135
+ violations.push(..._scanInstallerSurface());
136
+ return { violations, warnings, exitCode: violations.length ? 1 : 0 };
137
+ }
138
+
139
+ function main() {
140
+ const dir = process.argv[2] || 'nubos-pilot/workflows';
141
+ const { violations, warnings, exitCode } = checkWorkflows(dir);
142
+ if (violations.length) {
143
+ process.stderr.write('check-workflows: ' + violations.length + ' violation(s)\n');
144
+ for (const v of violations) {
145
+ process.stderr.write(' ' + v.file + ':' + v.line + ' -> ' + v.pattern + '\n');
146
+ }
147
+ }
148
+ if (warnings && warnings.length) {
149
+ process.stderr.write('check-workflows: ' + warnings.length + ' warning(s)\n');
150
+ for (const w of warnings) {
151
+ process.stderr.write(' ' + w.file + ':' + w.line + ' -> ' + w.pattern + '\n');
152
+ }
153
+ }
154
+ process.exit(exitCode);
155
+ }
156
+
157
+ if (require.main === module) main();
158
+
159
+ module.exports = {
160
+ checkWorkflows,
161
+ _scan,
162
+ _scanFiles,
163
+ _scanInstallerSurface,
164
+ _scanMetricsCoverage,
165
+ INSTALL_SCRIPT,
166
+ LIB_INSTALL_DIR,
167
+ INSTALLER_SCAN_PATHS,
168
+ SPAWN_SITE_RE,
169
+ METRICS_RECORD_RE,
170
+ METRICS_COVERAGE_WINDOW,
171
+ };
@@ -0,0 +1,208 @@
1
+ const test = require('node:test');
2
+ const assert = require('node:assert/strict');
3
+ const fs = require('node:fs');
4
+ const os = require('node:os');
5
+ const path = require('node:path');
6
+
7
+ const cw = require('./check-workflows.cjs');
8
+
9
+ test('CW-8: SCAN_PATHS includes bin/install.js and lib/install/ (Phase 7 extension)', () => {
10
+ const source = fs.readFileSync(path.resolve(__dirname, 'check-workflows.cjs'), 'utf8');
11
+ assert.match(source, /bin\/install\.js/);
12
+ assert.match(source, /lib\/install/);
13
+ assert.deepEqual(cw.INSTALLER_SCAN_PATHS, ['bin/install.js', 'lib/install/']);
14
+ });
15
+
16
+ function mkTmp() {
17
+ return fs.mkdtempSync(path.join(os.tmpdir(), 'cw-test-'));
18
+ }
19
+
20
+ function seed(root, files) {
21
+ for (const [rel, body] of Object.entries(files)) {
22
+ const abs = path.join(root, rel);
23
+ fs.mkdirSync(path.dirname(abs), { recursive: true });
24
+ fs.writeFileSync(abs, body, 'utf-8');
25
+ }
26
+ }
27
+
28
+ test('CW-1: absent directory returns empty violations + exitCode 0 (skip-on-absent)', () => {
29
+ const tmp = mkTmp();
30
+ try {
31
+ const res = cw.checkWorkflows(path.join(tmp, 'does/not/exist'));
32
+ assert.deepEqual(res.violations, []);
33
+ assert.equal(res.exitCode, 0);
34
+ } finally {
35
+ fs.rmSync(tmp, { recursive: true, force: true });
36
+ }
37
+ });
38
+
39
+ test('CW-2: empty directory returns empty violations + exitCode 0', () => {
40
+ const tmp = mkTmp();
41
+ try {
42
+ const dir = path.join(tmp, 'workflows');
43
+ fs.mkdirSync(dir, { recursive: true });
44
+ const res = cw.checkWorkflows(dir);
45
+ assert.deepEqual(res.violations, []);
46
+ assert.equal(res.exitCode, 0);
47
+ } finally {
48
+ fs.rmSync(tmp, { recursive: true, force: true });
49
+ }
50
+ });
51
+
52
+ test('CW-3: bare AskUserQuestion outside gateway → violation + exitCode 1', () => {
53
+ const tmp = mkTmp();
54
+ try {
55
+ const dir = path.join(tmp, 'workflows');
56
+ seed(dir, { 'bad.md': 'Please call AskUserQuestion directly here.\n' });
57
+ const res = cw.checkWorkflows(dir);
58
+ assert.equal(res.exitCode, 1);
59
+ assert.equal(res.violations.length, 1);
60
+ assert.match(res.violations[0].pattern, /AskUserQuestion/);
61
+ assert.ok(res.violations[0].file.endsWith('bad.md'));
62
+ assert.equal(typeof res.violations[0].line, 'number');
63
+ } finally {
64
+ fs.rmSync(tmp, { recursive: true, force: true });
65
+ }
66
+ });
67
+
68
+ test('CW-4: direct cat .nubos-pilot read triggers violation', () => {
69
+ const tmp = mkTmp();
70
+ try {
71
+ const dir = path.join(tmp, 'workflows');
72
+ seed(dir, { 'read.md': '```bash\ncat .nubos-pilot/STATE.md\n```\n' });
73
+ const res = cw.checkWorkflows(dir);
74
+ assert.equal(res.exitCode, 1);
75
+ assert.ok(res.violations.length >= 1);
76
+ assert.ok(res.violations.some((v) => /nubos-pilot/.test(v.pattern) || /cat/.test(v.pattern)));
77
+ } finally {
78
+ fs.rmSync(tmp, { recursive: true, force: true });
79
+ }
80
+ });
81
+
82
+ test('CW-5: single-call gateway through np-tools.cjs init does NOT violate', () => {
83
+ const tmp = mkTmp();
84
+ try {
85
+ const dir = path.join(tmp, 'workflows');
86
+ seed(dir, {
87
+ 'ok.md': '```bash\nINIT=$(node np-tools.cjs init phase-op 3)\n```\n',
88
+ });
89
+ const res = cw.checkWorkflows(dir);
90
+ assert.deepEqual(res.violations, []);
91
+ assert.equal(res.exitCode, 0);
92
+ } finally {
93
+ fs.rmSync(tmp, { recursive: true, force: true });
94
+ }
95
+ });
96
+
97
+ test('CW-6: CLI main reads argv[2]; violations exit 1 and write to stderr', () => {
98
+ const { spawnSync } = require('node:child_process');
99
+ const tmp = mkTmp();
100
+ try {
101
+ const dir = path.join(tmp, 'workflows');
102
+ seed(dir, { 'bad.md': 'note: AskUserQuestion here\n' });
103
+ const res = spawnSync(process.execPath, [path.join(__dirname, 'check-workflows.cjs'), dir], {
104
+ encoding: 'utf-8',
105
+ });
106
+ assert.equal(res.status, 1);
107
+ assert.match(res.stderr, /violation/i);
108
+ } finally {
109
+ fs.rmSync(tmp, { recursive: true, force: true });
110
+ }
111
+ });
112
+
113
+ test('CW-7: recursion detects violation in nested workflow .md file', () => {
114
+ const tmp = mkTmp();
115
+ try {
116
+ const dir = path.join(tmp, 'workflows');
117
+ seed(dir, { 'sub/deep.md': 'uses AskUserQuestion improperly\n' });
118
+ const res = cw.checkWorkflows(dir);
119
+ assert.equal(res.exitCode, 1);
120
+ assert.ok(res.violations.some((v) => v.file.includes(path.join('sub', 'deep.md'))));
121
+ } finally {
122
+ fs.rmSync(tmp, { recursive: true, force: true });
123
+ }
124
+ });
125
+
126
+ test('CW-M-1: Task spawn paired with `metrics record` inside bash block → no warning', () => {
127
+ const tmp = mkTmp();
128
+ try {
129
+ const dir = path.join(tmp, 'workflows');
130
+ seed(dir, {
131
+ 'good.md':
132
+ '```bash\n' +
133
+ 'MODEL=$(node np-tools.cjs resolve-model planner --profile balanced)\n' +
134
+ '# Spawn agent=np-planner tier=opus model=$MODEL\n' +
135
+ 'node np-tools.cjs metrics record --agent np-planner --phase 09 \\\n' +
136
+ ' --tier opus --resolved-model "$MODEL" --started "$START" --ended "$END"\n' +
137
+ '```\n',
138
+ });
139
+ const res = cw.checkWorkflows(dir);
140
+ assert.equal(res.exitCode, 0);
141
+ assert.deepEqual(res.violations, []);
142
+ assert.deepEqual(res.warnings, []);
143
+ } finally {
144
+ fs.rmSync(tmp, { recursive: true, force: true });
145
+ }
146
+ });
147
+
148
+ test('CW-M-2: Task spawn without `metrics record` → warning, exit code still 0', () => {
149
+ const tmp = mkTmp();
150
+ try {
151
+ const dir = path.join(tmp, 'workflows');
152
+ seed(dir, {
153
+ 'bad.md':
154
+ '```bash\n' +
155
+ 'MODEL=$(node np-tools.cjs resolve-model planner)\n' +
156
+ 'Task({ subagent_type: "np-planner", model: "$MODEL" })\n' +
157
+ 'echo done\n' +
158
+ '```\n',
159
+ });
160
+ const res = cw.checkWorkflows(dir);
161
+
162
+ assert.deepEqual(res.violations, []);
163
+ assert.equal(res.exitCode, 0);
164
+ assert.equal(res.warnings.length, 1);
165
+ assert.equal(res.warnings[0].pattern, 'workflow-missing-metrics');
166
+ assert.ok(res.warnings[0].file.endsWith('bad.md'));
167
+ assert.equal(typeof res.warnings[0].line, 'number');
168
+ } finally {
169
+ fs.rmSync(tmp, { recursive: true, force: true });
170
+ }
171
+ });
172
+
173
+ test('CW-M-3: workflow with no Task/Spawn sites → no warning', () => {
174
+ const tmp = mkTmp();
175
+ try {
176
+ const dir = path.join(tmp, 'workflows');
177
+ seed(dir, {
178
+ 'docs-only.md':
179
+ '# Pure documentation workflow\n\n' +
180
+ 'No bash, no Task, no Spawn. Just prose.\n',
181
+ });
182
+ const res = cw.checkWorkflows(dir);
183
+ assert.equal(res.exitCode, 0);
184
+ assert.deepEqual(res.violations, []);
185
+ assert.deepEqual(res.warnings, []);
186
+ } finally {
187
+ fs.rmSync(tmp, { recursive: true, force: true });
188
+ }
189
+ });
190
+
191
+ test('CW-M-4: Spawn agent= reference in a markdown table (prose) → NOT flagged', () => {
192
+ const tmp = mkTmp();
193
+ try {
194
+ const dir = path.join(tmp, 'workflows');
195
+ seed(dir, {
196
+ 'table.md':
197
+ '# Naming conventions\n\n' +
198
+ '| Legacy token | Canonical token |\n' +
199
+ '| ------------ | --------------- |\n' +
200
+ '| Task(…) | Spawn agent=… |\n',
201
+ });
202
+ const res = cw.checkWorkflows(dir);
203
+ assert.deepEqual(res.warnings, []);
204
+ assert.equal(res.exitCode, 0);
205
+ } finally {
206
+ fs.rmSync(tmp, { recursive: true, force: true });
207
+ }
208
+ });