nubos-pilot 1.1.3 → 1.2.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.
- package/CHANGELOG.md +23 -0
- package/LICENSE +21 -0
- package/README.md +30 -1
- package/SECURITY.md +60 -0
- package/bin/install.js +104 -39
- package/bin/np-tools/_args.cjs +8 -2
- package/bin/np-tools/_memory-resolve.cjs +4 -4
- package/bin/np-tools/checkpoint.cjs +1 -1
- package/bin/np-tools/close-project.cjs +3 -29
- package/bin/np-tools/commit-task.cjs +31 -35
- package/bin/np-tools/commit.cjs +0 -3
- package/bin/np-tools/config.cjs +4 -13
- package/bin/np-tools/discuss-phase.cjs +4 -27
- package/bin/np-tools/doctor.cjs +76 -16
- package/bin/np-tools/doctor.test.cjs +14 -0
- package/bin/np-tools/execute-milestone.cjs +6 -27
- package/bin/np-tools/handoff-write.cjs +16 -2
- package/bin/np-tools/init-dispatch.test.cjs +21 -0
- package/bin/np-tools/knowledge-search.cjs +0 -3
- package/bin/np-tools/learning-list.cjs +0 -2
- package/bin/np-tools/learning-log.cjs +1 -7
- package/bin/np-tools/loop-audit-tool-use.cjs +1 -11
- package/bin/np-tools/loop-run-round.cjs +51 -148
- package/bin/np-tools/loop-state-read.cjs +1 -5
- package/bin/np-tools/loop-state-record.cjs +1 -27
- package/bin/np-tools/loop-stuck.cjs +1 -8
- package/bin/np-tools/messages-send.cjs +16 -2
- package/bin/np-tools/metrics.test.cjs +4 -4
- package/bin/np-tools/new-milestone.cjs +14 -3
- package/bin/np-tools/new-project.cjs +4 -2
- package/bin/np-tools/new-project.test.cjs +12 -0
- package/bin/np-tools/park.cjs +2 -1
- package/bin/np-tools/plan-lint.cjs +0 -19
- package/bin/np-tools/plan-milestone.cjs +8 -29
- package/bin/np-tools/propose-milestones.cjs +14 -3
- package/bin/np-tools/propose-milestones.test.cjs +27 -0
- package/bin/np-tools/research-phase.cjs +7 -37
- package/bin/np-tools/researcher-reconcile.cjs +3 -21
- package/bin/np-tools/reset-slice.cjs +10 -16
- package/bin/np-tools/resolve-model.cjs +21 -26
- package/bin/np-tools/resolve-model.test.cjs +15 -5
- package/bin/np-tools/resume-work.cjs +1 -5
- package/bin/np-tools/skip.cjs +2 -1
- package/bin/np-tools/spawn-headless.cjs +138 -19
- package/bin/np-tools/spawn-headless.test.cjs +310 -0
- package/bin/np-tools/state.cjs +0 -1
- package/bin/np-tools/undo-task.cjs +2 -1
- package/bin/np-tools/undo.cjs +5 -3
- package/bin/np-tools/unpark.cjs +2 -1
- package/bin/np-tools/verify-work.cjs +82 -25
- package/bin/np-tools/verify-work.test.cjs +211 -1
- package/bin/researcher-merge.cjs +2 -1
- package/bin/researcher-merge.test.cjs +14 -0
- package/lib/agents-registry.cjs +32 -0
- package/lib/agents.cjs +14 -6
- package/lib/agents.test.cjs +44 -0
- package/lib/archive.cjs +102 -36
- package/lib/archive.test.cjs +115 -5
- package/lib/checkpoint.cjs +43 -23
- package/lib/checkpoint.test.cjs +67 -6
- package/lib/commit-policy.cjs +3 -1
- package/lib/commit-policy.test.cjs +6 -0
- package/lib/config-defaults.cjs +5 -1
- package/lib/config-defaults.test.cjs +71 -0
- package/lib/config-schema.cjs +204 -0
- package/lib/config-schema.test.cjs +148 -0
- package/lib/config.cjs +168 -14
- package/lib/config.test.cjs +234 -0
- package/lib/core.cjs +226 -52
- package/lib/core.test.cjs +193 -10
- package/lib/dashboard.cjs +0 -12
- package/lib/frontmatter.cjs +5 -0
- package/lib/git.cjs +34 -27
- package/lib/git.test.cjs +11 -3
- package/lib/handoff.cjs +16 -14
- package/lib/handoff.test.cjs +24 -0
- package/lib/ids.cjs +6 -0
- package/lib/init-emit.cjs +33 -0
- package/lib/install/claude-hooks.cjs +46 -25
- package/lib/install/claude-hooks.test.cjs +64 -0
- package/lib/install/manifest.cjs +19 -0
- package/lib/install/manifest.test.cjs +107 -0
- package/lib/knowledge-adapter.cjs +3 -49
- package/lib/learnings.cjs +3 -108
- package/lib/logger.cjs +157 -0
- package/lib/logger.test.cjs +159 -0
- package/lib/memory-index-usearch.cjs +9 -12
- package/lib/memory-provider-local.cjs +8 -0
- package/lib/memory.cjs +86 -27
- package/lib/memory.test.cjs +135 -0
- package/lib/messaging.cjs +155 -83
- package/lib/metrics-aggregate.cjs +26 -27
- package/lib/metrics.cjs +7 -3
- package/lib/metrics.test.cjs +6 -5
- package/lib/migrations.cjs +89 -0
- package/lib/migrations.test.cjs +82 -0
- package/lib/milestone-meta.cjs +70 -0
- package/lib/nubosloop-audit.cjs +41 -141
- package/lib/nubosloop.cjs +45 -149
- package/lib/plan-lint.cjs +0 -67
- package/lib/researcher-swarm.cjs +1 -62
- package/lib/roadmap-render.cjs +107 -33
- package/lib/roadmap-schema.cjs +42 -0
- package/lib/roadmap.cjs +93 -20
- package/lib/roadmap.test.cjs +215 -0
- package/lib/run-context.cjs +54 -0
- package/lib/run-context.test.cjs +53 -0
- package/lib/runtime/index.cjs +5 -10
- package/lib/runtime/index.test.cjs +8 -1
- package/lib/safe-path.cjs +156 -0
- package/lib/safe-path.test.cjs +164 -0
- package/lib/state.cjs +28 -10
- package/lib/state.test.cjs +72 -22
- package/lib/tasks.cjs +92 -14
- package/lib/tasks.test.cjs +65 -0
- package/lib/todo.cjs +7 -5
- package/lib/verify.cjs +44 -3
- package/lib/worktree.cjs +2 -2
- package/lib/yaml.cjs +44 -0
- package/lib/yaml.test.cjs +65 -0
- package/np-tools.cjs +25 -23
- package/package.json +5 -2
- package/workflows/research-phase.md +1 -1
|
@@ -1,17 +1,13 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
const fs = require('node:fs');
|
|
4
|
-
const path = require('node:path');
|
|
5
|
-
const os = require('node:os');
|
|
6
|
-
const crypto = require('node:crypto');
|
|
7
4
|
|
|
8
|
-
const { NubosPilotError
|
|
5
|
+
const { NubosPilotError } = require('../../lib/core.cjs');
|
|
6
|
+
const { emitInitPayload } = require('../../lib/init-emit.cjs');
|
|
9
7
|
const { getPhase } = require('../../lib/roadmap.cjs');
|
|
10
8
|
const layout = require('../../lib/layout.cjs');
|
|
11
9
|
const textMode = require('../../lib/text-mode.cjs');
|
|
12
10
|
|
|
13
|
-
const INLINE_THRESHOLD_BYTES = 16 * 1024;
|
|
14
|
-
|
|
15
11
|
function _parseArgs(args) {
|
|
16
12
|
const rest = [];
|
|
17
13
|
const flags = { assumptions: false };
|
|
@@ -51,25 +47,6 @@ function _agentSkills() {
|
|
|
51
47
|
return { planner: null };
|
|
52
48
|
}
|
|
53
49
|
|
|
54
|
-
function _emit(payload, stdout, cwd) {
|
|
55
|
-
const json = JSON.stringify(payload, null, 2);
|
|
56
|
-
if (Buffer.byteLength(json, 'utf-8') <= INLINE_THRESHOLD_BYTES) {
|
|
57
|
-
stdout.write(json);
|
|
58
|
-
return;
|
|
59
|
-
}
|
|
60
|
-
let tmpDir;
|
|
61
|
-
try {
|
|
62
|
-
tmpDir = path.join(projectStateDir(cwd), '.tmp');
|
|
63
|
-
fs.mkdirSync(tmpDir, { recursive: true });
|
|
64
|
-
} catch (_err) {
|
|
65
|
-
tmpDir = os.tmpdir();
|
|
66
|
-
}
|
|
67
|
-
const suffix = process.pid + '-' + crypto.randomBytes(4).toString('hex');
|
|
68
|
-
const tmpPath = path.join(tmpDir, 'init-discuss-phase-' + suffix + '.json');
|
|
69
|
-
fs.writeFileSync(tmpPath, json, 'utf-8');
|
|
70
|
-
stdout.write('@file:' + tmpPath);
|
|
71
|
-
}
|
|
72
|
-
|
|
73
50
|
function run(args, ctx) {
|
|
74
51
|
const context = ctx || {};
|
|
75
52
|
const cwd = context.cwd || process.cwd();
|
|
@@ -117,8 +94,8 @@ function run(args, ctx) {
|
|
|
117
94
|
agent_skills: _agentSkills(),
|
|
118
95
|
};
|
|
119
96
|
|
|
120
|
-
|
|
97
|
+
emitInitPayload(payload, stdout, cwd, 'discuss-phase');
|
|
121
98
|
return payload;
|
|
122
99
|
}
|
|
123
100
|
|
|
124
|
-
module.exports = { run, _parseArgs, _validateMilestoneArg
|
|
101
|
+
module.exports = { run, _parseArgs, _validateMilestoneArg };
|
package/bin/np-tools/doctor.cjs
CHANGED
|
@@ -5,6 +5,7 @@ const os = require('node:os');
|
|
|
5
5
|
const path = require('node:path');
|
|
6
6
|
|
|
7
7
|
const { NubosPilotError, atomicWriteFileSync } = require('../../lib/core.cjs');
|
|
8
|
+
const { safeYamlParse } = require('../../lib/yaml.cjs');
|
|
8
9
|
const manifestMod = require('../../lib/install/manifest.cjs');
|
|
9
10
|
const codexTomlMod = require('../../lib/install/codex-toml.cjs');
|
|
10
11
|
const runtimeAssetsMod = require('../../lib/install/runtime-assets.cjs');
|
|
@@ -20,14 +21,16 @@ const CODEX_CONFIG_PATH = path.join(os.homedir(), '.codex', 'config.toml');
|
|
|
20
21
|
const OPENCODE_LOCAL_PREFIX = '.opencode/nubos-pilot/';
|
|
21
22
|
|
|
22
23
|
function _readScope(projectRoot) {
|
|
23
|
-
const
|
|
24
|
-
|
|
24
|
+
const { readConfig, _CONFIG_PARSE_CODES } = require('../../lib/config.cjs');
|
|
25
|
+
let cfg;
|
|
25
26
|
try {
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
return 'local';
|
|
27
|
+
cfg = readConfig(projectRoot);
|
|
28
|
+
} catch (err) {
|
|
29
|
+
if (err && err.code === 'not-in-project') return 'local';
|
|
30
|
+
if (err && _CONFIG_PARSE_CODES.has(err.code)) return 'local';
|
|
31
|
+
throw err;
|
|
30
32
|
}
|
|
33
|
+
return cfg && cfg.scope === 'global' ? 'global' : 'local';
|
|
31
34
|
}
|
|
32
35
|
|
|
33
36
|
function _payloadBaseFor(projectRoot, scope) {
|
|
@@ -281,8 +284,7 @@ function _checkMilestoneLayout(projectRoot) {
|
|
|
281
284
|
if (!fs.existsSync(roadmapPath)) return [];
|
|
282
285
|
let doc;
|
|
283
286
|
try {
|
|
284
|
-
|
|
285
|
-
doc = YAML.parse(fs.readFileSync(roadmapPath, 'utf-8'));
|
|
287
|
+
doc = safeYamlParse(fs.readFileSync(roadmapPath, 'utf-8'), { kind: 'doctor-roadmap' });
|
|
286
288
|
} catch {
|
|
287
289
|
return [{
|
|
288
290
|
id: 'roadmap-unreadable',
|
|
@@ -321,7 +323,6 @@ function _checkMilestoneLayout(projectRoot) {
|
|
|
321
323
|
}
|
|
322
324
|
}
|
|
323
325
|
|
|
324
|
-
// Flag old .nubos-pilot/phases/ dir as stale if still present
|
|
325
326
|
const phasesDir = path.join(stateDir, 'phases');
|
|
326
327
|
if (fs.existsSync(phasesDir)) {
|
|
327
328
|
issues.push({
|
|
@@ -337,10 +338,6 @@ function _checkMilestoneLayout(projectRoot) {
|
|
|
337
338
|
return issues;
|
|
338
339
|
}
|
|
339
340
|
|
|
340
|
-
// Single-critic revision (ADR-0010 §Single-Critic Revision 2026-05-05): one
|
|
341
|
-
// np-critic spawned per round, with three audit-surface modules loaded as
|
|
342
|
-
// <files_to_read>. The doctor checks that all four files are present —
|
|
343
|
-
// missing the spawnable critic OR any of the three modules breaks the loop.
|
|
344
341
|
const NUBOSLOOP_CRITICS = [
|
|
345
342
|
'np-critic', // spawnable (sonnet)
|
|
346
343
|
'np-critic-style', // axis module (Style)
|
|
@@ -426,10 +423,28 @@ function _checkNubosloopKnowledgeStore(projectRoot) {
|
|
|
426
423
|
|
|
427
424
|
function _checkNubosloopConfig(projectRoot) {
|
|
428
425
|
const issues = [];
|
|
429
|
-
const
|
|
430
|
-
if (!fs.existsSync(cfgPath)) return issues;
|
|
426
|
+
const { readConfig, _CONFIG_PARSE_CODES } = require('../../lib/config.cjs');
|
|
431
427
|
let cfg;
|
|
432
|
-
try {
|
|
428
|
+
try {
|
|
429
|
+
cfg = readConfig(projectRoot);
|
|
430
|
+
} catch (err) {
|
|
431
|
+
if (err && err.code === 'not-in-project') return issues;
|
|
432
|
+
if (err && _CONFIG_PARSE_CODES.has(err.code)) {
|
|
433
|
+
issues.push({
|
|
434
|
+
id: 'config-json-corrupt',
|
|
435
|
+
severity: 'error',
|
|
436
|
+
fixable: 'manual',
|
|
437
|
+
details: {
|
|
438
|
+
code: err.code,
|
|
439
|
+
message: err.message,
|
|
440
|
+
file: err.details && err.details.file,
|
|
441
|
+
hint: 'Repair or delete .nubos-pilot/config.json — the file is unparseable or has the wrong shape.',
|
|
442
|
+
},
|
|
443
|
+
});
|
|
444
|
+
return issues;
|
|
445
|
+
}
|
|
446
|
+
throw err;
|
|
447
|
+
}
|
|
433
448
|
const swarm = cfg && cfg.swarm;
|
|
434
449
|
const adapter = swarm && swarm.knowledge_adapter;
|
|
435
450
|
if (adapter && adapter !== 'local') {
|
|
@@ -488,6 +503,50 @@ function _checkOrphanTmpFiles(projectRoot) {
|
|
|
488
503
|
return issues;
|
|
489
504
|
}
|
|
490
505
|
|
|
506
|
+
function _checkOrphanCheckpoints(projectRoot) {
|
|
507
|
+
const issues = [];
|
|
508
|
+
const stateDir = path.join(projectRoot, STATE_SUBPATH);
|
|
509
|
+
const cpDir = path.join(stateDir, 'checkpoints');
|
|
510
|
+
if (!fs.existsSync(cpDir)) return issues;
|
|
511
|
+
let entries;
|
|
512
|
+
try { entries = fs.readdirSync(cpDir); } catch { return issues; }
|
|
513
|
+
|
|
514
|
+
let currentTask = null;
|
|
515
|
+
try {
|
|
516
|
+
const statePath = path.join(stateDir, 'STATE.md');
|
|
517
|
+
if (fs.existsSync(statePath)) {
|
|
518
|
+
const { readState } = require('../../lib/state.cjs');
|
|
519
|
+
const s = readState(projectRoot);
|
|
520
|
+
currentTask = (s && s.frontmatter && s.frontmatter.current_task) || null;
|
|
521
|
+
}
|
|
522
|
+
} catch { /* state unreadable — treat as null current_task */ }
|
|
523
|
+
|
|
524
|
+
for (const name of entries) {
|
|
525
|
+
if (!name.endsWith('.json')) continue;
|
|
526
|
+
const taskId = name.slice(0, -5);
|
|
527
|
+
const cpPath = path.join(cpDir, name);
|
|
528
|
+
let cp;
|
|
529
|
+
try { cp = JSON.parse(fs.readFileSync(cpPath, 'utf-8')); }
|
|
530
|
+
catch { continue; }
|
|
531
|
+
if (!cp || cp.status !== 'in-progress') continue;
|
|
532
|
+
if (currentTask === taskId) continue;
|
|
533
|
+
issues.push({
|
|
534
|
+
id: 'orphan-checkpoint',
|
|
535
|
+
severity: 'warn',
|
|
536
|
+
fixable: 'manual',
|
|
537
|
+
details: {
|
|
538
|
+
task_id: taskId,
|
|
539
|
+
checkpoint: path.relative(projectRoot, cpPath),
|
|
540
|
+
current_task: currentTask,
|
|
541
|
+
hint: 'Checkpoint marks task as in-progress but STATE.md.current_task does not match. '
|
|
542
|
+
+ 'Likely a crash during finishTask between STATE-clear and checkpoint-unlink. '
|
|
543
|
+
+ 'Run `np-tools undo-task ' + taskId + '` to clean up, or delete manually after verifying the task is genuinely done.',
|
|
544
|
+
},
|
|
545
|
+
});
|
|
546
|
+
}
|
|
547
|
+
return issues;
|
|
548
|
+
}
|
|
549
|
+
|
|
491
550
|
function _checkOutputSchemas(projectRoot) {
|
|
492
551
|
const issues = [];
|
|
493
552
|
const milestonesRoot = path.join(projectRoot, STATE_SUBPATH, 'milestones');
|
|
@@ -551,6 +610,7 @@ function _audit(projectRoot) {
|
|
|
551
610
|
issues.push(..._checkNubosloopKnowledgeStore(projectRoot));
|
|
552
611
|
issues.push(..._checkNubosloopConfig(projectRoot));
|
|
553
612
|
issues.push(..._checkOrphanTmpFiles(projectRoot));
|
|
613
|
+
issues.push(..._checkOrphanCheckpoints(projectRoot));
|
|
554
614
|
issues.push(..._checkOutputSchemas(projectRoot));
|
|
555
615
|
return { issues, _codexContent: codex.content };
|
|
556
616
|
}
|
|
@@ -169,6 +169,20 @@ test('DOC-8: flags nubosloop-knowledge-adapter-invalid for unsupported adapter',
|
|
|
169
169
|
assert.ok(ids.includes('nubosloop-knowledge-adapter-invalid'));
|
|
170
170
|
});
|
|
171
171
|
|
|
172
|
+
test('DOC-9b: flags config-json-corrupt when config.json is unparseable', async () => {
|
|
173
|
+
const root = makeSandbox();
|
|
174
|
+
fs.writeFileSync(path.join(root, 'src.js'), 'export function a(){}');
|
|
175
|
+
scanCodebase.run([], { cwd: root, stdout: captureStdout().stub });
|
|
176
|
+
fs.writeFileSync(path.join(root, '.nubos-pilot', 'config.json'), '{ not json');
|
|
177
|
+
const cap = captureStdout();
|
|
178
|
+
await doctor.run([], { cwd: root, stdout: cap.stub, stderr: cap.stub, askUser: async () => ({ value: false }) });
|
|
179
|
+
const out = cap.json();
|
|
180
|
+
const issue = out.issues.find((i) => i.id === 'config-json-corrupt');
|
|
181
|
+
assert.ok(issue, 'expected config-json-corrupt issue');
|
|
182
|
+
assert.equal(issue.severity, 'error');
|
|
183
|
+
assert.match(issue.details.hint, /Repair or delete/);
|
|
184
|
+
});
|
|
185
|
+
|
|
172
186
|
test('DOC-9: flags nubosloop-maxRounds-out-of-range when value > 10', async () => {
|
|
173
187
|
const root = makeSandbox();
|
|
174
188
|
fs.writeFileSync(path.join(root, 'src.js'), 'export function a(){}');
|
|
@@ -2,22 +2,18 @@
|
|
|
2
2
|
|
|
3
3
|
const fs = require('node:fs');
|
|
4
4
|
const path = require('node:path');
|
|
5
|
-
const os = require('node:os');
|
|
6
|
-
const crypto = require('node:crypto');
|
|
7
5
|
|
|
8
6
|
const {
|
|
9
7
|
NubosPilotError,
|
|
10
|
-
projectStateDir,
|
|
11
8
|
atomicWriteFileSync,
|
|
12
9
|
} = require('../../lib/core.cjs');
|
|
10
|
+
const { emitInitPayload } = require('../../lib/init-emit.cjs');
|
|
13
11
|
const layout = require('../../lib/layout.cjs');
|
|
14
12
|
const { getPhase } = require('../../lib/roadmap.cjs');
|
|
15
13
|
const { extractFrontmatter } = require('../../lib/frontmatter.cjs');
|
|
16
14
|
const { getAgentSkills } = require('../../lib/agents.cjs');
|
|
17
15
|
const textMode = require('../../lib/text-mode.cjs');
|
|
18
16
|
|
|
19
|
-
const INLINE_THRESHOLD_BYTES = 16 * 1024;
|
|
20
|
-
|
|
21
17
|
function _hasVerifyWorkFlag(list) {
|
|
22
18
|
return Array.isArray(list) && list.some((a) => a === '--verify-work');
|
|
23
19
|
}
|
|
@@ -45,23 +41,6 @@ function _safeSkills(name, cwd) {
|
|
|
45
41
|
try { return getAgentSkills(name, cwd); } catch { return []; }
|
|
46
42
|
}
|
|
47
43
|
|
|
48
|
-
function _emit(payload, stdout, cwd) {
|
|
49
|
-
const json = JSON.stringify(payload, null, 2);
|
|
50
|
-
if (Buffer.byteLength(json, 'utf-8') <= INLINE_THRESHOLD_BYTES) {
|
|
51
|
-
stdout.write(json);
|
|
52
|
-
return;
|
|
53
|
-
}
|
|
54
|
-
let tmpDir;
|
|
55
|
-
try {
|
|
56
|
-
tmpDir = path.join(projectStateDir(cwd), '.tmp');
|
|
57
|
-
fs.mkdirSync(tmpDir, { recursive: true });
|
|
58
|
-
} catch { tmpDir = os.tmpdir(); }
|
|
59
|
-
const suffix = process.pid + '-' + crypto.randomBytes(4).toString('hex');
|
|
60
|
-
const tmpPath = path.join(tmpDir, 'init-execute-milestone-' + suffix + '.json');
|
|
61
|
-
fs.writeFileSync(tmpPath, json, 'utf-8');
|
|
62
|
-
stdout.write('@file:' + tmpPath);
|
|
63
|
-
}
|
|
64
|
-
|
|
65
44
|
function _readTaskPayload(taskPlanPath) {
|
|
66
45
|
const raw = fs.readFileSync(taskPlanPath, 'utf-8');
|
|
67
46
|
const { frontmatter, body } = extractFrontmatter(raw);
|
|
@@ -288,7 +267,7 @@ function run(args, ctx) {
|
|
|
288
267
|
const mNum = _validateMilestoneArg(list[1]);
|
|
289
268
|
const autoVerify = _hasVerifyWorkFlag(list.slice(2));
|
|
290
269
|
const payload = _initPayload(mNum, cwd, { auto_verify: autoVerify });
|
|
291
|
-
|
|
270
|
+
emitInitPayload(payload, stdout, cwd, 'execute-milestone');
|
|
292
271
|
return payload;
|
|
293
272
|
}
|
|
294
273
|
case 'execute-task': {
|
|
@@ -302,20 +281,20 @@ function run(args, ctx) {
|
|
|
302
281
|
);
|
|
303
282
|
}
|
|
304
283
|
const payload = _findTaskByFullId(mNum, taskId, cwd);
|
|
305
|
-
|
|
284
|
+
emitInitPayload(payload, stdout, cwd, 'execute-milestone');
|
|
306
285
|
return payload;
|
|
307
286
|
}
|
|
308
287
|
case 'finalize-slice': {
|
|
309
288
|
const mNum = _validateMilestoneArg(list[1]);
|
|
310
289
|
const sNum = _validateMilestoneArg(list[2]);
|
|
311
290
|
const payload = _finalizeSlice(mNum, sNum, cwd);
|
|
312
|
-
|
|
291
|
+
emitInitPayload(payload, stdout, cwd, 'execute-milestone');
|
|
313
292
|
return payload;
|
|
314
293
|
}
|
|
315
294
|
case 'finalize-milestone': {
|
|
316
295
|
const mNum = _validateMilestoneArg(list[1]);
|
|
317
296
|
const payload = _finalizeMilestone(mNum, cwd);
|
|
318
|
-
|
|
297
|
+
emitInitPayload(payload, stdout, cwd, 'execute-milestone');
|
|
319
298
|
return payload;
|
|
320
299
|
}
|
|
321
300
|
default:
|
|
@@ -327,4 +306,4 @@ function run(args, ctx) {
|
|
|
327
306
|
}
|
|
328
307
|
}
|
|
329
308
|
|
|
330
|
-
module.exports = { run
|
|
309
|
+
module.exports = { run };
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
const fs = require('node:fs');
|
|
4
|
-
const { NubosPilotError } = require('../../lib/core.cjs');
|
|
4
|
+
const { NubosPilotError, projectStateDir } = require('../../lib/core.cjs');
|
|
5
5
|
const { writeHandoff } = require('../../lib/handoff.cjs');
|
|
6
|
+
const safePath = require('../../lib/safe-path.cjs');
|
|
6
7
|
|
|
7
8
|
function _parseArgs(args) {
|
|
8
9
|
const out = {
|
|
@@ -32,7 +33,20 @@ function run(args, opts) {
|
|
|
32
33
|
|
|
33
34
|
let body = parsed.body || '';
|
|
34
35
|
if (parsed.bodyFile) {
|
|
35
|
-
|
|
36
|
+
let resolved;
|
|
37
|
+
try {
|
|
38
|
+
resolved = safePath.assertInsideAnyOf([cwd, projectStateDir(cwd)], parsed.bodyFile, 'body-file');
|
|
39
|
+
} catch (err) {
|
|
40
|
+
if (err && (err.code === 'safe-path-outside-base' || err.code === 'safe-path-invalid-input' || err.code === 'safe-path-base-missing')) {
|
|
41
|
+
throw new NubosPilotError(
|
|
42
|
+
'handoff-body-file-not-allowed',
|
|
43
|
+
'--body-file must reside inside the project or .nubos-pilot/: ' + parsed.bodyFile,
|
|
44
|
+
{ path: parsed.bodyFile, cause: err.code },
|
|
45
|
+
);
|
|
46
|
+
}
|
|
47
|
+
throw err;
|
|
48
|
+
}
|
|
49
|
+
try { body = fs.readFileSync(resolved, 'utf-8'); }
|
|
36
50
|
catch (err) {
|
|
37
51
|
throw new NubosPilotError(
|
|
38
52
|
'handoff-body-file-read-failed',
|
|
@@ -33,6 +33,27 @@ test('TD-1: topLevelCommands routes metrics/resolve-model/plan-diff and siblings
|
|
|
33
33
|
}
|
|
34
34
|
});
|
|
35
35
|
|
|
36
|
+
test('TD-1b: verify-work is reachable as top-level command (not only via init)', () => {
|
|
37
|
+
const np = require('../../np-tools.cjs');
|
|
38
|
+
assert.ok(np.topLevelCommands['verify-work'], 'verify-work must be exposed at top level for agents/workflows');
|
|
39
|
+
assert.equal(typeof np.topLevelCommands['verify-work'].run, 'function');
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
test('TD-1c: every initWorkflow is also exposed top-level (registry-parity invariant)', () => {
|
|
43
|
+
const np = require('../../np-tools.cjs');
|
|
44
|
+
for (const key of Object.keys(np.initWorkflows)) {
|
|
45
|
+
assert.ok(
|
|
46
|
+
np.topLevelCommands[key],
|
|
47
|
+
'initWorkflow "' + key + '" missing from topLevelCommands — both registries must list every agent-callable verb',
|
|
48
|
+
);
|
|
49
|
+
assert.strictEqual(
|
|
50
|
+
np.topLevelCommands[key],
|
|
51
|
+
np.initWorkflows[key],
|
|
52
|
+
'topLevelCommands["' + key + '"] must be the SAME module as initWorkflows["' + key + '"]',
|
|
53
|
+
);
|
|
54
|
+
}
|
|
55
|
+
});
|
|
56
|
+
|
|
36
57
|
test('TD-2: initWorkflows exposes plan-milestone + execute-milestone entries', () => {
|
|
37
58
|
const np = require('../../np-tools.cjs');
|
|
38
59
|
assert.ok(np.initWorkflows && typeof np.initWorkflows === 'object');
|
|
@@ -31,9 +31,6 @@ function run(args, ctx) {
|
|
|
31
31
|
}
|
|
32
32
|
const result = search(parsed.query, cwd, { limit: parsed.limit });
|
|
33
33
|
stdout.write(JSON.stringify(result));
|
|
34
|
-
// When run inside a Nubosloop task (--task), record Rule 9 evidence so the
|
|
35
|
-
// tool-use audit can verify the search actually happened. Best-effort:
|
|
36
|
-
// ledger failure must never fail the search itself.
|
|
37
34
|
if (parsed.task) {
|
|
38
35
|
try {
|
|
39
36
|
require('../../lib/nubosloop.cjs').recordSearchEvidence(parsed.task, parsed.query, cwd);
|
|
@@ -13,14 +13,12 @@ function run(argv, ctx) {
|
|
|
13
13
|
|
|
14
14
|
const adapter = knowledgeAdapter.getAdapter(cwd);
|
|
15
15
|
const all = adapter.list();
|
|
16
|
-
// Sort by occurrence desc, last_seen desc — most-used first.
|
|
17
16
|
const sorted = all.slice().sort((a, b) => {
|
|
18
17
|
const oc = (b.occurrence || 0) - (a.occurrence || 0);
|
|
19
18
|
if (oc !== 0) return oc;
|
|
20
19
|
return String(b.last_seen || '').localeCompare(String(a.last_seen || ''));
|
|
21
20
|
});
|
|
22
21
|
const truncated = sorted.slice(0, cap);
|
|
23
|
-
// Strip the bulky tokens[] array from the listing — it's an internal cache.
|
|
24
22
|
const projected = truncated.map((l) => {
|
|
25
23
|
const { tokens, ...rest } = l;
|
|
26
24
|
return rest;
|
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
const { NubosPilotError } = require('../../lib/core.cjs');
|
|
4
|
-
const { TASK_ID_RE } = require('../../lib/
|
|
5
|
-
const { MILESTONE_ID_RE } = require('../../lib/learnings.cjs');
|
|
4
|
+
const { TASK_ID_RE, MILESTONE_ID_RE } = require('../../lib/ids.cjs');
|
|
6
5
|
const knowledgeAdapter = require('../../lib/knowledge-adapter.cjs');
|
|
7
6
|
const { getFlag } = require('./_args.cjs');
|
|
8
7
|
|
|
@@ -52,11 +51,6 @@ function run(args, ctx) {
|
|
|
52
51
|
const learnings = require('../../lib/learnings.cjs');
|
|
53
52
|
const fingerprint = learnings._fingerprint(pattern);
|
|
54
53
|
const adapter = knowledgeAdapter.getAdapter(cwd);
|
|
55
|
-
// R5/risk from fifth review: the prior pre-scan-then-write approach raced
|
|
56
|
-
// a concurrent writer that landed the same fingerprint between the scan
|
|
57
|
-
// and the log() call. We now derive `was_new` from the post-write
|
|
58
|
-
// occurrence: logLearning is locked, so occurrence === 1 is determinative
|
|
59
|
-
// — exactly one log() call inside the lock window made the entry new.
|
|
60
54
|
const result = adapter.log({ pattern, outcome, task_id: taskId, milestone_id: milestoneId });
|
|
61
55
|
let occurrence = 1;
|
|
62
56
|
if (Array.isArray(result && result.learnings)) {
|
|
@@ -4,8 +4,7 @@ const checkpoint = require('../../lib/checkpoint.cjs');
|
|
|
4
4
|
const nubosloop = require('../../lib/nubosloop.cjs');
|
|
5
5
|
const agentsLib = require('../../lib/agents.cjs');
|
|
6
6
|
const args = require('./_args.cjs');
|
|
7
|
-
|
|
8
|
-
const TASK_ID_RE = checkpoint.TASK_ID_RE;
|
|
7
|
+
const { TASK_ID_RE } = require('../../lib/ids.cjs');
|
|
9
8
|
|
|
10
9
|
function run(argv, ctx) {
|
|
11
10
|
const context = ctx || {};
|
|
@@ -16,14 +15,12 @@ function run(argv, ctx) {
|
|
|
16
15
|
args.assertMatch(taskId, TASK_ID_RE, 'loop-audit-invalid-task-id', 'taskId');
|
|
17
16
|
const tail = list.slice(1);
|
|
18
17
|
|
|
19
|
-
// Read mode: just dump the audit log.
|
|
20
18
|
if (tail.includes('--read')) {
|
|
21
19
|
const log = nubosloop.readToolUseAudit(taskId, cwd) || [];
|
|
22
20
|
stdout.write(JSON.stringify({ task_id: taskId, audit: log }) + '\n');
|
|
23
21
|
return { task_id: taskId, audit: log };
|
|
24
22
|
}
|
|
25
23
|
|
|
26
|
-
// Append mode: agent + tool_use_log.
|
|
27
24
|
const agent = args.getFlag(tail, '--agent');
|
|
28
25
|
if (!agent) {
|
|
29
26
|
throw new (require('../../lib/core.cjs').NubosPilotError)(
|
|
@@ -43,15 +40,8 @@ function run(argv, ctx) {
|
|
|
43
40
|
} catch (err) {
|
|
44
41
|
if (!err) throw err;
|
|
45
42
|
if (err.code === 'loop-audit-agent-is-module') throw err;
|
|
46
|
-
// Any other error (agent-not-found, agent-not-a-module) means the name
|
|
47
|
-
// is not a known module — fall through and accept the audit.
|
|
48
43
|
}
|
|
49
44
|
}
|
|
50
|
-
// --tool-use-log is required for AUDITED_AGENTS (Rule 9 enforcement reads
|
|
51
|
-
// the tool list to verify search-knowledge / match-existing-learning calls).
|
|
52
|
-
// For non-audited spawns (critics, plan-checker, etc.) the orchestrator may
|
|
53
|
-
// omit it — we still record the spawn for Layer-C audit-trail evidence with
|
|
54
|
-
// an empty log. Explicit empty-array is also accepted.
|
|
55
45
|
const isAuditedAgent = nubosloop.AUDITED_AGENTS.includes(agent);
|
|
56
46
|
let log;
|
|
57
47
|
if (tail.includes('--tool-use-log')) {
|