nubos-pilot 1.3.2 → 1.3.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.
- package/CHANGELOG.md +5 -2
- 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/agents/np-task-architect.md +95 -0
- package/agents/np-test-writer.md +89 -0
- package/bin/install.js +86 -0
- package/bin/np-tools/_commands.cjs +2 -0
- package/bin/np-tools/commit-task.cjs +80 -6
- package/bin/np-tools/commit-task.test.cjs +133 -0
- package/bin/np-tools/doctor.cjs +1 -0
- package/bin/np-tools/economy-mode.cjs +47 -0
- package/bin/np-tools/loop-commands.test.cjs +121 -2
- package/bin/np-tools/loop-run-round.cjs +122 -6
- package/bin/np-tools/resolve-model.cjs +1 -0
- package/bin/np-tools/simplify-debt.cjs +91 -0
- package/bin/np-tools/simplify-debt.test.cjs +99 -0
- package/lib/agents-registry.cjs +12 -1
- package/lib/agents.test.cjs +4 -0
- package/lib/config-defaults.cjs +22 -1
- package/lib/config-defaults.test.cjs +9 -0
- package/lib/config-schema.cjs +6 -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 +6 -2
- package/lib/git.test.cjs +28 -0
- 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/templates/RULES.md +36 -1
- package/workflows/execute-phase.md +154 -1
- package/workflows/plan-phase.md +17 -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
|
@@ -58,7 +58,7 @@ function assertCommittablePaths(paths, opts) {
|
|
|
58
58
|
return committable;
|
|
59
59
|
}
|
|
60
60
|
|
|
61
|
-
function commitTask(taskId, files, message) {
|
|
61
|
+
function commitTask(taskId, files, message, body) {
|
|
62
62
|
const { committable, ignored } = classifyCommittablePaths(files);
|
|
63
63
|
if (committable.length === 0) {
|
|
64
64
|
if (ignored.length > 0) {
|
|
@@ -84,7 +84,11 @@ function commitTask(taskId, files, message) {
|
|
|
84
84
|
});
|
|
85
85
|
}
|
|
86
86
|
execFileSync('git', ['add', '--', ...committable], { stdio: 'pipe', timeout: GIT_TIMEOUT_MS });
|
|
87
|
-
|
|
87
|
+
const commitArgs = ['commit', '-m', message];
|
|
88
|
+
if (typeof body === 'string' && body.trim().length > 0) {
|
|
89
|
+
commitArgs.push('-m', body);
|
|
90
|
+
}
|
|
91
|
+
execFileSync('git', [...commitArgs, '--', ...committable], { stdio: 'pipe', timeout: GIT_TIMEOUT_MS });
|
|
88
92
|
return {
|
|
89
93
|
committed: true,
|
|
90
94
|
files_committed: committable.slice(),
|
package/lib/git.test.cjs
CHANGED
|
@@ -199,6 +199,34 @@ test('GIT-5: commitTask creates a single commit containing exactly the supplied
|
|
|
199
199
|
});
|
|
200
200
|
});
|
|
201
201
|
|
|
202
|
+
test('GIT-5b: commitTask attaches a multi-line body via a second -m when body is supplied', () => {
|
|
203
|
+
const root = makeRepo();
|
|
204
|
+
inRepo(root, () => {
|
|
205
|
+
writeFile(root, 'lib/git.cjs', '// stub');
|
|
206
|
+
git.commitTask(
|
|
207
|
+
'M006-S001-T0001',
|
|
208
|
+
['lib/git.cjs'],
|
|
209
|
+
'task(M006-S001-T0001): add git helper',
|
|
210
|
+
'Implements the git helper.\n\nTask: M006-S001-T0001',
|
|
211
|
+
);
|
|
212
|
+
const subject = execFileSync('git', ['log', '-n', '1', '--format=%s'], { encoding: 'utf-8' }).trim();
|
|
213
|
+
const fullBody = execFileSync('git', ['log', '-n', '1', '--format=%b'], { encoding: 'utf-8' });
|
|
214
|
+
assert.equal(subject, 'task(M006-S001-T0001): add git helper');
|
|
215
|
+
assert.match(fullBody, /Implements the git helper\./);
|
|
216
|
+
assert.match(fullBody, /Task: M006-S001-T0001/);
|
|
217
|
+
});
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
test('GIT-5c: commitTask omits the body -m when body is empty/whitespace (backward-compatible)', () => {
|
|
221
|
+
const root = makeRepo();
|
|
222
|
+
inRepo(root, () => {
|
|
223
|
+
writeFile(root, 'lib/git.cjs', '// stub');
|
|
224
|
+
git.commitTask('M006-S001-T0001', ['lib/git.cjs'], 'task(M006-S001-T0001): add git helper', ' ');
|
|
225
|
+
const fullBody = execFileSync('git', ['log', '-n', '1', '--format=%b'], { encoding: 'utf-8' }).trim();
|
|
226
|
+
assert.equal(fullBody, '');
|
|
227
|
+
});
|
|
228
|
+
});
|
|
229
|
+
|
|
202
230
|
test('GIT-6: findCommitByTaskId returns 40-char SHA for known task commit', () => {
|
|
203
231
|
const root = makeRepo();
|
|
204
232
|
inRepo(root, () => {
|
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
package/templates/RULES.md
CHANGED
|
@@ -79,7 +79,33 @@ Examples:
|
|
|
79
79
|
-->
|
|
80
80
|
- _TBD — fill with logging policy._
|
|
81
81
|
|
|
82
|
-
##
|
|
82
|
+
## Conventions
|
|
83
|
+
|
|
84
|
+
> **How your code must be built.** These rules bind the architect (`np-task-architect`),
|
|
85
|
+
> the test-writer (`np-test-writer`), the executor, and the style critic
|
|
86
|
+
> (`np-critic-style`). They are read on every task. Each subsection is **MUST FILL** —
|
|
87
|
+
> use `- _none — <reason>_` only when a subsection genuinely does not apply.
|
|
88
|
+
|
|
89
|
+
### Class / Module Structure
|
|
90
|
+
|
|
91
|
+
<!-- How classes, modules, and units are shaped. Examples:
|
|
92
|
+
- One public class per file; file name matches the class name.
|
|
93
|
+
- Constructor injection only — no service-locator / static singletons.
|
|
94
|
+
- Business logic lives in Services/Actions; controllers stay thin (no DB access).
|
|
95
|
+
- Public surface ≤ 5 methods; split when it grows past that.
|
|
96
|
+
-->
|
|
97
|
+
- _TBD — fill with class/module construction rules._
|
|
98
|
+
|
|
99
|
+
### Naming
|
|
100
|
+
|
|
101
|
+
<!-- Identifier conventions. Examples:
|
|
102
|
+
- Classes PascalCase, methods camelCase, constants UPPER_SNAKE.
|
|
103
|
+
- Booleans read as predicates (`isActive`, `hasAccess`), never `flag`/`status`.
|
|
104
|
+
- Table names follow the framework's pluralization — never override.
|
|
105
|
+
-->
|
|
106
|
+
- _TBD — fill with naming conventions._
|
|
107
|
+
|
|
108
|
+
### Code Style
|
|
83
109
|
|
|
84
110
|
<!-- Format/lint/comment policy. Examples:
|
|
85
111
|
- No comments inside source — names + tests carry intent.
|
|
@@ -88,6 +114,15 @@ Examples:
|
|
|
88
114
|
-->
|
|
89
115
|
- _TBD — fill with style policy._
|
|
90
116
|
|
|
117
|
+
### Patterns & Paradigms
|
|
118
|
+
|
|
119
|
+
<!-- Architectural patterns that are required or banned. Examples:
|
|
120
|
+
- Required: Repository pattern for all persistence; Result objects over exceptions for control flow.
|
|
121
|
+
- Banned: anemic domain models; inheritance for code reuse (prefer composition).
|
|
122
|
+
- Errors are typed and surfaced — never swallowed or stringly-typed.
|
|
123
|
+
-->
|
|
124
|
+
- _TBD — fill with required/banned patterns._
|
|
125
|
+
|
|
91
126
|
## Out-of-Scope (Forever)
|
|
92
127
|
|
|
93
128
|
<!-- Things this project explicitly will never do. Distinct from deferred ideas.
|
|
@@ -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`).**
|
|
@@ -213,6 +219,10 @@ SPAWN_HEADLESS_ENABLED=$(node .nubos-pilot/bin/np-tools.cjs config-get spawn.hea
|
|
|
213
219
|
SPAWN_HEADLESS_AGENTS=$(node .nubos-pilot/bin/np-tools.cjs config-get spawn.headless.agents 2>/dev/null || echo '["np-critic","np-researcher"]')
|
|
214
220
|
SPAWN_HEADLESS_FALLBACK=$(node .nubos-pilot/bin/np-tools.cjs config-get spawn.headless.fallback_on_error 2>/dev/null || echo true)
|
|
215
221
|
CONF_INJECT_CRITERIA=$(node .nubos-pilot/bin/np-tools.cjs config-get conformance.inject_criteria 2>/dev/null || echo true)
|
|
222
|
+
# Round-1 prep agents (default on; backfilled on install/update). When a toggle
|
|
223
|
+
# is false the matching ACTION CONTRACT (Step 2b / Step 2c) is skipped wholesale.
|
|
224
|
+
ARCHITECT_ENABLED=$(node .nubos-pilot/bin/np-tools.cjs config-get agents.architect 2>/dev/null || echo true)
|
|
225
|
+
TEST_WRITER_ENABLED=$(node .nubos-pilot/bin/np-tools.cjs config-get agents.test_writer 2>/dev/null || echo true)
|
|
216
226
|
# Milestone success_criteria as the executor's acceptance target (rendered once from the INIT payload).
|
|
217
227
|
# Intent-level only (ADR-0019): these describe what "done right" means, NOT how to build it.
|
|
218
228
|
SUCCESS_CRITERIA_BLOCK=$(echo "$INIT" | node -e 'process.stdin.on("data",d=>{try{const c=JSON.parse(d).success_criteria||[];console.log(c.map(x=>"- "+(x.id?x.id+": ":"")+(x.text||x)).join("\n"))}catch(e){console.log("")}})')
|
|
@@ -408,6 +418,131 @@ for WAVE_INDEX in 0 1 2 ...; do
|
|
|
408
418
|
CONSENSUS_PATTERN=""
|
|
409
419
|
fi
|
|
410
420
|
|
|
421
|
+
# ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
422
|
+
# ACTION CONTRACT — Step 2b: Per-Task Architect (Round 1, config-gated)
|
|
423
|
+
# ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
424
|
+
# WHEN: $ROUND -eq 1 AND $ARCHITECT_ENABLED = true. Skip wholesale otherwise
|
|
425
|
+
# (agents.architect=false → no architect this run; R≥2 build-fixer rounds
|
|
426
|
+
# never run it).
|
|
427
|
+
# SKIP-GUARD: loop-post-architect-missing-spawn-audit (needs 1 architect audit).
|
|
428
|
+
#
|
|
429
|
+
# Execute EXACTLY these three groups, in order:
|
|
430
|
+
#
|
|
431
|
+
# (1) ONE Agent tool-call (real, not bash):
|
|
432
|
+
# Agent(subagent_type="np-task-architect", prompt=<…>)
|
|
433
|
+
# Prompt fields:
|
|
434
|
+
# <files_to_read>: task plan, slice plan, CONTEXT.md, RULES.md,
|
|
435
|
+
# M<NNN>-ARCHITECTURE.md (if present), .nubos-pilot/codebase/INDEX.md
|
|
436
|
+
# <consensus_pattern>: $CONSENSUS_PATTERN (researcher output; may be empty)
|
|
437
|
+
# <lang_directive>: $LANG_DIRECTIVE
|
|
438
|
+
# Curated skills (quality bar) — instruct the agent to Read each that
|
|
439
|
+
# applies from .claude/skills/<skill>/SKILL.md: np-system-design,
|
|
440
|
+
# np-service-boundary, np-api-design, np-composition-patterns,
|
|
441
|
+
# np-error-handling, np-adr (only for a costly-to-reverse choice).
|
|
442
|
+
# The agent is READ-ONLY: it emits its Task-Architecture spec as its FINAL
|
|
443
|
+
# MESSAGE (markdown per its Output Contract). Write that message verbatim
|
|
444
|
+
# to "$ARCH_CONSTRAINTS_PATH".
|
|
445
|
+
#
|
|
446
|
+
# (2) ONE Bash audit-stamp (same round) — architect is NOT Rule-9 audited,
|
|
447
|
+
# so an empty tool-use log is correct:
|
|
448
|
+
# node .nubos-pilot/bin/np-tools.cjs loop-audit-tool-use "$TASK_ID" \
|
|
449
|
+
# --agent np-task-architect --tool-use-log '[]'
|
|
450
|
+
#
|
|
451
|
+
# (3) ONE Bash advance:
|
|
452
|
+
# node .nubos-pilot/bin/np-tools.cjs loop-run-round "$TASK_ID" \
|
|
453
|
+
# --phase post-architect
|
|
454
|
+
#
|
|
455
|
+
# $ARCH_CONSTRAINTS is injected as <architecture_constraints> into the
|
|
456
|
+
# test-writer (Step 2c) AND executor (Step 3) prompts.
|
|
457
|
+
#
|
|
458
|
+
# Rationale: ADR-0023 — a per-task structural pass before tests/code so the
|
|
459
|
+
# test-writer and executor build against a decided shape, honouring RULES.md
|
|
460
|
+
# Conventions. Ephemeral ($TMPDIR, never committed) → plan-lint untouched.
|
|
461
|
+
# ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
462
|
+
ARCH_CONSTRAINTS=""
|
|
463
|
+
ARCH_CONSTRAINTS_PATH="${TMPDIR:-/tmp}/np-arch-${TASK_ID}.md"
|
|
464
|
+
if [ "$ROUND" -eq 1 ] && [ "$ARCHITECT_ENABLED" = "true" ]; then
|
|
465
|
+
# Off-host (ADR-0021): np-task-architect is read-only (Read/Grep/Glob), not
|
|
466
|
+
# Rule-9 audited, writes no files — run via spawn-offhost with default cwd
|
|
467
|
+
# when it routes to an openai-compat provider; its spec returns as the
|
|
468
|
+
# spawn's final message (content).
|
|
469
|
+
ARCHITECT_KIND=$(node .nubos-pilot/bin/np-tools.cjs resolve-model np-task-architect --json 2>/dev/null \
|
|
470
|
+
| node -e 'let s="";process.stdin.on("data",d=>s+=d).on("end",()=>{try{console.log(JSON.parse(s).kind||"native")}catch{console.log("native")}})')
|
|
471
|
+
if [ "$ARCHITECT_KIND" = "openai-compat" ]; then
|
|
472
|
+
A_PROMPT="${TMPDIR:-/tmp}/np-offhost-task-architect-${TASK_ID}.md"
|
|
473
|
+
# … render the files_to_read block + consensus + skills + $LANG_DIRECTIVE into "$A_PROMPT" …
|
|
474
|
+
A_OUT=$(node .nubos-pilot/bin/np-tools.cjs spawn-offhost \
|
|
475
|
+
--agent np-task-architect --task-file "$A_PROMPT" --task-id "$TASK_ID" \
|
|
476
|
+
--read-only --no-audit ${SLICE_CWD:+--cwd "$SLICE_CWD"})
|
|
477
|
+
echo "$A_OUT" | ARCH_PATH="$ARCH_CONSTRAINTS_PATH" node -e 'let s="";process.stdin.on("data",d=>s+=d).on("end",()=>{let c="";try{c=JSON.parse(s).content||""}catch{}require("fs").writeFileSync(process.env.ARCH_PATH,c)})'
|
|
478
|
+
else
|
|
479
|
+
true # → execute group (1): native Agent spawn; write its final message to "$ARCH_CONSTRAINTS_PATH".
|
|
480
|
+
fi
|
|
481
|
+
node .nubos-pilot/bin/np-tools.cjs loop-audit-tool-use "$TASK_ID" --agent np-task-architect --tool-use-log '[]'
|
|
482
|
+
node .nubos-pilot/bin/np-tools.cjs loop-run-round "$TASK_ID" --phase post-architect
|
|
483
|
+
[ -f "$ARCH_CONSTRAINTS_PATH" ] && ARCH_CONSTRAINTS=$(cat "$ARCH_CONSTRAINTS_PATH")
|
|
484
|
+
fi
|
|
485
|
+
|
|
486
|
+
# ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
487
|
+
# ACTION CONTRACT — Step 2c: Test-Writer / TDD (Round 1, config-gated)
|
|
488
|
+
# ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
489
|
+
# WHEN: $ROUND -eq 1 AND $TEST_WRITER_ENABLED = true. Runs AFTER the architect,
|
|
490
|
+
# BEFORE the executor. Skip wholesale otherwise.
|
|
491
|
+
# SKIP-GUARD: loop-post-test-writer-missing-spawn-audit (needs 1 test-writer audit).
|
|
492
|
+
#
|
|
493
|
+
# Execute EXACTLY these three groups, in order:
|
|
494
|
+
#
|
|
495
|
+
# (1) ONE Agent tool-call (real, not bash):
|
|
496
|
+
# Agent(subagent_type="np-test-writer", prompt=<…>)
|
|
497
|
+
# Prompt fields:
|
|
498
|
+
# <files_to_read>: task plan, slice plan, RULES.md, neighbouring tests
|
|
499
|
+
# <architecture_constraints>: $ARCH_CONSTRAINTS (the architect's required
|
|
500
|
+
# test surfaces; empty when the architect is disabled)
|
|
501
|
+
# <success_criteria>: $SUCCESS_CRITERIA_BLOCK + slice UAT path (intent-level)
|
|
502
|
+
# <lang_directive>: $LANG_DIRECTIVE
|
|
503
|
+
# Curated skill (quality bar) — instruct the agent to Read
|
|
504
|
+
# .claude/skills/np-test-strategy/SKILL.md and satisfy its Verification bar.
|
|
505
|
+
# RULES — the agent writes REAL, VALID test files for every required surface;
|
|
506
|
+
# it MUST NOT skip/stub/weaken assertions (Rule 10). Tests MAY be red now;
|
|
507
|
+
# the executor makes them green. The agent emits a JSON envelope whose
|
|
508
|
+
# tests_written paths you collect into $TDD_TESTS.
|
|
509
|
+
#
|
|
510
|
+
# (2) ONE Bash audit-stamp (same round) — test-writer is NOT Rule-9 audited:
|
|
511
|
+
# node .nubos-pilot/bin/np-tools.cjs loop-audit-tool-use "$TASK_ID" \
|
|
512
|
+
# --agent np-test-writer --tool-use-log '[]'
|
|
513
|
+
#
|
|
514
|
+
# (3) ONE Bash advance — pass the written test paths so they are recorded in
|
|
515
|
+
# the checkpoint (nubosloop.tdd_tests) and commit-task folds them into the
|
|
516
|
+
# commit even when files_modified did not enumerate them:
|
|
517
|
+
# node .nubos-pilot/bin/np-tools.cjs loop-run-round "$TASK_ID" \
|
|
518
|
+
# --phase post-test-writer --tests "$TDD_TESTS"
|
|
519
|
+
#
|
|
520
|
+
# Rationale: ADR-0023 — TDD inside the loop. The mechanical verify gate
|
|
521
|
+
# (Step 4) runs only AFTER the executor, so red-until-executor is expected
|
|
522
|
+
# and not a failure. The np-critic-tests axis (Step 5) re-audits for any
|
|
523
|
+
# skipped/vacuous assertions that slipped through.
|
|
524
|
+
# ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
525
|
+
TDD_TESTS=""
|
|
526
|
+
if [ "$ROUND" -eq 1 ] && [ "$TEST_WRITER_ENABLED" = "true" ]; then
|
|
527
|
+
# Off-host (ADR-0021): np-test-writer writes test files, so off-host needs
|
|
528
|
+
# worktree isolation exactly like the executor (model-driven Write confined
|
|
529
|
+
# + ff-merged back). When worktree isolation is off, it runs native.
|
|
530
|
+
TEST_WRITER_KIND=$(node .nubos-pilot/bin/np-tools.cjs resolve-model np-test-writer --json 2>/dev/null \
|
|
531
|
+
| node -e 'let s="";process.stdin.on("data",d=>s+=d).on("end",()=>{try{console.log(JSON.parse(s).kind||"native")}catch{console.log("native")}})')
|
|
532
|
+
if [ "$TEST_WRITER_KIND" = "openai-compat" ] && [ "$WORKTREE_ISOLATION" = "true" ] && [ -n "$SLICE_CWD" ] && [ "$SLICE_CWD" != "." ]; then
|
|
533
|
+
TW_PROMPT="${TMPDIR:-/tmp}/np-offhost-test-writer-${TASK_ID}.md"
|
|
534
|
+
# … render files_to_read + architecture_constraints + success_criteria + skill + $LANG_DIRECTIVE into "$TW_PROMPT" …
|
|
535
|
+
TW_OUT=$(node .nubos-pilot/bin/np-tools.cjs spawn-offhost \
|
|
536
|
+
--agent np-test-writer --task-file "$TW_PROMPT" --task-id "$TASK_ID" \
|
|
537
|
+
--cwd "$SLICE_CWD" --allow-bash --no-audit)
|
|
538
|
+
TDD_TESTS=$(echo "$TW_OUT" | node -e 'let s="";process.stdin.on("data",d=>s+=d).on("end",()=>{try{const j=JSON.parse(JSON.parse(s).content||"{}");console.log((j.tests_written||[]).join(", "))}catch{console.log("")}})')
|
|
539
|
+
else
|
|
540
|
+
true # → execute group (1): native Agent spawn; collect tests_written from the envelope into $TDD_TESTS.
|
|
541
|
+
fi
|
|
542
|
+
node .nubos-pilot/bin/np-tools.cjs loop-audit-tool-use "$TASK_ID" --agent np-test-writer --tool-use-log '[]'
|
|
543
|
+
node .nubos-pilot/bin/np-tools.cjs loop-run-round "$TASK_ID" --phase post-test-writer --tests "$TDD_TESTS"
|
|
544
|
+
fi
|
|
545
|
+
|
|
411
546
|
# ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
412
547
|
# ACTION CONTRACT — Step 3: Executor (R1) / Build-Fixer (R≥2)
|
|
413
548
|
# ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
@@ -418,6 +553,13 @@ for WAVE_INDEX in 0 1 2 ...; do
|
|
|
418
553
|
# Prompt fields:
|
|
419
554
|
# <files_to_read>: task plan, slice plan, prior slice SUMMARYs, CONTEXT.md
|
|
420
555
|
# <consensus_pattern>: $CONSENSUS_PATTERN (with [VERIFIED]/[PROVISIONAL]/[CACHED])
|
|
556
|
+
# <architecture_constraints>: $ARCH_CONSTRAINTS — the per-task architect's
|
|
557
|
+
# decided structure + constraints (empty when agents.architect is off).
|
|
558
|
+
# The executor builds against this shape; it is intent-level, not a code spec.
|
|
559
|
+
# <tdd_tests>: $TDD_TESTS — test files np-test-writer wrote (R1, empty when off).
|
|
560
|
+
# The executor MUST make them green WITHOUT deleting, skipping, or weakening
|
|
561
|
+
# any assertion. They are in scope alongside files_modified (recorded in the
|
|
562
|
+
# checkpoint at post-test-writer) and commit-task commits them with the diff.
|
|
421
563
|
# <success_criteria>: when $CONF_INJECT_CRITERIA = true, include the milestone
|
|
422
564
|
# acceptance target — $SUCCESS_CRITERIA_BLOCK plus the slice UAT path
|
|
423
565
|
# (.nubos-pilot/milestones/M<NNN>/slices/S<NNN>/S<NNN>-UAT.md). Frame it as
|
|
@@ -427,7 +569,12 @@ for WAVE_INDEX in 0 1 2 ...; do
|
|
|
427
569
|
# <verify_excerpt>: tail of $VERIFY_LOG (R≥2 only)
|
|
428
570
|
# <lang_directive>: $LANG_DIRECTIVE
|
|
429
571
|
# <skills>: $AGENT_SKILLS_EXECUTOR
|
|
430
|
-
#
|
|
572
|
+
# <economy_mode>: $ECONOMY_MODE — when $ECONOMY_PREVENTION = true (lite/full/
|
|
573
|
+
# ultra) instruct the agent to APPLY the np-executor "Climb the ladder"
|
|
574
|
+
# discipline before writing (prevention-first). When $ECONOMY_MODE = off,
|
|
575
|
+
# instruct it to SKIP the ladder (no economy pressure this run).
|
|
576
|
+
# RULES — Agent MUST: edit ONLY paths in files_modified plus the <tdd_tests>
|
|
577
|
+
# paths (D-04 scope guard; the TDD tests are the sole sanctioned addition) —
|
|
431
578
|
# success_criteria are the acceptance target, NEVER a licence to touch other files,
|
|
432
579
|
# run `node np-tools.cjs knowledge-search "<q>" --task $TASK_ID` via Bash
|
|
433
580
|
# ≥1× (Rule 9 — the --task flag writes the audit evidence ledger),
|
|
@@ -566,6 +713,12 @@ for WAVE_INDEX in 0 1 2 ...; do
|
|
|
566
713
|
# - agents/np-critic-style.md
|
|
567
714
|
# - agents/np-critic-tests.md
|
|
568
715
|
# - agents/np-critic-acceptance.md
|
|
716
|
+
# - agents/np-critic-economy.md ← ONLY when $ECONOMY_CRITIC = true (mode full/ultra)
|
|
717
|
+
# (resolved once in the init block via `economy-mode --json`; omit this line
|
|
718
|
+
# entirely when $ECONOMY_CRITIC = false — default mode lite has prevention
|
|
719
|
+
# on but the critic off). When $ECONOMY_MODE = ultra, ALSO append to the
|
|
720
|
+
# prompt: "Economy mode: ultra — lower the shrinkable bar per the Ultra
|
|
721
|
+
# section of np-critic-economy.md." Never inject the module at off/lite.
|
|
569
722
|
# <report_path>$CRITIC_REPORT_PATH</report_path>
|
|
570
723
|
# Agent MUST: Write the full findings JSON to $CRITIC_REPORT_PATH,
|
|
571
724
|
# emit ONLY the verdict-envelope as final message (~150 bytes):
|