nubos-pilot 1.3.1 → 1.3.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/CHANGELOG.md +13 -0
- package/agents/np-critic-economy.md +103 -0
- package/agents/np-critic.md +11 -10
- package/agents/np-executor.md +14 -0
- package/agents/np-simplifier.md +83 -0
- package/bin/install.js +30 -1
- package/bin/np-tools/_commands.cjs +2 -0
- package/bin/np-tools/doctor.cjs +26 -3
- package/bin/np-tools/economy-mode.cjs +47 -0
- package/bin/np-tools/loop-run-round.cjs +1 -1
- package/bin/np-tools/resolve-model.cjs +1 -0
- package/bin/np-tools/resume-work.cjs +9 -0
- package/bin/np-tools/resume-work.test.cjs +21 -1
- package/bin/np-tools/simplify-debt.cjs +91 -0
- package/bin/np-tools/simplify-debt.test.cjs +99 -0
- package/lib/agents-registry.cjs +2 -1
- package/lib/agents.test.cjs +2 -0
- package/lib/checkpoint-reconcile.cjs +42 -0
- package/lib/checkpoint-reconcile.test.cjs +106 -0
- package/lib/config-defaults.cjs +14 -1
- package/lib/config-defaults.test.cjs +9 -0
- package/lib/config-schema.cjs +4 -0
- package/lib/economy-debt.cjs +235 -0
- package/lib/economy-debt.test.cjs +131 -0
- package/lib/economy-mode.cjs +66 -0
- package/lib/economy-mode.test.cjs +85 -0
- package/lib/git.cjs +4 -2
- package/lib/nubosloop.cjs +4 -0
- package/lib/nubosloop.test.cjs +1 -0
- package/np-tools.cjs +2 -0
- package/package.json +1 -1
- package/workflows/execute-phase.md +26 -2
- package/workflows/simplify-debt.md +93 -0
- package/workflows/simplify-review.md +103 -0
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const fs = require('node:fs');
|
|
4
|
+
const os = require('node:os');
|
|
5
|
+
const path = require('node:path');
|
|
6
|
+
const { test, afterEach } = require('node:test');
|
|
7
|
+
const assert = require('node:assert/strict');
|
|
8
|
+
|
|
9
|
+
const debt = require('./economy-debt.cjs');
|
|
10
|
+
|
|
11
|
+
const _sandboxes = [];
|
|
12
|
+
|
|
13
|
+
function makeSandbox() {
|
|
14
|
+
const root = fs.mkdtempSync(path.join(os.tmpdir(), 'np-economy-debt-'));
|
|
15
|
+
fs.mkdirSync(path.join(root, '.nubos-pilot'), { recursive: true });
|
|
16
|
+
_sandboxes.push(root);
|
|
17
|
+
return root;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
afterEach(() => {
|
|
21
|
+
while (_sandboxes.length) {
|
|
22
|
+
try { fs.rmSync(_sandboxes.pop(), { recursive: true, force: true }); } catch { /* best effort */ }
|
|
23
|
+
}
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
test('ED-1: addEntry writes an open entry and returns was_new=true', () => {
|
|
27
|
+
const cwd = makeSandbox();
|
|
28
|
+
const e = debt.addEntry(
|
|
29
|
+
{ file: 'src/foo.ts', line: 42, category: 'over-engineering', note: 'Single-use factory — inline it.' },
|
|
30
|
+
cwd,
|
|
31
|
+
);
|
|
32
|
+
assert.equal(e.was_new, true);
|
|
33
|
+
assert.equal(e.status, 'open');
|
|
34
|
+
assert.equal(e.category, 'over-engineering');
|
|
35
|
+
assert.equal(e.file, 'src/foo.ts');
|
|
36
|
+
assert.equal(e.line, 42);
|
|
37
|
+
assert.match(e.id, /^[0-9a-f]{7}$/);
|
|
38
|
+
assert.ok(fs.existsSync(e.path));
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
test('ED-2: addEntry is idempotent — identical input does not duplicate', () => {
|
|
42
|
+
const cwd = makeSandbox();
|
|
43
|
+
const first = debt.addEntry(
|
|
44
|
+
{ file: 'a.ts', line: 1, category: 'shrinkable', note: 'manual reduce -> Array.reduce' },
|
|
45
|
+
cwd,
|
|
46
|
+
);
|
|
47
|
+
const second = debt.addEntry(
|
|
48
|
+
{ file: 'a.ts', line: 1, category: 'shrinkable', note: 'manual reduce -> Array.reduce' },
|
|
49
|
+
cwd,
|
|
50
|
+
);
|
|
51
|
+
assert.equal(first.id, second.id);
|
|
52
|
+
assert.equal(second.was_new, false);
|
|
53
|
+
assert.equal(debt.listEntries('open', cwd).length, 1);
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
test('ED-3: addEntry rejects a category outside the four economy routes', () => {
|
|
57
|
+
const cwd = makeSandbox();
|
|
58
|
+
assert.throws(
|
|
59
|
+
() => debt.addEntry({ file: 'a.ts', category: 'security', note: 'x' }, cwd),
|
|
60
|
+
(err) => err && err.name === 'NubosPilotError' && err.code === 'economy-debt-invalid-category',
|
|
61
|
+
);
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
test('ED-4: addEntry rejects an empty note', () => {
|
|
65
|
+
const cwd = makeSandbox();
|
|
66
|
+
assert.throws(
|
|
67
|
+
() => debt.addEntry({ file: 'a.ts', category: 'shrinkable', note: ' ' }, cwd),
|
|
68
|
+
(err) => err && err.name === 'NubosPilotError' && err.code === 'economy-debt-missing-note',
|
|
69
|
+
);
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
test('ED-5: line defaults to 0 (file-level) when omitted', () => {
|
|
73
|
+
const cwd = makeSandbox();
|
|
74
|
+
const e = debt.addEntry({ file: 'a.ts', category: 'native-duplication', note: 'reimplements framework helper' }, cwd);
|
|
75
|
+
assert.equal(e.line, 0);
|
|
76
|
+
const parsed = debt.listEntries('open', cwd)[0];
|
|
77
|
+
assert.equal(parsed.line, 0);
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
test('ED-6: listEntries sorts oldest-first and round-trips note + fields', () => {
|
|
81
|
+
const cwd = makeSandbox();
|
|
82
|
+
debt.addEntry({ file: 'a.ts', line: 5, category: 'shrinkable', note: 'first' }, cwd);
|
|
83
|
+
debt.addEntry({ file: 'b.ts', line: 9, category: 'over-engineering', note: 'second' }, cwd);
|
|
84
|
+
const list = debt.listEntries('open', cwd);
|
|
85
|
+
assert.equal(list.length, 2);
|
|
86
|
+
assert.equal(list[0].note, 'first');
|
|
87
|
+
assert.equal(list[1].note, 'second');
|
|
88
|
+
assert.equal(list[1].category, 'over-engineering');
|
|
89
|
+
assert.equal(list[1].line, 9);
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
test('ED-7: resolveEntry moves open -> resolved and stamps resolved time', () => {
|
|
93
|
+
const cwd = makeSandbox();
|
|
94
|
+
const e = debt.addEntry({ file: 'a.ts', line: 1, category: 'stdlib-reinvention', note: 'hand-rolled clamp' }, cwd);
|
|
95
|
+
const r = debt.resolveEntry(e.id, cwd);
|
|
96
|
+
assert.equal(r.status, 'resolved');
|
|
97
|
+
assert.match(r.resolved, /^\d{4}-\d{2}-\d{2}T/);
|
|
98
|
+
assert.equal(debt.listEntries('open', cwd).length, 0);
|
|
99
|
+
assert.equal(debt.listEntries('resolved', cwd).length, 1);
|
|
100
|
+
assert.equal(debt.listEntries('all', cwd).length, 1);
|
|
101
|
+
assert.ok(!fs.existsSync(e.path));
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
test('ED-8: resolveEntry throws economy-debt-not-found for an unknown id', () => {
|
|
105
|
+
const cwd = makeSandbox();
|
|
106
|
+
assert.throws(
|
|
107
|
+
() => debt.resolveEntry('deadbee', cwd),
|
|
108
|
+
(err) => err && err.name === 'NubosPilotError' && err.code === 'economy-debt-not-found',
|
|
109
|
+
);
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
test('ED-9: listEntries rejects an invalid status', () => {
|
|
113
|
+
const cwd = makeSandbox();
|
|
114
|
+
assert.throws(
|
|
115
|
+
() => debt.listEntries('bogus', cwd),
|
|
116
|
+
(err) => err && err.name === 'NubosPilotError' && err.code === 'economy-debt-invalid-status',
|
|
117
|
+
);
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
test('ED-10: empty ledger lists as []', () => {
|
|
121
|
+
const cwd = makeSandbox();
|
|
122
|
+
assert.deepEqual(debt.listEntries('open', cwd), []);
|
|
123
|
+
assert.deepEqual(debt.listEntries('all', cwd), []);
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
test('ED-11: ECONOMY_CATEGORIES matches the four canonical economy routes', () => {
|
|
127
|
+
assert.deepEqual(
|
|
128
|
+
debt.ECONOMY_CATEGORIES.slice().sort(),
|
|
129
|
+
['native-duplication', 'over-engineering', 'shrinkable', 'stdlib-reinvention'],
|
|
130
|
+
);
|
|
131
|
+
});
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// Single source of truth for the Economy axis activation level (Ponytail-style
|
|
4
|
+
// graduated modes). The Economy schicht has two mechanisms — the prevention
|
|
5
|
+
// ladder in agents/np-executor.md (guidance BEFORE writing) and the in-loop
|
|
6
|
+
// Economy critic (agents/np-critic-economy.md, audits the diff AFTER). One
|
|
7
|
+
// enum dials both:
|
|
8
|
+
//
|
|
9
|
+
// off prevention OFF, critic OFF — no economy pressure at all
|
|
10
|
+
// lite prevention ON, critic OFF — prevention-first DEFAULT (advisory only)
|
|
11
|
+
// full prevention ON, critic ON — standard critic rubric
|
|
12
|
+
// ultra prevention ON, critic ON — aggressive critic (lowered shrinkable bar)
|
|
13
|
+
//
|
|
14
|
+
// Default is `lite`: the climb-the-ladder discipline is on, but nothing bounces
|
|
15
|
+
// work back. This makes prevention-first the documented default philosophy
|
|
16
|
+
// while keeping the costlier critic opt-in (full/ultra).
|
|
17
|
+
//
|
|
18
|
+
// Backward-compat: the legacy boolean `agents.economy_critic` is honoured when
|
|
19
|
+
// `agents.economy` is absent — true→full, false→lite — so a pre-existing
|
|
20
|
+
// gitignored config keeps its behaviour. The resolver is LOUD: an explicit but
|
|
21
|
+
// invalid `agents.economy` string throws rather than silently defaulting.
|
|
22
|
+
|
|
23
|
+
const { NubosPilotError } = require('./core.cjs');
|
|
24
|
+
|
|
25
|
+
const VALID_ECONOMY_MODES = Object.freeze(['off', 'lite', 'full', 'ultra']);
|
|
26
|
+
const DEFAULT_ECONOMY_MODE = 'lite';
|
|
27
|
+
|
|
28
|
+
function resolveEconomyMode(config) {
|
|
29
|
+
const agents = config && typeof config === 'object' ? config.agents : null;
|
|
30
|
+
if (agents && typeof agents === 'object') {
|
|
31
|
+
if (agents.economy !== undefined) {
|
|
32
|
+
const explicit = agents.economy;
|
|
33
|
+
if (typeof explicit !== 'string' || !VALID_ECONOMY_MODES.includes(explicit)) {
|
|
34
|
+
throw new NubosPilotError(
|
|
35
|
+
'config-invalid-economy-mode',
|
|
36
|
+
'agents.economy must be one of ' + VALID_ECONOMY_MODES.join('|') + ' (got: ' + JSON.stringify(explicit) + ')',
|
|
37
|
+
{ value: explicit, valid: VALID_ECONOMY_MODES },
|
|
38
|
+
);
|
|
39
|
+
}
|
|
40
|
+
return explicit;
|
|
41
|
+
}
|
|
42
|
+
if (typeof agents.economy_critic === 'boolean') {
|
|
43
|
+
return agents.economy_critic ? 'full' : 'lite';
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
return DEFAULT_ECONOMY_MODE;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function preventionOn(mode) { return mode !== 'off'; }
|
|
50
|
+
function criticOn(mode) { return mode === 'full' || mode === 'ultra'; }
|
|
51
|
+
function isUltra(mode) { return mode === 'ultra'; }
|
|
52
|
+
|
|
53
|
+
function economyFlags(config) {
|
|
54
|
+
const mode = resolveEconomyMode(config);
|
|
55
|
+
return { mode, prevention: preventionOn(mode), critic: criticOn(mode), ultra: isUltra(mode) };
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
module.exports = {
|
|
59
|
+
VALID_ECONOMY_MODES,
|
|
60
|
+
DEFAULT_ECONOMY_MODE,
|
|
61
|
+
resolveEconomyMode,
|
|
62
|
+
preventionOn,
|
|
63
|
+
criticOn,
|
|
64
|
+
isUltra,
|
|
65
|
+
economyFlags,
|
|
66
|
+
};
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { test } = require('node:test');
|
|
4
|
+
const assert = require('node:assert/strict');
|
|
5
|
+
|
|
6
|
+
const {
|
|
7
|
+
VALID_ECONOMY_MODES,
|
|
8
|
+
DEFAULT_ECONOMY_MODE,
|
|
9
|
+
resolveEconomyMode,
|
|
10
|
+
preventionOn,
|
|
11
|
+
criticOn,
|
|
12
|
+
isUltra,
|
|
13
|
+
economyFlags,
|
|
14
|
+
} = require('./economy-mode.cjs');
|
|
15
|
+
|
|
16
|
+
test('default is lite (prevention-first) when nothing is set', () => {
|
|
17
|
+
assert.equal(DEFAULT_ECONOMY_MODE, 'lite');
|
|
18
|
+
assert.equal(resolveEconomyMode({}), 'lite');
|
|
19
|
+
assert.equal(resolveEconomyMode({ agents: {} }), 'lite');
|
|
20
|
+
assert.equal(resolveEconomyMode(null), 'lite');
|
|
21
|
+
assert.equal(resolveEconomyMode(undefined), 'lite');
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
test('explicit agents.economy wins for every valid mode', () => {
|
|
25
|
+
for (const mode of VALID_ECONOMY_MODES) {
|
|
26
|
+
assert.equal(resolveEconomyMode({ agents: { economy: mode } }), mode);
|
|
27
|
+
}
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
test('legacy agents.economy_critic maps true→full, false→lite', () => {
|
|
31
|
+
assert.equal(resolveEconomyMode({ agents: { economy_critic: true } }), 'full');
|
|
32
|
+
assert.equal(resolveEconomyMode({ agents: { economy_critic: false } }), 'lite');
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
test('non-boolean legacy economy_critic falls back to the lite default (schema warns separately)', () => {
|
|
36
|
+
assert.equal(resolveEconomyMode({ agents: { economy_critic: 'true' } }), 'lite');
|
|
37
|
+
assert.equal(resolveEconomyMode({ agents: { economy_critic: 1 } }), 'lite');
|
|
38
|
+
assert.equal(resolveEconomyMode({ agents: { economy_critic: null } }), 'lite');
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
test('explicit economy overrides the legacy bool', () => {
|
|
42
|
+
assert.equal(resolveEconomyMode({ agents: { economy: 'off', economy_critic: true } }), 'off');
|
|
43
|
+
assert.equal(resolveEconomyMode({ agents: { economy: 'ultra', economy_critic: false } }), 'ultra');
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
test('invalid explicit economy throws loud (no silent default)', () => {
|
|
47
|
+
assert.throws(
|
|
48
|
+
() => resolveEconomyMode({ agents: { economy: 'banana' } }),
|
|
49
|
+
(err) => err.code === 'config-invalid-economy-mode',
|
|
50
|
+
);
|
|
51
|
+
assert.throws(
|
|
52
|
+
() => resolveEconomyMode({ agents: { economy: 42 } }),
|
|
53
|
+
(err) => err.code === 'config-invalid-economy-mode',
|
|
54
|
+
);
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
test('flag helpers gate prevention/critic/ultra correctly', () => {
|
|
58
|
+
assert.equal(preventionOn('off'), false);
|
|
59
|
+
assert.equal(preventionOn('lite'), true);
|
|
60
|
+
assert.equal(preventionOn('full'), true);
|
|
61
|
+
assert.equal(preventionOn('ultra'), true);
|
|
62
|
+
|
|
63
|
+
assert.equal(criticOn('off'), false);
|
|
64
|
+
assert.equal(criticOn('lite'), false);
|
|
65
|
+
assert.equal(criticOn('full'), true);
|
|
66
|
+
assert.equal(criticOn('ultra'), true);
|
|
67
|
+
|
|
68
|
+
assert.equal(isUltra('ultra'), true);
|
|
69
|
+
assert.equal(isUltra('full'), false);
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
test('economyFlags bundles the resolved mode with its gates', () => {
|
|
73
|
+
assert.deepEqual(economyFlags({ agents: { economy: 'off' } }), {
|
|
74
|
+
mode: 'off', prevention: false, critic: false, ultra: false,
|
|
75
|
+
});
|
|
76
|
+
assert.deepEqual(economyFlags({}), {
|
|
77
|
+
mode: 'lite', prevention: true, critic: false, ultra: false,
|
|
78
|
+
});
|
|
79
|
+
assert.deepEqual(economyFlags({ agents: { economy: 'full' } }), {
|
|
80
|
+
mode: 'full', prevention: true, critic: true, ultra: false,
|
|
81
|
+
});
|
|
82
|
+
assert.deepEqual(economyFlags({ agents: { economy: 'ultra' } }), {
|
|
83
|
+
mode: 'ultra', prevention: true, critic: true, ultra: true,
|
|
84
|
+
});
|
|
85
|
+
});
|
package/lib/git.cjs
CHANGED
|
@@ -92,7 +92,7 @@ function commitTask(taskId, files, message) {
|
|
|
92
92
|
};
|
|
93
93
|
}
|
|
94
94
|
|
|
95
|
-
function findCommitByTaskId(id) {
|
|
95
|
+
function findCommitByTaskId(id, cwd) {
|
|
96
96
|
if (typeof id !== 'string' || !TASK_ID_RE.test(id)) {
|
|
97
97
|
throw new NubosPilotError(
|
|
98
98
|
'task-commit-not-found',
|
|
@@ -101,6 +101,8 @@ function findCommitByTaskId(id) {
|
|
|
101
101
|
);
|
|
102
102
|
}
|
|
103
103
|
|
|
104
|
+
const spawnOpts = { encoding: 'utf-8', timeout: GIT_TIMEOUT_MS };
|
|
105
|
+
if (cwd) spawnOpts.cwd = cwd;
|
|
104
106
|
const out = execFileSync(
|
|
105
107
|
'git',
|
|
106
108
|
[
|
|
@@ -112,7 +114,7 @@ function findCommitByTaskId(id) {
|
|
|
112
114
|
'1',
|
|
113
115
|
'--format=%H',
|
|
114
116
|
],
|
|
115
|
-
|
|
117
|
+
spawnOpts,
|
|
116
118
|
).trim();
|
|
117
119
|
if (!out) {
|
|
118
120
|
throw new NubosPilotError(
|
package/lib/nubosloop.cjs
CHANGED
|
@@ -34,6 +34,10 @@ const ROUTE_TABLE = {
|
|
|
34
34
|
'verify-mismatch': 'executor',
|
|
35
35
|
'unmet-criterion': 'executor',
|
|
36
36
|
'scope-creep': 'executor',
|
|
37
|
+
'over-engineering': 'executor',
|
|
38
|
+
'stdlib-reinvention': 'executor',
|
|
39
|
+
'native-duplication': 'executor',
|
|
40
|
+
'shrinkable': 'executor',
|
|
37
41
|
'information-missing': 'researcher',
|
|
38
42
|
'question-to-user': 'askuser',
|
|
39
43
|
'locked-decision-violation': 'plan-checker',
|
package/lib/nubosloop.test.cjs
CHANGED
|
@@ -191,6 +191,7 @@ test('NL-17: ROUTE_TABLE covers every documented finding category', () => {
|
|
|
191
191
|
'missing-test', 'edge-case-gap',
|
|
192
192
|
'weak-assertion', 'silenced-failure', 'test-naming', 'non-deterministic',
|
|
193
193
|
'verify-mismatch', 'unmet-criterion', 'scope-creep', 'information-missing',
|
|
194
|
+
'over-engineering', 'stdlib-reinvention', 'native-duplication', 'shrinkable',
|
|
194
195
|
'infrastructure-mismatch',
|
|
195
196
|
'question-to-user', 'locked-decision-violation', 'stuck-detected',
|
|
196
197
|
];
|
package/np-tools.cjs
CHANGED
|
@@ -44,6 +44,7 @@ const topLevelCommands = {
|
|
|
44
44
|
'askuser': require('./bin/np-tools/askuser.cjs'),
|
|
45
45
|
'commit': require('./bin/np-tools/commit.cjs'),
|
|
46
46
|
'config-get': require('./bin/np-tools/config.cjs'),
|
|
47
|
+
'economy-mode': require('./bin/np-tools/economy-mode.cjs'),
|
|
47
48
|
'scan-codebase': require('./bin/np-tools/scan-codebase.cjs'),
|
|
48
49
|
'update-docs': require('./bin/np-tools/update-docs.cjs'),
|
|
49
50
|
'graph-impact': require('./bin/np-tools/graph-impact.cjs'),
|
|
@@ -79,6 +80,7 @@ const topLevelCommands = {
|
|
|
79
80
|
'worktree-list': require('./bin/np-tools/worktree-list.cjs'),
|
|
80
81
|
'worktree-ff-merge': require('./bin/np-tools/worktree-ff-merge.cjs'),
|
|
81
82
|
'dashboard': require('./bin/np-tools/dashboard.cjs'),
|
|
83
|
+
'simplify-debt': require('./bin/np-tools/simplify-debt.cjs'),
|
|
82
84
|
'archive-project': require('./bin/np-tools/archive-project.cjs'),
|
|
83
85
|
|
|
84
86
|
...initWorkflows,
|
package/package.json
CHANGED
|
@@ -35,8 +35,14 @@ RUNTIME=$(node .nubos-pilot/bin/np-tools.cjs detect-runtime)
|
|
|
35
35
|
WORKTREE_ISOLATION=$(node .nubos-pilot/bin/np-tools.cjs config-get workflow.worktree_isolation 2>/dev/null || echo "false")
|
|
36
36
|
TIER_ROUTING=$(node .nubos-pilot/bin/np-tools.cjs config-get workflow.tier_routing 2>/dev/null || echo "false")
|
|
37
37
|
VERIFY_RUNS=$(node .nubos-pilot/bin/np-tools.cjs config-get loop.verify_runs 2>/dev/null || echo "1")
|
|
38
|
+
ECONOMY=$(node .nubos-pilot/bin/np-tools.cjs economy-mode --json 2>/dev/null || echo '{"mode":"lite","prevention":true,"critic":false,"ultra":false}')
|
|
39
|
+
ECONOMY_MODE=$(echo "$ECONOMY" | node -e 'let s="";process.stdin.on("data",d=>s+=d).on("end",()=>{try{console.log(JSON.parse(s).mode)}catch{console.log("lite")}})')
|
|
40
|
+
ECONOMY_PREVENTION=$(echo "$ECONOMY" | node -e 'let s="";process.stdin.on("data",d=>s+=d).on("end",()=>{try{console.log(JSON.parse(s).prevention)}catch{console.log("true")}})')
|
|
41
|
+
ECONOMY_CRITIC=$(echo "$ECONOMY" | node -e 'let s="";process.stdin.on("data",d=>s+=d).on("end",()=>{try{console.log(JSON.parse(s).critic)}catch{console.log("false")}})')
|
|
38
42
|
```
|
|
39
43
|
|
|
44
|
+
**Economy axis (Ponytail-style graduated modes, SSOT = `economy-mode`).** `$ECONOMY_MODE` is one of `off|lite|full|ultra` (default `lite` = prevention-first). It dials two mechanisms: `$ECONOMY_PREVENTION` (`true` for `lite`/`full`/`ultra`) gates the climb-the-ladder directive injected into the Executor (Step 3); `$ECONOMY_CRITIC` (`true` for `full`/`ultra`) gates the `np-critic-economy.md` audit module injected into np-critic (Step 5). `ultra` additionally tells the critic to lower its `shrinkable` bar. Resolve this ONCE here — never re-read the raw config toggle downstream.
|
|
45
|
+
|
|
40
46
|
When `--verify-work` is passed, the init payload's `auto_verify: true` flag tells this workflow to chain into `/np:verify-work $PHASE` after every slice committed and `finalize-milestone` ran. Without the flag the workflow stops after finalize as before — verify-work then remains a separate manual step.
|
|
41
47
|
|
|
42
48
|
**Language (SSOT = `.nubos-pilot/config.json` → `response_language`).**
|
|
@@ -118,16 +124,17 @@ If zero skills match, omit the block — do **not** invent skills. Adding new sk
|
|
|
118
124
|
|
|
119
125
|
## Pre-Flight — orphan-checkpoint guard
|
|
120
126
|
|
|
121
|
-
Detect stale checkpoints from a prior run before starting new work:
|
|
127
|
+
Detect stale checkpoints from a prior run before starting new work. `init resume-work` first **reconciles every checkpoint against git** (`lib/checkpoint-reconcile.cjs`): any checkpoint whose task already has a `task(<id>):` commit is a tombstone (finishTask never unlinked it — crash between commit and unlink, or a commit made outside `commit-task`) and is pruned silently. They surface in `RESUME.pruned_checkpoints` for the log, never as a prompt. Only genuinely **uncommitted** checkpoints reach `status: orphan` and the dialog below — so a finished milestone can never block the next one.
|
|
122
128
|
|
|
123
129
|
```bash
|
|
124
130
|
RESUME=$(node .nubos-pilot/bin/np-tools.cjs init resume-work)
|
|
125
131
|
RESUME_STATUS=$(echo "$RESUME" | node -e "process.stdin.on('data', d => console.log(JSON.parse(d).status))")
|
|
126
132
|
if [ "$RESUME_STATUS" = "orphan" ]; then
|
|
133
|
+
ORPHAN_ID=$(echo "$RESUME" | node -e "process.stdin.on('data', d => { const p = JSON.parse(d); console.log((p.checkpoint_ids || [])[0] || '') })")
|
|
127
134
|
CHOICE=$(node .nubos-pilot/bin/np-tools.cjs askuser --json '{
|
|
128
135
|
"type": "select",
|
|
129
136
|
"header": "Verwaiste Checkpoints gefunden",
|
|
130
|
-
"question": "Vor dem Milestone-Start
|
|
137
|
+
"question": "Vor dem Milestone-Start wurde ein uncommitteter Checkpoint ohne passenden STATE.current_task gefunden (kein zugehöriger Commit). Was tun?",
|
|
131
138
|
"options": [
|
|
132
139
|
{"label": "Clean working tree (reset-slice)", "description": "Verwirft die in-flight Task und löscht ihren Checkpoint."},
|
|
133
140
|
{"label": "Resume the orphan task", "description": "Setzt STATE.current_task auf den Checkpoint-Eintrag und spawnt den Executor."},
|
|
@@ -135,6 +142,13 @@ if [ "$RESUME_STATUS" = "orphan" ]; then
|
|
|
135
142
|
]
|
|
136
143
|
}')
|
|
137
144
|
case "$CHOICE" in
|
|
145
|
+
"Clean working tree (reset-slice)")
|
|
146
|
+
node .nubos-pilot/bin/np-tools.cjs reset-slice "$ORPHAN_ID"
|
|
147
|
+
;;
|
|
148
|
+
"Resume the orphan task")
|
|
149
|
+
echo "execute-phase: resuming orphan task $ORPHAN_ID — run /np:resume-work" >&2
|
|
150
|
+
exit 0
|
|
151
|
+
;;
|
|
138
152
|
"Abort") exit 0 ;;
|
|
139
153
|
esac
|
|
140
154
|
fi
|
|
@@ -419,6 +433,10 @@ for WAVE_INDEX in 0 1 2 ...; do
|
|
|
419
433
|
# <verify_excerpt>: tail of $VERIFY_LOG (R≥2 only)
|
|
420
434
|
# <lang_directive>: $LANG_DIRECTIVE
|
|
421
435
|
# <skills>: $AGENT_SKILLS_EXECUTOR
|
|
436
|
+
# <economy_mode>: $ECONOMY_MODE — when $ECONOMY_PREVENTION = true (lite/full/
|
|
437
|
+
# ultra) instruct the agent to APPLY the np-executor "Climb the ladder"
|
|
438
|
+
# discipline before writing (prevention-first). When $ECONOMY_MODE = off,
|
|
439
|
+
# instruct it to SKIP the ladder (no economy pressure this run).
|
|
422
440
|
# RULES — Agent MUST: edit ONLY paths in files_modified (D-04 scope guard) —
|
|
423
441
|
# success_criteria are the acceptance target, NEVER a licence to touch other files,
|
|
424
442
|
# run `node np-tools.cjs knowledge-search "<q>" --task $TASK_ID` via Bash
|
|
@@ -558,6 +576,12 @@ for WAVE_INDEX in 0 1 2 ...; do
|
|
|
558
576
|
# - agents/np-critic-style.md
|
|
559
577
|
# - agents/np-critic-tests.md
|
|
560
578
|
# - agents/np-critic-acceptance.md
|
|
579
|
+
# - agents/np-critic-economy.md ← ONLY when $ECONOMY_CRITIC = true (mode full/ultra)
|
|
580
|
+
# (resolved once in the init block via `economy-mode --json`; omit this line
|
|
581
|
+
# entirely when $ECONOMY_CRITIC = false — default mode lite has prevention
|
|
582
|
+
# on but the critic off). When $ECONOMY_MODE = ultra, ALSO append to the
|
|
583
|
+
# prompt: "Economy mode: ultra — lower the shrinkable bar per the Ultra
|
|
584
|
+
# section of np-critic-economy.md." Never inject the module at off/lite.
|
|
561
585
|
# <report_path>$CRITIC_REPORT_PATH</report_path>
|
|
562
586
|
# Agent MUST: Write the full findings JSON to $CRITIC_REPORT_PATH,
|
|
563
587
|
# emit ONLY the verdict-envelope as final message (~150 bytes):
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
---
|
|
2
|
+
command: np:simplify-debt
|
|
3
|
+
description: Economy-debt ledger — record, list, and resolve simplifications you deferred rather than fixed now, so "later" doesn't become "never". CRUD-only (no agent spawn unless you harvest from a review). Manual twin of the in-loop Economy critic (agents.economy full/ultra).
|
|
4
|
+
argument-hint: "[list [--status open|resolved|all]] | [add --file <f> --line <n> --category <c> --note <text>] | [resolve <id>] | [harvest [<git-range>]]"
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# /np:simplify-debt
|
|
8
|
+
|
|
9
|
+
<objective>
|
|
10
|
+
Keep a durable ledger of over-engineering you chose NOT to fix this round. `/np:simplify-review` finds what could be deleted, reused, or condensed; whatever you defer goes here as a tracked entry, so the shortcut is harvested rather than lost. Each entry carries a `file`, optional `line`, one of the four Economy categories (`over-engineering` / `stdlib-reinvention` / `native-duplication` / `shrinkable`), and a one-line note. This command is CRUD over `.nubos-pilot/economy-debt/` — it does not edit source and does not commit code. It is the deferred-work counterpart of the in-loop Economy critic (`/np:execute-phase` with `agents.economy` set to `full` or `ultra`).
|
|
11
|
+
</objective>
|
|
12
|
+
|
|
13
|
+
## Initialize
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
LANG_DIRECTIVE=$(node .nubos-pilot/bin/np-tools.cjs lang-directive)
|
|
17
|
+
VERB="${1:-list}"
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
`$LANG_DIRECTIVE` governs the prose you wrap around the CLI output; the ledger lines (id, category, `file:line`, note) stay canonical. Supersedes CLAUDE.md.
|
|
21
|
+
|
|
22
|
+
## Route by verb
|
|
23
|
+
|
|
24
|
+
This is a pure-CRUD workflow — the ledger lives behind `node .nubos-pilot/bin/np-tools.cjs simplify-debt`; never read or write `.nubos-pilot/economy-debt/` directly.
|
|
25
|
+
|
|
26
|
+
### `list` (default)
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
node .nubos-pilot/bin/np-tools.cjs simplify-debt list --status "${2:-open}"
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
Prints the open ledger (oldest debt first — longest-deferred is most urgent). `--status resolved` or `--status all` widen the view; append `--json` for the machine shape. Print the output verbatim.
|
|
33
|
+
|
|
34
|
+
### `add`
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
node .nubos-pilot/bin/np-tools.cjs simplify-debt add \
|
|
38
|
+
--file "$FILE" --line "$LINE" --category "$CATEGORY" --note "$NOTE"
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
`--category` MUST be one of `over-engineering`, `stdlib-reinvention`, `native-duplication`, `shrinkable` (the CLI rejects anything else). `--line` is optional (omit for a file-level note). Adds are idempotent: re-adding an identical finding is a no-op (`was_new=false`), so re-harvesting the same review never duplicates.
|
|
42
|
+
|
|
43
|
+
### `resolve`
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
node .nubos-pilot/bin/np-tools.cjs simplify-debt resolve "$ID"
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
Moves the entry from `open/` to `resolved/` and stamps the resolution time. Use it once the simplification has actually landed (by hand or via a later `/np:execute-phase` round).
|
|
50
|
+
|
|
51
|
+
### `harvest [<git-range>]`
|
|
52
|
+
|
|
53
|
+
Spawn ONE read-only reviewer — `Agent(subagent_type="np-simplifier", prompt=<…>)` — over the diff (default: working tree + staged vs `HEAD`; or the passed git range). Its prompt MUST carry `agents/np-critic-economy.md` as the rubric (the canonical ladder + safety boundaries) and the captured `git diff --no-color`. For each finding the reviewer returns that you are NOT fixing now, record it with one `simplify-debt add` call (mapping the report tag to the matching `--category`). Findings you fix immediately are not harvested. Print the resulting open ledger when done.
|
|
54
|
+
|
|
55
|
+
## No Source Mutation
|
|
56
|
+
|
|
57
|
+
CRUD over the ledger only. This workflow NEVER edits source, NEVER stages, NEVER commits code. The ledger files under `.nubos-pilot/economy-debt/` are the only writes; commit them with your normal docs/artifact flow if `workflow.commit_docs` is set.
|
|
58
|
+
|
|
59
|
+
## Scope Guardrail
|
|
60
|
+
|
|
61
|
+
<scope_guardrail>
|
|
62
|
+
**Do:**
|
|
63
|
+
- Route every ledger read/write through `node .nubos-pilot/bin/np-tools.cjs simplify-debt <verb>` — never touch `.nubos-pilot/economy-debt/` directly.
|
|
64
|
+
- Use one of the four Economy categories on `add`; the CLI is the source of truth and rejects others.
|
|
65
|
+
- On `harvest`, pass `agents/np-critic-economy.md` to the reviewer so the ledger and the in-loop critic apply the identical bar.
|
|
66
|
+
|
|
67
|
+
**Don't:**
|
|
68
|
+
- Edit, stage, or commit source — this command only records deferred work. No `Write`/`Edit` of source, no `git add` of code.
|
|
69
|
+
- Log a test, input-validation, error-handling, security, or required-edge-case removal as debt — those are completeness, never economy (the rubric's safety boundaries; completeness wins).
|
|
70
|
+
- Record low-confidence "could be cleaner" noise — every entry needs a concrete file + replacement, same bar as `/np:simplify-review`.
|
|
71
|
+
</scope_guardrail>
|
|
72
|
+
|
|
73
|
+
## Output
|
|
74
|
+
|
|
75
|
+
- The ledger view (open / resolved / all) or the result of an `add` / `resolve`, printed verbatim.
|
|
76
|
+
- On `harvest`: one ledger entry per deferred finding, then the refreshed open ledger.
|
|
77
|
+
- No source changes, no code commits.
|
|
78
|
+
|
|
79
|
+
## Definition of Done
|
|
80
|
+
|
|
81
|
+
Ledger CRUD. Definition of Done, per [`templates/COMPLETENESS.md`](../templates/COMPLETENESS.md):
|
|
82
|
+
|
|
83
|
+
- Rule 3 (Do it with tests) — the ledger NEVER records deleting or weakening test code; coverage is completeness, not debt.
|
|
84
|
+
- Rule 8 (Never present a workaround when the real fix exists) — entries point at the root-cause-simple form, not an obscure golfed one-liner.
|
|
85
|
+
- Rule 11 (Ship the complete thing) — deferred simplifications are tracked, not silently dropped; "later" stays visible until `resolve`.
|
|
86
|
+
|
|
87
|
+
Any violation = the operation is incomplete; surface it and exit non-zero. The orchestrator does not relax these.
|
|
88
|
+
|
|
89
|
+
## Related Workflows
|
|
90
|
+
|
|
91
|
+
- **`/np:simplify-review <git-range>`** — read-only economy audit of a diff (the finder; this command is the ledger that outlives a single review).
|
|
92
|
+
- **`/np:execute-phase`** — runs the Economy critic in-loop when `agents.economy` is `full` or `ultra`, enforcing the same rubric during execution.
|
|
93
|
+
- **`/np:add-todo`** — general pending-todo capture (this command is the economy-specific deferred-work ledger).
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
---
|
|
2
|
+
command: np:simplify-review
|
|
3
|
+
description: Read-only economy audit of a git diff, the working tree, or the whole repo (--repo) — flags over-engineering, stdlib-reinvention, native-duplication, and shrinkable logic via the np-simplifier agent. Never edits or commits; emits a deletion-oriented report. Manual twin of the Economy critic axis (agents.economy full/ultra).
|
|
4
|
+
argument-hint: "[<git-range> | --repo] (default: working tree + staged vs HEAD)"
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# /np:simplify-review
|
|
8
|
+
|
|
9
|
+
<objective>
|
|
10
|
+
Catalogue what could be deleted, reused, or condensed in a change set — the "wrote-too-much" review. A read-only `np-simplifier` agent scans the diff against the Economy rubric (`agents/np-critic-economy.md`) and emits one finding per removable construct plus a `net: -<N> lines possible.` summary. This command NEVER edits source and NEVER commits; it hands a report to the user. It is the manual counterpart of the in-loop Economy critic (`/np:execute-phase` with `agents.economy` set to `full` or `ultra`), and both apply the identical rubric and safety boundaries.
|
|
11
|
+
</objective>
|
|
12
|
+
|
|
13
|
+
## Initialize
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
LANG_DIRECTIVE=$(node .nubos-pilot/bin/np-tools.cjs lang-directive)
|
|
17
|
+
RANGE="$*"
|
|
18
|
+
if [[ "$RANGE" == "--repo" || "$RANGE" == "--all" ]]; then
|
|
19
|
+
SCOPE_MODE="repo"
|
|
20
|
+
FILES=$(git ls-files)
|
|
21
|
+
SCOPE_DESC="whole repository (tracked files)"
|
|
22
|
+
if [[ -z "$FILES" ]]; then
|
|
23
|
+
echo "No tracked files in scope ($SCOPE_DESC). Nothing to review."
|
|
24
|
+
exit 0
|
|
25
|
+
fi
|
|
26
|
+
elif [[ -n "$RANGE" ]]; then
|
|
27
|
+
SCOPE_MODE="diff"
|
|
28
|
+
DIFF=$(git diff --no-color "$RANGE")
|
|
29
|
+
SCOPE_DESC="$RANGE"
|
|
30
|
+
else
|
|
31
|
+
SCOPE_MODE="diff"
|
|
32
|
+
DIFF=$(git diff --no-color HEAD)
|
|
33
|
+
SCOPE_DESC="working tree + staged vs HEAD"
|
|
34
|
+
fi
|
|
35
|
+
if [[ "$SCOPE_MODE" == "diff" && -z "$DIFF" ]]; then
|
|
36
|
+
echo "No changes in scope ($SCOPE_DESC). Nothing to review."
|
|
37
|
+
exit 0
|
|
38
|
+
fi
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
Capture is read-only — neither `git diff` nor `git ls-files` stages, edits, or commits anything. There are two scope modes:
|
|
42
|
+
|
|
43
|
+
- **diff** (default) — a range (`HEAD~5..HEAD`, a branch name, `--staged`, …) is passed through verbatim; with no argument the scope is uncommitted + staged work against `HEAD`. This is the "review what just changed" mode.
|
|
44
|
+
- **repo** (`--repo` / `--all`) — audits the whole tracked tree, not just a diff. `git ls-files` hands the agent the file roster; the agent walks the existing source for standing over-engineering (single-use abstractions, hand-rolled stdlib, duplicated native features, condensable logic) that predates any one change. Slower and noisier than diff mode — use it for a periodic cleanup pass, not every review.
|
|
45
|
+
|
|
46
|
+
**Language (SSOT = `.nubos-pilot/config.json` → `response_language`).** `$LANG_DIRECTIVE` is authoritative for the report's prose and the final summary line. Finding lines (`<file>:L<line>: <tag> …`), file paths, and code snippets stay canonical. Supersedes CLAUDE.md.
|
|
47
|
+
|
|
48
|
+
## Review
|
|
49
|
+
|
|
50
|
+
Spawn ONE read-only reviewer — `Agent(subagent_type="np-simplifier", prompt=<…>)`, sonnet by default. The prompt MUST carry:
|
|
51
|
+
|
|
52
|
+
- `<files_to_read>` listing `agents/np-critic-economy.md` (the canonical rubric — ladder, categories, severity bar, safety boundaries), plus `.nubos-pilot/codebase/INDEX.md` and `.nubos-pilot/RULES.md` when present (stdlib / native-feature / existing-helper context).
|
|
53
|
+
- The review scope, by mode:
|
|
54
|
+
- **diff mode** — pass the captured `$DIFF` as the scope; the agent reviews only the changed hunks.
|
|
55
|
+
- **repo mode** — pass `$SCOPE_DESC` plus the `$FILES` roster from `git ls-files`, and instruct the agent to walk the tracked source itself (`Read`/`Grep`/`Glob`) under the same rubric. The agent should skip vendored, generated, and lock files and prioritise the largest hand-written modules. Because there is no diff, findings cite the source line as `<file>:L<line>` from the file it read.
|
|
56
|
+
- `$SCOPE_MODE` and `$SCOPE_DESC` so the agent knows whether it is auditing a change set or the standing codebase.
|
|
57
|
+
- `$LANG_DIRECTIVE` so the prose follows the project language.
|
|
58
|
+
|
|
59
|
+
The agent is READ-ONLY (`tools: Read, Bash, Grep, Glob` — no Write/Edit). It returns a plain-text report: one line per finding in the shape `<file>:L<line>: <tag> <what>. <replacement>.` (tags `delete:` / `stdlib:` / `native:` / `shrink:`), ending with `net: -<N> lines possible.` or `Lean already. Ship.`
|
|
60
|
+
|
|
61
|
+
## Report
|
|
62
|
+
|
|
63
|
+
Print the agent's report verbatim to the user. Do not edit files, do not stage, do not commit — this command only catalogues. Close with the next-step hint:
|
|
64
|
+
|
|
65
|
+
```
|
|
66
|
+
Reductions are suggestions, not applied. To act on them: edit by hand, or run
|
|
67
|
+
/np:execute-phase with agents.economy set to full (or ultra) so the Economy critic
|
|
68
|
+
enforces the same bar inside the adversarial loop.
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
## Scope Guardrail
|
|
72
|
+
|
|
73
|
+
<scope_guardrail>
|
|
74
|
+
**Do:**
|
|
75
|
+
- Capture the scope read-only — `git diff` in diff mode, `git ls-files` in `--repo` mode (no staging, no `-w` rewrites, no commit).
|
|
76
|
+
- Spawn exactly one `np-simplifier` agent; pass it `agents/np-critic-economy.md` as the rubric so the manual command and the in-loop critic never diverge.
|
|
77
|
+
- Print the report verbatim; respect `$LANG_DIRECTIVE` for prose only.
|
|
78
|
+
|
|
79
|
+
**Don't:**
|
|
80
|
+
- Edit, stage, or commit anything — this is a read-only audit. No `git add`, no `commit`, no `Write`/`Edit` of source.
|
|
81
|
+
- Flag tests, input validation, error handling, security/access-control, or required edge cases as removable (the rubric's safety boundaries; completeness wins over economy).
|
|
82
|
+
- Emit low-confidence "could be cleaner" noise — high-confidence, concrete replacements only.
|
|
83
|
+
</scope_guardrail>
|
|
84
|
+
|
|
85
|
+
## Output
|
|
86
|
+
|
|
87
|
+
- A plain-text economy report to the user (findings + `net: -<N> lines possible.` or `Lean already. Ship.`).
|
|
88
|
+
- No filesystem changes, no commits, no state mutation — read-only by contract.
|
|
89
|
+
|
|
90
|
+
## Definition of Done
|
|
91
|
+
|
|
92
|
+
This workflow audits a diff for economy and reports. The Definition of Done, per [`templates/COMPLETENESS.md`](../templates/COMPLETENESS.md):
|
|
93
|
+
|
|
94
|
+
- Rule 3 (Do it with tests) — the report NEVER proposes deleting or weakening test code; coverage is completeness, not bloat.
|
|
95
|
+
- Rule 5 (Aim to genuinely impress) — every finding cites file, line, the exact construct, and a concrete replacement; no vague "could be simpler" entries.
|
|
96
|
+
- Rule 8 (Never present a workaround when the real fix exists) — reductions favour the root-cause-simple form over obscure golfed one-liners.
|
|
97
|
+
|
|
98
|
+
Any violation = the review is incomplete; surface it and exit non-zero. The orchestrator does not relax these.
|
|
99
|
+
|
|
100
|
+
## Related Workflows
|
|
101
|
+
|
|
102
|
+
- **`/np:verify-work <N>`** — correctness/acceptance verification (the orthogonal axis; economy never judges correctness).
|
|
103
|
+
- **`/np:execute-phase`** — runs the Economy critic in-loop when `agents.economy` is `full` or `ultra`, enforcing this exact rubric during execution.
|