nubos-pilot 0.6.2 → 0.6.4

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 (36) hide show
  1. package/agents/np-nyquist-auditor.md +1 -1
  2. package/agents/np-sc-extractor.md +1 -1
  3. package/bin/np-tools/_commands.cjs +8 -1
  4. package/bin/np-tools/phase-meta.cjs +102 -0
  5. package/bin/np-tools/phase-meta.test.cjs +134 -0
  6. package/bin/np-tools/render-template.cjs +56 -0
  7. package/bin/np-tools/render-template.test.cjs +113 -0
  8. package/bin/np-tools/research-phase.cjs +27 -5
  9. package/bin/np-tools/research-phase.test.cjs +53 -4
  10. package/bin/np-tools/session-aggregate.cjs +56 -0
  11. package/bin/np-tools/session-aggregate.test.cjs +120 -0
  12. package/bin/np-tools/session-pointer-write.cjs +55 -0
  13. package/bin/np-tools/session-pointer-write.test.cjs +62 -0
  14. package/bin/np-tools/state-dir.cjs +47 -0
  15. package/bin/np-tools/state-dir.test.cjs +72 -0
  16. package/bin/np-tools/state-incr.cjs +43 -0
  17. package/bin/np-tools/state-incr.test.cjs +100 -0
  18. package/bin/np-tools/thread-resume.cjs +83 -0
  19. package/bin/np-tools/thread-resume.test.cjs +134 -0
  20. package/bin/np-tools/workspace-scan.cjs +49 -0
  21. package/bin/np-tools/workspace-scan.test.cjs +77 -0
  22. package/lib/config-defaults.cjs +8 -1
  23. package/lib/roadmap-render.cjs +2 -12
  24. package/lib/roadmap.cjs +2 -12
  25. package/np-tools.cjs +8 -0
  26. package/package.json +1 -1
  27. package/templates/claude/payload/README.md +0 -2
  28. package/workflows/add-todo.md +1 -1
  29. package/workflows/discuss-phase.md +3 -19
  30. package/workflows/execute-phase.md +1 -1
  31. package/workflows/new-project.md +1 -14
  32. package/workflows/note.md +1 -1
  33. package/workflows/plan-phase.md +2 -2
  34. package/workflows/research-phase.md +1 -1
  35. package/workflows/session-report.md +7 -24
  36. package/workflows/thread.md +3 -27
@@ -0,0 +1,134 @@
1
+ 'use strict';
2
+
3
+ const { test } = require('node:test');
4
+ const assert = require('node:assert/strict');
5
+ const fs = require('node:fs');
6
+ const os = require('node:os');
7
+ const path = require('node:path');
8
+
9
+ const mod = require('./thread-resume.cjs');
10
+
11
+ function mkSandbox(fmLines, body) {
12
+ const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'np-tr-'));
13
+ const p = path.join(dir, 'thread.md');
14
+ const content = '---\n' + fmLines.join('\n') + '\n---\n' + (body || '# body\n');
15
+ fs.writeFileSync(p, content);
16
+ return { dir, p };
17
+ }
18
+
19
+ function captureStdout() {
20
+ const chunks = [];
21
+ return {
22
+ stream: { write: (c) => { chunks.push(c); } },
23
+ read: () => chunks.join(''),
24
+ };
25
+ }
26
+
27
+ test('TR-1: OPEN bumps to IN_PROGRESS', () => {
28
+ assert.equal(mod._bumpStatus('OPEN'), 'IN_PROGRESS');
29
+ });
30
+
31
+ test('TR-2: IN_PROGRESS stays IN_PROGRESS', () => {
32
+ assert.equal(mod._bumpStatus('IN_PROGRESS'), 'IN_PROGRESS');
33
+ });
34
+
35
+ test('TR-3: RESOLVED stays RESOLVED (monotonic)', () => {
36
+ assert.equal(mod._bumpStatus('RESOLVED'), 'RESOLVED');
37
+ });
38
+
39
+ test('TR-4: run() bumps OPEN and writes last_resumed', () => {
40
+ const { dir, p } = mkSandbox([
41
+ 'slug: foo',
42
+ 'status: OPEN',
43
+ 'created: 2026-04-01',
44
+ 'last_resumed: 2026-04-01',
45
+ ], '# Thread: foo\n');
46
+ try {
47
+ const cap = captureStdout();
48
+ const rc = mod.run([p, '--today', '2026-04-22'], { cwd: dir, stdout: cap.stream });
49
+ assert.equal(rc, 0);
50
+ const written = fs.readFileSync(p, 'utf-8');
51
+ assert.match(written, /status: IN_PROGRESS/);
52
+ assert.match(written, /last_resumed: 2026-04-22/);
53
+ const out = JSON.parse(cap.read());
54
+ assert.equal(out.status, 'IN_PROGRESS');
55
+ } finally {
56
+ fs.rmSync(dir, { recursive: true, force: true });
57
+ }
58
+ });
59
+
60
+ test('TR-5: RESOLVED stays RESOLVED across resume', () => {
61
+ const { dir, p } = mkSandbox([
62
+ 'slug: bar',
63
+ 'status: RESOLVED',
64
+ 'created: 2026-04-01',
65
+ 'last_resumed: 2026-04-10',
66
+ ], '# body\n');
67
+ try {
68
+ const cap = captureStdout();
69
+ mod.run([p, '--today', '2026-04-22'], { cwd: dir, stdout: cap.stream });
70
+ const written = fs.readFileSync(p, 'utf-8');
71
+ assert.match(written, /status: RESOLVED/);
72
+ assert.match(written, /last_resumed: 2026-04-22/);
73
+ } finally {
74
+ fs.rmSync(dir, { recursive: true, force: true });
75
+ }
76
+ });
77
+
78
+ test('TR-6: stable field ordering (slug, status, created, last_resumed)', () => {
79
+ const { dir, p } = mkSandbox([
80
+ 'last_resumed: 2026-04-01',
81
+ 'created: 2026-04-01',
82
+ 'status: OPEN',
83
+ 'slug: baz',
84
+ ], '# body\n');
85
+ try {
86
+ const cap = captureStdout();
87
+ mod.run([p, '--today', '2026-04-22'], { cwd: dir, stdout: cap.stream });
88
+ const written = fs.readFileSync(p, 'utf-8');
89
+ const fmBlock = written.split('---')[1];
90
+ const idxSlug = fmBlock.indexOf('slug:');
91
+ const idxStatus = fmBlock.indexOf('status:');
92
+ const idxCreated = fmBlock.indexOf('created:');
93
+ const idxLast = fmBlock.indexOf('last_resumed:');
94
+ assert.ok(idxSlug < idxStatus && idxStatus < idxCreated && idxCreated < idxLast);
95
+ } finally {
96
+ fs.rmSync(dir, { recursive: true, force: true });
97
+ }
98
+ });
99
+
100
+ test('TR-7: missing path throws', () => {
101
+ assert.throws(
102
+ () => mod.run([], { cwd: '/tmp', stdout: { write: () => {} } }),
103
+ (err) => err.code === 'thread-resume-missing-path',
104
+ );
105
+ });
106
+
107
+ test('TR-8: nonexistent file throws read-error', () => {
108
+ const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'np-tr-'));
109
+ try {
110
+ assert.throws(
111
+ () => mod.run([path.join(dir, 'nope.md')], { cwd: dir, stdout: { write: () => {} } }),
112
+ (err) => err.code === 'thread-resume-read-error',
113
+ );
114
+ } finally {
115
+ fs.rmSync(dir, { recursive: true, force: true });
116
+ }
117
+ });
118
+
119
+ test('TR-9: default today is current ISO date', () => {
120
+ const { dir, p } = mkSandbox([
121
+ 'slug: x',
122
+ 'status: OPEN',
123
+ 'created: 2026-04-01',
124
+ 'last_resumed: 2026-04-01',
125
+ ], '');
126
+ try {
127
+ const cap = captureStdout();
128
+ mod.run([p], { cwd: dir, stdout: cap.stream });
129
+ const out = JSON.parse(cap.read());
130
+ assert.match(out.last_resumed, /^\d{4}-\d{2}-\d{2}$/);
131
+ } finally {
132
+ fs.rmSync(dir, { recursive: true, force: true });
133
+ }
134
+ });
@@ -0,0 +1,49 @@
1
+ 'use strict';
2
+
3
+ const { NubosPilotError } = require('../../lib/core.cjs');
4
+ const { scan } = require('../../lib/workspace-scan.cjs');
5
+
6
+ function _parseArgs(args) {
7
+ const out = { batchSize: 1000, summary: false };
8
+ for (let i = 0; i < args.length; i++) {
9
+ const a = args[i];
10
+ if (a === '--batch-size' || a === '-b') {
11
+ const n = Number(args[++i]);
12
+ if (!Number.isFinite(n) || n <= 0) {
13
+ throw new NubosPilotError('workspace-scan-invalid-batch-size',
14
+ '--batch-size must be a positive integer', { raw: args[i] });
15
+ }
16
+ out.batchSize = n;
17
+ continue;
18
+ }
19
+ if (a === '--summary' || a === '-s') { out.summary = true; continue; }
20
+ }
21
+ return out;
22
+ }
23
+
24
+ function _projectSummary(r) {
25
+ const readmeHead = r.docs && r.docs['README.md']
26
+ ? r.docs['README.md'].content.split('\n').slice(0, 20).join('\n')
27
+ : null;
28
+ return {
29
+ file_count: r.stats.file_count,
30
+ langs: r.language_distribution,
31
+ manifests: Object.keys(r.manifests),
32
+ docs: Object.keys(r.docs),
33
+ readme_head: readmeHead,
34
+ git: r.git,
35
+ };
36
+ }
37
+
38
+ function run(args, opts) {
39
+ const o = opts || {};
40
+ const cwd = o.cwd || process.cwd();
41
+ const stdout = o.stdout || process.stdout;
42
+ const parsed = _parseArgs(args || []);
43
+ const result = scan({ cwd, batchSize: parsed.batchSize });
44
+ const payload = parsed.summary ? _projectSummary(result) : result;
45
+ stdout.write(JSON.stringify(payload));
46
+ return 0;
47
+ }
48
+
49
+ module.exports = { run, _parseArgs, _projectSummary };
@@ -0,0 +1,77 @@
1
+ 'use strict';
2
+
3
+ const { test } = require('node:test');
4
+ const assert = require('node:assert/strict');
5
+ const fs = require('node:fs');
6
+ const os = require('node:os');
7
+ const path = require('node:path');
8
+
9
+ const mod = require('./workspace-scan.cjs');
10
+
11
+ function mkSandbox() {
12
+ const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'np-ws-'));
13
+ fs.writeFileSync(path.join(dir, 'README.md'), '# Test\n\nLine 1\nLine 2\n');
14
+ fs.writeFileSync(path.join(dir, 'index.js'), 'console.log("hi");\n');
15
+ fs.writeFileSync(path.join(dir, 'package.json'), '{"name":"x"}\n');
16
+ return dir;
17
+ }
18
+
19
+ function captureStdout() {
20
+ const chunks = [];
21
+ return {
22
+ stream: { write: (c) => { chunks.push(c); } },
23
+ read: () => chunks.join(''),
24
+ };
25
+ }
26
+
27
+ test('WS-1: default run emits full scan result JSON', () => {
28
+ const dir = mkSandbox();
29
+ try {
30
+ const cap = captureStdout();
31
+ const rc = mod.run([], { cwd: dir, stdout: cap.stream });
32
+ assert.equal(rc, 0);
33
+ const out = JSON.parse(cap.read());
34
+ assert.ok(out.stats);
35
+ assert.ok(out.language_distribution);
36
+ assert.equal(typeof out.stats.file_count, 'number');
37
+ } finally {
38
+ fs.rmSync(dir, { recursive: true, force: true });
39
+ }
40
+ });
41
+
42
+ test('WS-2: --summary emits the new-project shape', () => {
43
+ const dir = mkSandbox();
44
+ try {
45
+ const cap = captureStdout();
46
+ const rc = mod.run(['--summary'], { cwd: dir, stdout: cap.stream });
47
+ assert.equal(rc, 0);
48
+ const out = JSON.parse(cap.read());
49
+ assert.ok('file_count' in out);
50
+ assert.ok('langs' in out);
51
+ assert.ok('manifests' in out);
52
+ assert.ok('docs' in out);
53
+ assert.ok('readme_head' in out);
54
+ assert.ok('git' in out);
55
+ assert.match(out.readme_head, /^# Test/);
56
+ } finally {
57
+ fs.rmSync(dir, { recursive: true, force: true });
58
+ }
59
+ });
60
+
61
+ test('WS-3: --batch-size accepts positive integer', () => {
62
+ assert.deepEqual(mod._parseArgs(['--batch-size', '500']), { batchSize: 500, summary: false });
63
+ });
64
+
65
+ test('WS-4: --batch-size rejects non-positive', () => {
66
+ assert.throws(
67
+ () => mod._parseArgs(['--batch-size', '0']),
68
+ (err) => err.code === 'workspace-scan-invalid-batch-size',
69
+ );
70
+ });
71
+
72
+ test('WS-5: --batch-size rejects NaN', () => {
73
+ assert.throws(
74
+ () => mod._parseArgs(['--batch-size', 'ten']),
75
+ (err) => err.code === 'workspace-scan-invalid-batch-size',
76
+ );
77
+ });
@@ -1,8 +1,14 @@
1
1
  'use strict';
2
2
 
3
+ const DEFAULT_RESEARCH_TOOLS = Object.freeze({
4
+ WebFetch: true,
5
+ Context7: true,
6
+ });
7
+
3
8
  const DEFAULT_WORKFLOW = Object.freeze({
4
9
  commit_docs: true,
5
10
  commit_artifacts: true,
11
+ research_tools: DEFAULT_RESEARCH_TOOLS,
6
12
  });
7
13
 
8
14
  const DEFAULT_AGENTS = Object.freeze({
@@ -25,13 +31,14 @@ function buildInstallConfig(answers) {
25
31
  mcp: !!a.mcp,
26
32
  model_profile: a.model_profile || DEFAULT_MODEL_PROFILE,
27
33
  response_language: a.response_language || DEFAULT_RESPONSE_LANGUAGE,
28
- workflow: { ...DEFAULT_WORKFLOW },
34
+ workflow: { ...DEFAULT_WORKFLOW, research_tools: { ...DEFAULT_RESEARCH_TOOLS } },
29
35
  agents: { ...DEFAULT_AGENTS },
30
36
  };
31
37
  }
32
38
 
33
39
  module.exports = {
34
40
  DEFAULT_WORKFLOW,
41
+ DEFAULT_RESEARCH_TOOLS,
35
42
  DEFAULT_AGENTS,
36
43
  DEFAULT_MODEL_PROFILE,
37
44
  DEFAULT_SCOPE,
@@ -11,21 +11,11 @@ const {
11
11
  const GENERATED_HEADER = '<!-- Generated from roadmap.yaml — do not edit by hand -->';
12
12
 
13
13
  function _yamlPath(cwd) {
14
- try {
15
- return path.join(projectStateDir(cwd), 'roadmap.yaml');
16
- } catch (err) {
17
- if (!err || err.code !== 'not-in-project') throw err;
18
- return path.join(path.resolve(cwd), '.planning', 'roadmap.yaml');
19
- }
14
+ return path.join(projectStateDir(cwd), 'roadmap.yaml');
20
15
  }
21
16
 
22
17
  function _mdPath(cwd) {
23
- try {
24
- return path.join(projectStateDir(cwd), 'ROADMAP.md');
25
- } catch (err) {
26
- if (!err || err.code !== 'not-in-project') throw err;
27
- return path.join(path.resolve(cwd), '.planning', 'ROADMAP.md');
28
- }
18
+ return path.join(projectStateDir(cwd), 'ROADMAP.md');
29
19
  }
30
20
 
31
21
  function _readYaml(p) {
package/lib/roadmap.cjs CHANGED
@@ -10,12 +10,7 @@ const {
10
10
  const { renderMarkdown } = require('./roadmap-render.cjs');
11
11
 
12
12
  function roadmapPath(cwd) {
13
- try {
14
- return path.join(projectStateDir(cwd), 'roadmap.yaml');
15
- } catch (err) {
16
- if (!err || err.code !== 'not-in-project') throw err;
17
- return path.join(path.resolve(cwd), '.planning', 'roadmap.yaml');
18
- }
13
+ return path.join(projectStateDir(cwd), 'roadmap.yaml');
19
14
  }
20
15
 
21
16
  function _readRaw(p) {
@@ -159,12 +154,7 @@ const _MAX_ROADMAP_BYTES = 1024 * 1024;
159
154
  const _SLUG_RE = /^[a-z0-9-]+$/;
160
155
 
161
156
  function _mdPath(cwd) {
162
- try {
163
- return path.join(projectStateDir(cwd), 'ROADMAP.md');
164
- } catch (err) {
165
- if (!err || err.code !== 'not-in-project') throw err;
166
- return path.join(path.resolve(cwd), '.planning', 'ROADMAP.md');
167
- }
157
+ return path.join(projectStateDir(cwd), 'ROADMAP.md');
168
158
  }
169
159
 
170
160
  function _mutate(cwd, fn) {
package/np-tools.cjs CHANGED
@@ -51,6 +51,14 @@ const topLevelCommands = {
51
51
  'detect-runtime': require('./bin/np-tools/detect-runtime.cjs'),
52
52
  'template-path': require('./bin/np-tools/template-path.cjs'),
53
53
  'update-phase-meta': require('./bin/np-tools/update-phase-meta.cjs'),
54
+ 'phase-meta': require('./bin/np-tools/phase-meta.cjs'),
55
+ 'state-dir': require('./bin/np-tools/state-dir.cjs'),
56
+ 'render-template': require('./bin/np-tools/render-template.cjs'),
57
+ 'thread-resume': require('./bin/np-tools/thread-resume.cjs'),
58
+ 'state-incr': require('./bin/np-tools/state-incr.cjs'),
59
+ 'session-aggregate': require('./bin/np-tools/session-aggregate.cjs'),
60
+ 'session-pointer-write': require('./bin/np-tools/session-pointer-write.cjs'),
61
+ 'workspace-scan': require('./bin/np-tools/workspace-scan.cjs'),
54
62
  };
55
63
 
56
64
  const THRESHOLD = 16 * 1024;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nubos-pilot",
3
- "version": "0.6.2",
3
+ "version": "0.6.4",
4
4
  "description": "AI-driven planning and execution tool for code projects",
5
5
  "homepage": "https://github.com/Nubos-AI/nubos-pilot",
6
6
  "repository": {
@@ -7,5 +7,3 @@ Subdirectories (populated in Phase 8 and onwards):
7
7
  - `agents/` — agent `.md` files
8
8
  - `hooks/` — hook scripts
9
9
  - `templates/` — scaffolding templates
10
-
11
- See `.planning/phases/07-install-npx-flow/07-RESEARCH.md` §Recommended Project Structure for the full layout.
@@ -131,7 +131,7 @@ reads of the project state directory from this workflow would bypass
131
131
  the lock and are explicitly forbidden by the check-workflows lint.
132
132
 
133
133
  ```bash
134
- node -e "require('./lib/state.cjs').mutateState(function (doc) { doc.frontmatter.pending_todos = (doc.frontmatter.pending_todos || 0) + 1; return doc; });"
134
+ node .nubos-pilot/bin/np-tools.cjs state-incr pending_todos > /dev/null
135
135
  ```
136
136
 
137
137
  The mutator increments the `pending_todos` counter on the STATE.md
@@ -340,14 +340,7 @@ CONTEXT_PATH=$(echo "$INIT" | node -e 'let d="";process.stdin.on("data",c=>d+=c)
340
340
  mkdir -p "$MILESTONE_DIR"
341
341
  mkdir -p "$MILESTONE_DIR/slices"
342
342
 
343
- TPL_PATH=$(node .nubos-pilot/bin/np-tools.cjs template-path milestone/CONTEXT)
344
- node -e '
345
- const { render } = require("./lib/template.cjs");
346
- const fs = require("node:fs");
347
- const tpl = fs.readFileSync(process.argv[1], "utf-8");
348
- const vars = JSON.parse(process.argv[2]);
349
- process.stdout.write(render(tpl, vars));
350
- ' "$TPL_PATH" "$VARS_JSON" > "$CONTEXT_PATH"
343
+ node .nubos-pilot/bin/np-tools.cjs render-template milestone/CONTEXT --vars "$VARS_JSON" > "$CONTEXT_PATH"
351
344
  ```
352
345
 
353
346
  `$VARS_JSON` is the JSON-serialised accumulator from Steps 2–5 (keys map to
@@ -379,13 +372,8 @@ SC_START=$(node .nubos-pilot/bin/np-tools.cjs metrics start-timestamp)
379
372
  SC_MODEL=$(node .nubos-pilot/bin/np-tools.cjs resolve-model np-sc-extractor --profile balanced)
380
373
 
381
374
  REQS_PATH=".nubos-pilot/REQUIREMENTS.md"
382
- [[ -f "$REQS_PATH" ]] || REQS_PATH=".planning/REQUIREMENTS.md"
383
375
 
384
- EXISTING_SC_JSON=$(node -e '
385
- const r = require("./lib/roadmap.cjs");
386
- const p = r.getPhase(process.argv[1]);
387
- process.stdout.write(JSON.stringify(p.success_criteria || []));
388
- ' "$PHASE")
376
+ EXISTING_SC_JSON=$(node .nubos-pilot/bin/np-tools.cjs phase-meta "$PHASE" --field success_criteria)
389
377
 
390
378
  # Spawn agent=np-sc-extractor tier=haiku model=$SC_MODEL milestone=$PHASE
391
379
  # input: milestone=$PHASE, milestone_id=$MILESTONE_ID, milestone_dir=$MILESTONE_DIR,
@@ -406,11 +394,7 @@ node .nubos-pilot/bin/np-tools.cjs metrics record \
406
394
  After the spawn, sanity-check that `success_criteria` is non-empty:
407
395
 
408
396
  ```bash
409
- SC_COUNT=$(node -e '
410
- const r = require("./lib/roadmap.cjs");
411
- const p = r.getPhase(process.argv[1]);
412
- process.stdout.write(String((p.success_criteria || []).length));
413
- ' "$PHASE")
397
+ SC_COUNT=$(node .nubos-pilot/bin/np-tools.cjs phase-meta "$PHASE" --field success_criteria --length)
414
398
  if [[ "$SC_COUNT" -lt 1 ]]; then
415
399
  echo "ERROR: np-sc-extractor produced no success_criteria for $MILESTONE_ID — refusing to continue." >&2
416
400
  exit 1
@@ -106,7 +106,7 @@ for WAVE_INDEX in 0 1 2 ...; do
106
106
  if [[ "$TASK_JSON" == @file:* ]]; then TASK_JSON=$(cat "${TASK_JSON#@file:}"); fi
107
107
 
108
108
  EXECUTOR_START=$(node .nubos-pilot/bin/np-tools.cjs metrics start-timestamp)
109
- EXECUTOR_MODEL=$(node .nubos-pilot/bin/np-tools.cjs resolve-model executor --profile frontier)
109
+ EXECUTOR_MODEL=$(node .nubos-pilot/bin/np-tools.cjs resolve-model np-executor --profile frontier)
110
110
 
111
111
  # Spawn agents/np-executor.md (tier: sonnet, model resolved as $EXECUTOR_MODEL)
112
112
  # with a <files_to_read> block containing: the task plan file, the slice
@@ -75,20 +75,7 @@ still has `_TBD` placeholders.
75
75
  Probe the workspace for context before asking anything:
76
76
 
77
77
  ```bash
78
- SCAN=$(node -e '
79
- const { scan } = require("./lib/workspace-scan.cjs");
80
- const r = scan({ cwd: process.cwd(), batchSize: 1000 });
81
- process.stdout.write(JSON.stringify({
82
- file_count: r.stats.file_count,
83
- langs: r.language_distribution,
84
- manifests: Object.keys(r.manifests),
85
- docs: Object.keys(r.docs),
86
- readme_head: r.docs["README.md"]
87
- ? r.docs["README.md"].content.split("\\n").slice(0, 20).join("\\n")
88
- : null,
89
- git: r.git,
90
- }));
91
- ')
78
+ SCAN=$(node .nubos-pilot/bin/np-tools.cjs workspace-scan --summary --batch-size 1000)
92
79
  ```
93
80
 
94
81
  Show findings to the user and offer pre-filled suggestions:
package/workflows/note.md CHANGED
@@ -74,7 +74,7 @@ sidesteps that resolver and hardcodes `HOME + /.nubos-pilot/notes`.
74
74
  if [[ "$SCOPE" == "global" ]]; then
75
75
  NOTES_DIR="$HOME/.nubos-pilot/notes"
76
76
  else
77
- NOTES_DIR=$(node -e "console.log(require('./lib/core.cjs').projectStateDir(process.cwd()) + '/notes')")
77
+ NOTES_DIR=$(node .nubos-pilot/bin/np-tools.cjs state-dir --subdir notes)
78
78
  fi
79
79
  mkdir -p "$NOTES_DIR"
80
80
  DATE=$(date +%Y-%m-%d)
@@ -215,7 +215,7 @@ for ITER in 1 2; do
215
215
 
216
216
  # --- Spawn planner ---
217
217
  PLANNER_START=$(node .nubos-pilot/bin/np-tools.cjs metrics start-timestamp)
218
- PLANNER_MODEL=$(node .nubos-pilot/bin/np-tools.cjs resolve-model planner --profile frontier)
218
+ PLANNER_MODEL=$(node .nubos-pilot/bin/np-tools.cjs resolve-model np-planner --profile frontier)
219
219
  # Spawn agent=np-planner tier=opus model=$PLANNER_MODEL mode=$MODE milestone=$PHASE
220
220
  # milestone_dir=$milestone_dir goal=$goal requirements=$requirements
221
221
  # prior_findings=$LAST_FINDINGS agent_skills=$AGENT_SKILLS_PLANNER
@@ -229,7 +229,7 @@ for ITER in 1 2; do
229
229
 
230
230
  # --- Spawn plan-checker ---
231
231
  CHECKER_START=$(node .nubos-pilot/bin/np-tools.cjs metrics start-timestamp)
232
- CHECKER_MODEL=$(node .nubos-pilot/bin/np-tools.cjs resolve-model plan-checker --profile frontier)
232
+ CHECKER_MODEL=$(node .nubos-pilot/bin/np-tools.cjs resolve-model np-plan-checker --profile frontier)
233
233
  # Spawn agent=np-plan-checker tier=opus model=$CHECKER_MODEL milestone=$PHASE
234
234
  # milestone_dir=$milestone_dir agent_skills=$AGENT_SKILLS_CHECKER
235
235
  # Checker writes YAML verdict; orchestrator converts to JSON.
@@ -209,7 +209,7 @@ omit the `model:` parameter at spawn (Phase 8 D-22 inherit-pattern).
209
209
 
210
210
  ```bash
211
211
  RESEARCHER_START=$(node .nubos-pilot/bin/np-tools.cjs metrics start-timestamp)
212
- RESEARCHER_MODEL=$(node .nubos-pilot/bin/np-tools.cjs resolve-model researcher --profile balanced)
212
+ RESEARCHER_MODEL=$(node .nubos-pilot/bin/np-tools.cjs resolve-model np-researcher --profile balanced)
213
213
  ```
214
214
 
215
215
  ```text
@@ -40,7 +40,7 @@ for arg in "$@"; do
40
40
  esac
41
41
  done
42
42
 
43
- STATE_DIR=$(node -e "console.log(require('./lib/core.cjs').projectStateDir(process.cwd()))")
43
+ STATE_DIR=$(node .nubos-pilot/bin/np-tools.cjs state-dir)
44
44
  REPORTS_DIR="${STATE_DIR}/reports"
45
45
  POINTER="${REPORTS_DIR}/.last-session"
46
46
  mkdir -p "$REPORTS_DIR"
@@ -80,21 +80,11 @@ between "read pointer" and "write new pointer" (T-10-06-02 / Pitfall
80
80
  longer hit `lock-timeout` from `lib/core.cjs.NubosPilotError`.
81
81
 
82
82
  ```bash
83
- REPORT_JSON=$(node -e '
84
- const fs = require("node:fs");
85
- const { withFileLock } = require("./lib/core.cjs");
86
- const { aggregateSession } = require("./lib/metrics-aggregate.cjs");
87
- const pointer = process.argv[1];
88
- const override = process.argv[2] || "";
89
- const done = withFileLock(pointer, async () => {
90
- let since = override || "";
91
- if (!override && fs.existsSync(pointer)) {
92
- since = fs.readFileSync(pointer, "utf-8").trim();
93
- }
94
- return aggregateSession(since || null, { cwd: process.cwd() });
95
- }, { timeoutMs: 10000 });
96
- Promise.resolve(done).then((r) => process.stdout.write(JSON.stringify(r)));
97
- ' "$POINTER" "$SINCE_OVERRIDE")
83
+ if [[ -n "$SINCE_OVERRIDE" ]]; then
84
+ REPORT_JSON=$(node .nubos-pilot/bin/np-tools.cjs session-aggregate --since "$SINCE_OVERRIDE")
85
+ else
86
+ REPORT_JSON=$(node .nubos-pilot/bin/np-tools.cjs session-aggregate)
87
+ fi
98
88
  ```
99
89
 
100
90
  The `aggregateSession` helper returns
@@ -150,14 +140,7 @@ and "update pointer" leaves the pointer STALE — the next run
150
140
  re-covers the missing period (safe-by-default).
151
141
 
152
142
  ```bash
153
- node -e '
154
- const { withFileLock, atomicWriteFileSync } = require("./lib/core.cjs");
155
- withFileLock(
156
- process.argv[1],
157
- () => atomicWriteFileSync(process.argv[1], process.argv[2]),
158
- { timeoutMs: 10000 }
159
- );
160
- ' "$POINTER" "$NOW_ISO"
143
+ node .nubos-pilot/bin/np-tools.cjs session-pointer-write "$NOW_ISO" > /dev/null
161
144
  ```
162
145
 
163
146
  Using `atomicWriteFileSync` ensures the pointer update is crash-safe
@@ -35,7 +35,7 @@ if [[ -z "$SLUG" ]]; then
35
35
  echo "Error: argument produced no slug-safe characters." >&2
36
36
  exit 1
37
37
  fi
38
- STATE_DIR=$(node -e "console.log(require('./lib/core.cjs').projectStateDir(process.cwd()))")
38
+ STATE_DIR=$(node .nubos-pilot/bin/np-tools.cjs state-dir)
39
39
  THREADS_DIR="${STATE_DIR}/threads"
40
40
  THREAD_PATH="${THREADS_DIR}/${SLUG}.md"
41
41
  TODAY=$(date +%Y-%m-%d)
@@ -119,35 +119,11 @@ order.
119
119
 
120
120
  ```bash
121
121
  if [[ "$MODE" == "resume" ]]; then
122
- node -e "
123
- const fs = require('node:fs');
124
- const { extractFrontmatter } = require('./lib/frontmatter.cjs');
125
- const { atomicWriteFileSync } = require('./lib/core.cjs');
126
- const p = process.argv[1];
127
- const today = process.argv[2];
128
- const raw = fs.readFileSync(p, 'utf-8');
129
- const { frontmatter, body } = extractFrontmatter(raw);
130
- const next = Object.assign({}, frontmatter);
131
- const cur = String(next.status || 'OPEN');
132
- if (cur === 'OPEN') next.status = 'IN_PROGRESS';
133
- next.last_resumed = today;
134
- const order = ['slug', 'status', 'created', 'last_resumed'];
135
- const seen = new Set();
136
- const lines = ['---'];
137
- for (const k of order) {
138
- if (k in next) { lines.push(k + ': ' + next[k]); seen.add(k); }
139
- }
140
- for (const k of Object.keys(next)) {
141
- if (!seen.has(k)) lines.push(k + ': ' + next[k]);
142
- }
143
- lines.push('---');
144
- const out = lines.join('\n') + '\n' + body;
145
- atomicWriteFileSync(p, out);
146
- " "$THREAD_PATH" "$TODAY"
122
+ node .nubos-pilot/bin/np-tools.cjs thread-resume "$THREAD_PATH" --today "$TODAY" > /dev/null
147
123
  echo "Thread resumed: $THREAD_PATH"
148
124
  echo ""
149
125
  echo "--- thread content ---"
150
- node -e "process.stdout.write(require('node:fs').readFileSync(process.argv[1], 'utf-8'))" "$THREAD_PATH"
126
+ cat "$THREAD_PATH"
151
127
  fi
152
128
  ```
153
129