nubos-pilot 0.6.2 → 0.6.3
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.
- package/agents/np-nyquist-auditor.md +1 -1
- package/agents/np-sc-extractor.md +1 -1
- package/bin/np-tools/_commands.cjs +8 -1
- package/bin/np-tools/phase-meta.cjs +102 -0
- package/bin/np-tools/phase-meta.test.cjs +134 -0
- package/bin/np-tools/render-template.cjs +56 -0
- package/bin/np-tools/render-template.test.cjs +113 -0
- package/bin/np-tools/research-phase.cjs +27 -5
- package/bin/np-tools/research-phase.test.cjs +53 -4
- package/bin/np-tools/session-aggregate.cjs +56 -0
- package/bin/np-tools/session-aggregate.test.cjs +120 -0
- package/bin/np-tools/session-pointer-write.cjs +55 -0
- package/bin/np-tools/session-pointer-write.test.cjs +62 -0
- package/bin/np-tools/state-dir.cjs +47 -0
- package/bin/np-tools/state-dir.test.cjs +72 -0
- package/bin/np-tools/state-incr.cjs +43 -0
- package/bin/np-tools/state-incr.test.cjs +100 -0
- package/bin/np-tools/thread-resume.cjs +83 -0
- package/bin/np-tools/thread-resume.test.cjs +134 -0
- package/bin/np-tools/workspace-scan.cjs +49 -0
- package/bin/np-tools/workspace-scan.test.cjs +77 -0
- package/lib/config-defaults.cjs +8 -1
- package/lib/roadmap-render.cjs +2 -12
- package/lib/roadmap.cjs +2 -12
- package/np-tools.cjs +8 -0
- package/package.json +1 -1
- package/templates/claude/payload/README.md +0 -2
- package/workflows/add-todo.md +1 -1
- package/workflows/discuss-phase.md +3 -19
- package/workflows/new-project.md +1 -14
- package/workflows/note.md +1 -1
- package/workflows/session-report.md +7 -24
- 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
|
+
});
|
package/lib/config-defaults.cjs
CHANGED
|
@@ -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,
|
package/lib/roadmap-render.cjs
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
package/workflows/add-todo.md
CHANGED
|
@@ -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 -
|
|
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
|
-
|
|
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 -
|
|
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 -
|
|
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
|
package/workflows/new-project.md
CHANGED
|
@@ -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 -
|
|
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 -
|
|
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)
|
|
@@ -40,7 +40,7 @@ for arg in "$@"; do
|
|
|
40
40
|
esac
|
|
41
41
|
done
|
|
42
42
|
|
|
43
|
-
STATE_DIR=$(node -
|
|
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
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
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 -
|
|
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
|
package/workflows/thread.md
CHANGED
|
@@ -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 -
|
|
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 -
|
|
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
|
-
|
|
126
|
+
cat "$THREAD_PATH"
|
|
151
127
|
fi
|
|
152
128
|
```
|
|
153
129
|
|