nubos-pilot 0.9.1 → 0.9.2
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/bin/np-tools/research-merge.cjs +105 -0
- package/bin/np-tools/research-merge.test.cjs +166 -0
- package/package.json +1 -1
- package/workflows/execute-phase.md +166 -107
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const fs = require('node:fs');
|
|
4
|
+
const path = require('node:path');
|
|
5
|
+
|
|
6
|
+
const { NubosPilotError } = require('../../lib/core.cjs');
|
|
7
|
+
const swarm = require('../../lib/researcher-swarm.cjs');
|
|
8
|
+
|
|
9
|
+
function _parseArgs(args) {
|
|
10
|
+
const out = { inputs: null, output: null, heading: null };
|
|
11
|
+
for (let i = 0; i < args.length; i++) {
|
|
12
|
+
const a = args[i];
|
|
13
|
+
if (a === '--inputs') { out.inputs = args[++i] || null; continue; }
|
|
14
|
+
if (a === '--output') { out.output = args[++i] || null; continue; }
|
|
15
|
+
if (a === '--heading') { out.heading = args[++i] || null; continue; }
|
|
16
|
+
}
|
|
17
|
+
return out;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function _readSpawnOutput(filePath) {
|
|
21
|
+
let raw;
|
|
22
|
+
try {
|
|
23
|
+
raw = fs.readFileSync(filePath, 'utf-8');
|
|
24
|
+
} catch (err) {
|
|
25
|
+
throw new NubosPilotError(
|
|
26
|
+
'research-merge-input-missing',
|
|
27
|
+
'Cannot read spawn output file: ' + filePath + ' — ' + err.message,
|
|
28
|
+
{ path: filePath },
|
|
29
|
+
);
|
|
30
|
+
}
|
|
31
|
+
let parsed;
|
|
32
|
+
try {
|
|
33
|
+
parsed = JSON.parse(raw);
|
|
34
|
+
} catch (err) {
|
|
35
|
+
throw new NubosPilotError(
|
|
36
|
+
'research-merge-input-invalid-json',
|
|
37
|
+
'Spawn output is not valid JSON: ' + filePath + ' — ' + err.message,
|
|
38
|
+
{ path: filePath },
|
|
39
|
+
);
|
|
40
|
+
}
|
|
41
|
+
if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) {
|
|
42
|
+
throw new NubosPilotError(
|
|
43
|
+
'research-merge-input-shape',
|
|
44
|
+
'Spawn output must be a JSON object: ' + filePath,
|
|
45
|
+
{ path: filePath },
|
|
46
|
+
);
|
|
47
|
+
}
|
|
48
|
+
return parsed;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function run(args, opts) {
|
|
52
|
+
const o = opts || {};
|
|
53
|
+
const cwd = o.cwd || process.cwd();
|
|
54
|
+
const stdout = o.stdout || process.stdout;
|
|
55
|
+
const parsed = _parseArgs(Array.isArray(args) ? args : []);
|
|
56
|
+
|
|
57
|
+
if (!parsed.inputs) {
|
|
58
|
+
throw new NubosPilotError(
|
|
59
|
+
'research-merge-missing-inputs',
|
|
60
|
+
'research-merge requires --inputs <comma-separated JSON paths>',
|
|
61
|
+
{ args },
|
|
62
|
+
);
|
|
63
|
+
}
|
|
64
|
+
if (!parsed.output) {
|
|
65
|
+
throw new NubosPilotError(
|
|
66
|
+
'research-merge-missing-output',
|
|
67
|
+
'research-merge requires --output <RESEARCH.md path>',
|
|
68
|
+
{ args },
|
|
69
|
+
);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const inputPaths = parsed.inputs.split(',')
|
|
73
|
+
.map((s) => s.trim())
|
|
74
|
+
.filter(Boolean)
|
|
75
|
+
.map((p) => (path.isAbsolute(p) ? p : path.resolve(cwd, p)));
|
|
76
|
+
if (!inputPaths.length) {
|
|
77
|
+
throw new NubosPilotError(
|
|
78
|
+
'research-merge-empty-inputs',
|
|
79
|
+
'research-merge --inputs resolved to zero paths',
|
|
80
|
+
{ raw: parsed.inputs },
|
|
81
|
+
);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const spawnOutputs = inputPaths.map(_readSpawnOutput);
|
|
85
|
+
const consensus = swarm.mergeConsensus(spawnOutputs);
|
|
86
|
+
const md = swarm.renderConsensusToMarkdown(
|
|
87
|
+
consensus,
|
|
88
|
+
parsed.heading ? { heading: parsed.heading } : undefined,
|
|
89
|
+
);
|
|
90
|
+
|
|
91
|
+
const outputPath = path.isAbsolute(parsed.output)
|
|
92
|
+
? parsed.output
|
|
93
|
+
: path.resolve(cwd, parsed.output);
|
|
94
|
+
fs.mkdirSync(path.dirname(outputPath), { recursive: true });
|
|
95
|
+
fs.writeFileSync(outputPath, md, 'utf-8');
|
|
96
|
+
|
|
97
|
+
stdout.write(JSON.stringify({
|
|
98
|
+
output_path: outputPath,
|
|
99
|
+
inputs: inputPaths,
|
|
100
|
+
meta: consensus.meta,
|
|
101
|
+
}) + '\n');
|
|
102
|
+
return 0;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
module.exports = { run, _parseArgs };
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { test, afterEach } = require('node:test');
|
|
4
|
+
const assert = require('node:assert/strict');
|
|
5
|
+
const fs = require('node:fs');
|
|
6
|
+
const path = require('node:path');
|
|
7
|
+
|
|
8
|
+
const { makeSandbox, cleanupAll } = require('../../tests/helpers/fixture.cjs');
|
|
9
|
+
const subcmd = require('./research-merge.cjs');
|
|
10
|
+
|
|
11
|
+
afterEach(cleanupAll);
|
|
12
|
+
|
|
13
|
+
function _capture() {
|
|
14
|
+
let buf = '';
|
|
15
|
+
const stub = { write: (s) => { buf += s; return true; } };
|
|
16
|
+
return { stub, get: () => buf };
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function _writeSpawn(sandbox, name, payload) {
|
|
20
|
+
const dir = path.join(sandbox, '.nubos-pilot', '.tmp-swarm');
|
|
21
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
22
|
+
const target = path.join(dir, name);
|
|
23
|
+
fs.writeFileSync(target, JSON.stringify(payload), 'utf-8');
|
|
24
|
+
return target;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
test('RM-1: merges 3 spawn JSONs into RESEARCH.md and emits meta', () => {
|
|
28
|
+
const sandbox = makeSandbox();
|
|
29
|
+
const a = _writeSpawn(sandbox, 'spawn-0.json', {
|
|
30
|
+
decisions: [{ claim: 'Use jose@6.0.10', confidence: 'HIGH', provenance: '[VERIFIED]' }],
|
|
31
|
+
risks: [{ description: 'Token expiry not validated', severity: 'HIGH' }],
|
|
32
|
+
patterns: [{ name: 'JWT verify wrapper' }],
|
|
33
|
+
open_questions: ['Refresh token rotation policy?'],
|
|
34
|
+
sources: [{ url: 'https://example.com/jose', credibility: 'HIGH' }],
|
|
35
|
+
});
|
|
36
|
+
const b = _writeSpawn(sandbox, 'spawn-1.json', {
|
|
37
|
+
decisions: [{ claim: 'Use jose@6.0.10', confidence: 'HIGH', provenance: '[VERIFIED]' }],
|
|
38
|
+
patterns: [{ name: 'JWT verify wrapper' }],
|
|
39
|
+
open_questions: ['Refresh token rotation policy?'],
|
|
40
|
+
});
|
|
41
|
+
const c = _writeSpawn(sandbox, 'spawn-2.json', {
|
|
42
|
+
decisions: [{ claim: 'Use jose@6.0.10', confidence: 'HIGH', provenance: '[VERIFIED]' }],
|
|
43
|
+
risks: [{ description: 'Token expiry not validated', severity: 'HIGH' }],
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
const out = path.join(sandbox, '.nubos-pilot', 'milestones', 'M001', 'RESEARCH.md');
|
|
47
|
+
const cap = _capture();
|
|
48
|
+
const rc = subcmd.run([
|
|
49
|
+
'--inputs', [a, b, c].join(','),
|
|
50
|
+
'--output', out,
|
|
51
|
+
], { cwd: sandbox, stdout: cap.stub });
|
|
52
|
+
|
|
53
|
+
assert.equal(rc, 0);
|
|
54
|
+
const payload = JSON.parse(cap.get().trim());
|
|
55
|
+
assert.equal(payload.output_path, out);
|
|
56
|
+
assert.equal(payload.meta.k, 3);
|
|
57
|
+
assert.equal(payload.meta.flagged_count, 0);
|
|
58
|
+
assert.ok(fs.existsSync(out));
|
|
59
|
+
const md = fs.readFileSync(out, 'utf-8');
|
|
60
|
+
assert.match(md, /# Researcher-Schwarm Consensus/);
|
|
61
|
+
assert.match(md, /<consensus_meta>/);
|
|
62
|
+
assert.match(md, /Use jose@6\.0\.10/);
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
test('RM-2: --heading overrides default consensus heading', () => {
|
|
66
|
+
const sandbox = makeSandbox();
|
|
67
|
+
const a = _writeSpawn(sandbox, 's.json', { decisions: [{ claim: 'X' }] });
|
|
68
|
+
const out = path.join(sandbox, 'R.md');
|
|
69
|
+
const cap = _capture();
|
|
70
|
+
subcmd.run([
|
|
71
|
+
'--inputs', a,
|
|
72
|
+
'--output', out,
|
|
73
|
+
'--heading', 'M001 Phase 5 Research',
|
|
74
|
+
], { cwd: sandbox, stdout: cap.stub });
|
|
75
|
+
const md = fs.readFileSync(out, 'utf-8');
|
|
76
|
+
assert.match(md, /^# M001 Phase 5 Research/);
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
test('RM-3: missing --inputs throws structured error', () => {
|
|
80
|
+
const sandbox = makeSandbox();
|
|
81
|
+
const cap = _capture();
|
|
82
|
+
assert.throws(
|
|
83
|
+
() => subcmd.run(['--output', 'R.md'], { cwd: sandbox, stdout: cap.stub }),
|
|
84
|
+
(err) => err && err.code === 'research-merge-missing-inputs',
|
|
85
|
+
);
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
test('RM-4: missing --output throws structured error', () => {
|
|
89
|
+
const sandbox = makeSandbox();
|
|
90
|
+
const cap = _capture();
|
|
91
|
+
assert.throws(
|
|
92
|
+
() => subcmd.run(['--inputs', 'a.json'], { cwd: sandbox, stdout: cap.stub }),
|
|
93
|
+
(err) => err && err.code === 'research-merge-missing-output',
|
|
94
|
+
);
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
test('RM-5: missing input file throws structured error', () => {
|
|
98
|
+
const sandbox = makeSandbox();
|
|
99
|
+
const cap = _capture();
|
|
100
|
+
assert.throws(
|
|
101
|
+
() => subcmd.run([
|
|
102
|
+
'--inputs', path.join(sandbox, 'nope.json'),
|
|
103
|
+
'--output', path.join(sandbox, 'R.md'),
|
|
104
|
+
], { cwd: sandbox, stdout: cap.stub }),
|
|
105
|
+
(err) => err && err.code === 'research-merge-input-missing',
|
|
106
|
+
);
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
test('RM-6: invalid JSON input throws structured error', () => {
|
|
110
|
+
const sandbox = makeSandbox();
|
|
111
|
+
const bad = path.join(sandbox, 'bad.json');
|
|
112
|
+
fs.writeFileSync(bad, '{not-json', 'utf-8');
|
|
113
|
+
const cap = _capture();
|
|
114
|
+
assert.throws(
|
|
115
|
+
() => subcmd.run([
|
|
116
|
+
'--inputs', bad,
|
|
117
|
+
'--output', path.join(sandbox, 'R.md'),
|
|
118
|
+
], { cwd: sandbox, stdout: cap.stub }),
|
|
119
|
+
(err) => err && err.code === 'research-merge-input-invalid-json',
|
|
120
|
+
);
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
test('RM-7: array-shaped JSON input throws structured error', () => {
|
|
124
|
+
const sandbox = makeSandbox();
|
|
125
|
+
const bad = path.join(sandbox, 'arr.json');
|
|
126
|
+
fs.writeFileSync(bad, '[]', 'utf-8');
|
|
127
|
+
const cap = _capture();
|
|
128
|
+
assert.throws(
|
|
129
|
+
() => subcmd.run([
|
|
130
|
+
'--inputs', bad,
|
|
131
|
+
'--output', path.join(sandbox, 'R.md'),
|
|
132
|
+
], { cwd: sandbox, stdout: cap.stub }),
|
|
133
|
+
(err) => err && err.code === 'research-merge-input-shape',
|
|
134
|
+
);
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
test('RM-8: relative paths resolve against cwd', () => {
|
|
138
|
+
const sandbox = makeSandbox();
|
|
139
|
+
const a = _writeSpawn(sandbox, 'rel.json', { decisions: [{ claim: 'Y' }] });
|
|
140
|
+
const rel = path.relative(sandbox, a);
|
|
141
|
+
const cap = _capture();
|
|
142
|
+
subcmd.run([
|
|
143
|
+
'--inputs', rel,
|
|
144
|
+
'--output', 'out.md',
|
|
145
|
+
], { cwd: sandbox, stdout: cap.stub });
|
|
146
|
+
const payload = JSON.parse(cap.get().trim());
|
|
147
|
+
assert.equal(payload.output_path, path.join(sandbox, 'out.md'));
|
|
148
|
+
assert.ok(fs.existsSync(path.join(sandbox, 'out.md')));
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
test('RM-9: flagged decision (only 1 of 3 spawns) is marked, not accepted', () => {
|
|
152
|
+
const sandbox = makeSandbox();
|
|
153
|
+
const a = _writeSpawn(sandbox, '0.json', { decisions: [{ claim: 'Solo claim' }] });
|
|
154
|
+
const b = _writeSpawn(sandbox, '1.json', { decisions: [{ claim: 'Other' }] });
|
|
155
|
+
const c = _writeSpawn(sandbox, '2.json', { decisions: [{ claim: 'Other' }] });
|
|
156
|
+
const out = path.join(sandbox, 'R.md');
|
|
157
|
+
const cap = _capture();
|
|
158
|
+
subcmd.run(['--inputs', [a, b, c].join(','), '--output', out],
|
|
159
|
+
{ cwd: sandbox, stdout: cap.stub });
|
|
160
|
+
const payload = JSON.parse(cap.get().trim());
|
|
161
|
+
assert.equal(payload.meta.k, 3);
|
|
162
|
+
assert.equal(payload.meta.flagged_count, 1);
|
|
163
|
+
const md = fs.readFileSync(out, 'utf-8');
|
|
164
|
+
assert.match(md, /## Flagged Decisions \(no majority\)/);
|
|
165
|
+
assert.match(md, /Solo claim/);
|
|
166
|
+
});
|
package/package.json
CHANGED
|
@@ -102,99 +102,52 @@ if [ "$TOTAL_TASKS" = "0" ]; then
|
|
|
102
102
|
fi
|
|
103
103
|
```
|
|
104
104
|
|
|
105
|
-
##
|
|
105
|
+
## Execution — per-task Nubosloop, slices serial
|
|
106
106
|
|
|
107
|
-
Every task runs through the Nubosloop ([ADR-0010](../docs/adr/0010-nubosloop.md), `lib/nubosloop.cjs`). The loop terminates only on (a) zero
|
|
107
|
+
Every task runs through the **Nubosloop** ([ADR-0010](../docs/adr/0010-nubosloop.md), `lib/nubosloop.cjs`) — pre-flight cache lookup → researcher-schwarm (on miss) → executor or build-fixer → mechanical checks + tool-use audit → critic-schwarm → route. The loop terminates only on (a) `loop-evaluate.next_action == "commit"` (zero blocking findings) followed by `commit-task` (atomic commit per ADR-0004), or (b) `loop.maxRounds` cap (default `3`) reached → `loop-run-round --phase stuck` writes the marker, dashboard surfaces it, orchestrator escalates via `askuser`. Single-pass `executor → commit-task` is forbidden — the loop is the only sanctioned path.
|
|
108
108
|
|
|
109
|
-
**
|
|
109
|
+
**Wave shape (slices serial, tasks parallel within a slice):**
|
|
110
110
|
|
|
111
|
-
**
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
3. **Executor (or Build-Fixer on Round ≥ 2)** — single `np-executor` spawn writes code in scope. Round 2+ uses `np-build-fixer` with the prior Critic findings + verify output appended to its prompt.
|
|
116
|
-
4. **Mechanical Checks** — the orchestrator (NOT the agent) runs the task's `verify` command, plus stack-specific linters (`phpstan`, `pint`, `tsc`, `eslint`), plus a tool-use audit confirming the agent invoked `search-knowledge` or `match-existing-learning` at least once. Red ⇒ findings route back to Step 3.
|
|
117
|
-
5. **Critic-Schwarm** — three Critic agents spawn in parallel (`agents/np-critic-style.md` haiku, `agents/np-critic-tests.md` sonnet, `agents/np-critic-acceptance.md` sonnet). Each emits structured findings JSON.
|
|
118
|
-
6. **Route + Loop or Commit** — the orchestrator merges + decides next-action via the agent-native CLI:
|
|
119
|
-
|
|
120
|
-
```bash
|
|
121
|
-
ROUND=$(node .nubos-pilot/bin/np-tools.cjs loop-state-read "$TASK_ID" | node -e 'process.stdin.on("data",d=>{const s=JSON.parse(d);console.log((s&&s.round)||1)})')
|
|
122
|
-
EVAL=$(node .nubos-pilot/bin/np-tools.cjs loop-evaluate \
|
|
123
|
-
--round "$ROUND" --max-rounds "$LOOP_MAX_ROUNDS" \
|
|
124
|
-
--json "$CRITIC_OUTPUTS_JSON")
|
|
125
|
-
NEXT=$(echo "$EVAL" | node -e 'process.stdin.on("data",d=>console.log(JSON.parse(d).next_action))')
|
|
126
|
-
```
|
|
127
|
-
|
|
128
|
-
`next_action` ∈ `{commit, executor, researcher, askuser, plan-checker, stuck}`. Routing rules:
|
|
129
|
-
- `executor` — Style / Bug / Test / Acceptance findings → spawn `np-build-fixer` on Round ≥ 2.
|
|
130
|
-
- `researcher` — `information-missing` findings → re-run Researcher-Schwarm with the gap as input.
|
|
131
|
-
- `askuser` — `question-to-user` findings → block on user reply.
|
|
132
|
-
- `plan-checker` — `locked-decision-violation` → orchestrator escalation.
|
|
133
|
-
- `commit` — zero findings → atomic commit per ADR-0004 + auto-`learning-log`.
|
|
134
|
-
- `stuck` — `loop.maxRounds` reached → `loop-stuck $TASK_ID --reason ... --findings ...`.
|
|
135
|
-
|
|
136
|
-
**End-to-end round (single CLI surface):**
|
|
137
|
-
|
|
138
|
-
```bash
|
|
139
|
-
# Step 1 — preflight cache lookup (advances round counter, stamps cache_hit)
|
|
140
|
-
node .nubos-pilot/bin/np-tools.cjs loop-run-round "$TASK_ID" --phase preflight \
|
|
141
|
-
--query "$TASK_QUERY"
|
|
142
|
-
|
|
143
|
-
# Step 2 — LLM spawns (researcher swarm if no cache hit, then executor) run extern.
|
|
144
|
-
|
|
145
|
-
# Step 3 — after executor commits draft, signal verify result
|
|
146
|
-
node .nubos-pilot/bin/np-tools.cjs loop-run-round "$TASK_ID" --phase post-executor \
|
|
147
|
-
--verify-exit-code "$VERIFY_EXIT" --verify-output-path "$VERIFY_LOG"
|
|
111
|
+
1. Dispatch **all tasks in the slice in parallel** — each task is one independent Nubosloop instance.
|
|
112
|
+
2. Wait until every task in the slice committed OR is `stuck` OR hit `plan-checker`.
|
|
113
|
+
3. If any task is `stuck` or hit `plan-checker` → stop the wave and exit non-zero. Previously committed tasks remain committed.
|
|
114
|
+
4. Move to the next slice.
|
|
148
115
|
|
|
149
|
-
|
|
116
|
+
**Per-task driver (single agent-native CLI surface):** `node .nubos-pilot/bin/np-tools.cjs loop-run-round <task-id> --phase <preflight|post-executor|post-critics|commit|stuck>`. Every non-LLM transition lives in this verb; LLM spawns (researcher, executor / build-fixer, critics) remain extern and feed their results back via `--query` / `--verify-exit-code` / `--critic-outputs`. A non-LLM runtime can drive the loop with five shell-outs per round.
|
|
150
117
|
|
|
151
|
-
|
|
152
|
-
node .nubos-pilot/bin/np-tools.cjs loop-run-round "$TASK_ID" --phase post-critics \
|
|
153
|
-
--critic-outputs "$CRITIC_JSON"
|
|
118
|
+
**Per-task, per-round protocol:**
|
|
154
119
|
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
120
|
+
1. **Pre-flight cache lookup** (Round 1 only) — `loop-run-round --phase preflight --query "$TASK_QUERY"`. A hit at similarity ≥ `swarm.research.threshold` and `occurrence ≥ swarm.research.minOccurrence` short-circuits the Researcher-Schwarm; the cached pattern enters the Executor prompt with provenance `[CACHED]`. Soft cache failures (mcp-not-implemented, adapter-unknown) downgrade to a miss with `cache_miss_reason` populated; hard failures (corrupt store, version mismatch) propagate.
|
|
121
|
+
2. **Researcher-Schwarm (on cache miss, or on `next_action=researcher` re-route)** — orchestrator spawns `swarm.research.k=3` independent `np-researcher` agents IN PARALLEL (single message, three Agent blocks) and merges their outputs through `lib/researcher-swarm.cjs::mergeConsensus` (Mehrheit / Union / Schnittmenge). The merged consensus enters the Executor prompt with provenance.
|
|
122
|
+
3. **Executor (R1) or Build-Fixer (R≥2)** — single LLM spawn. Round 1 spawns `agents/np-executor.md`. Round ≥ 2 spawns `agents/np-build-fixer.md` with prior critic findings + verify output appended. Edits ONLY paths in `files_modified` (D-04 — no scope expansion). Does NOT call `commit-task`.
|
|
123
|
+
4. **Mechanical Checks (orchestrator, NOT the agent)** — run task's `<verify>` command + stack linters (`phpstan`, `pint`, `tsc`, `eslint`); capture exit code + output to `$VERIFY_LOG`. Then `loop-audit-tool-use --task-id ... --round ...` confirms the spawn invoked `search-knowledge` or `match-existing-learning` ≥ 1× (Rule 9). Audit findings get round-stamped and feed `loop-evaluate` alongside critic findings. Then call `loop-run-round --phase post-executor --verify-exit-code "$VERIFY_EXIT" --verify-output-path "$VERIFY_LOG"`. On verify-red the verb returns `next_action: spawn-build-fixer` — skip critics, advance to next round directly.
|
|
124
|
+
5. **Critic-Schwarm (verify-green only)** — three Critic agents spawn IN PARALLEL (single message, three Agent blocks): `agents/np-critic-style.md` (haiku), `agents/np-critic-tests.md` (sonnet), `agents/np-critic-acceptance.md` (sonnet). Each emits structured findings JSON.
|
|
125
|
+
6. **Route** — `loop-run-round --phase post-critics --critic-outputs "$CRITIC_JSON"` returns `next_action ∈ {commit, executor, researcher, askuser, plan-checker, stuck}`:
|
|
159
126
|
|
|
160
|
-
|
|
127
|
+
| `next_action` | Trigger | Action |
|
|
128
|
+
|------------------|------------------------------------|-----------------------------------------------------------------|
|
|
129
|
+
| `commit` | Zero blocking findings | `loop-run-round --phase commit` + `commit-task` (atomic) |
|
|
130
|
+
| `executor` | Style/Bug/Test/Acceptance findings | R≥2: spawn `np-build-fixer` with prior findings (next round) |
|
|
131
|
+
| `researcher` | `information-missing` finding | Re-run Researcher-Schwarm with the gap as input (next round) |
|
|
132
|
+
| `askuser` | `question-to-user` finding | Block on user reply via `askuser`; resume same round |
|
|
133
|
+
| `plan-checker` | `locked-decision-violation` | Abort wave; orchestrator escalates |
|
|
134
|
+
| `stuck` | `loop.maxRounds` reached | `loop-run-round --phase stuck` + dashboard + askuser escalation |
|
|
161
135
|
|
|
162
|
-
|
|
163
|
-
node .nubos-pilot/bin/np-tools.cjs loop-state-record "$TASK_ID" \
|
|
164
|
-
--json "$(printf '{"round":%s,"last_action":"%s","findings":%s}' "$ROUND" "$NEXT" "$FINDINGS_JSON")"
|
|
165
|
-
```
|
|
136
|
+
7. **Commit** — `loop-run-round --phase commit --learning-pattern "$CONSENSUS_PATTERN" --learning-outcome verified` stamps the checkpoint to `pre-commit` and auto-logs the learning (when `auto_log_learning=true`, default — feeds future Round-1 cache hits). Then `node .nubos-pilot/bin/np-tools.cjs commit-task "$TASK_ID"` performs the atomic commit per ADR-0004.
|
|
166
137
|
|
|
167
|
-
**
|
|
138
|
+
**Per-task loop control values (read once at wave start):**
|
|
168
139
|
|
|
169
140
|
```bash
|
|
170
|
-
node .nubos-pilot/bin/np-tools.cjs learning-log \
|
|
171
|
-
--pattern "$CONSENSUS_PATTERN" --outcome verified \
|
|
172
|
-
--task-id "$TASK_ID" --milestone-id "$MILESTONE_ID"
|
|
173
|
-
```
|
|
174
|
-
|
|
175
|
-
Future similar tasks hit the cache and bypass the Researcher-Schwarm at Step 1.
|
|
176
|
-
|
|
177
|
-
```bash
|
|
178
|
-
# Per-task loop control values (read once from config)
|
|
179
141
|
LOOP_MAX_ROUNDS=$(node .nubos-pilot/bin/np-tools.cjs config-get loop.maxRounds 2>/dev/null || echo 3)
|
|
180
142
|
SWARM_K=$(node .nubos-pilot/bin/np-tools.cjs config-get swarm.research.k 2>/dev/null || echo 3)
|
|
143
|
+
SWARM_THRESHOLD=$(node .nubos-pilot/bin/np-tools.cjs config-get swarm.research.threshold 2>/dev/null || echo 0.9)
|
|
144
|
+
SWARM_MIN_OCC=$(node .nubos-pilot/bin/np-tools.cjs config-get swarm.research.minOccurrence 2>/dev/null || echo 3)
|
|
181
145
|
AUTO_LOG_LEARNING=$(node .nubos-pilot/bin/np-tools.cjs config-get auto_log_learning 2>/dev/null || echo true)
|
|
182
146
|
```
|
|
183
147
|
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
## Execution — slices serial, tasks parallel within a slice
|
|
187
|
-
|
|
188
|
-
For each wave (slice) in `waves[]`, in order:
|
|
189
|
-
|
|
190
|
-
1. Dispatch **all tasks in the slice in parallel** (one Nubosloop per task — see above).
|
|
191
|
-
2. Wait until every task in the slice is committed OR one failed OR `stuck`.
|
|
192
|
-
3. If any task failed or is `stuck` → stop the wave and exit non-zero. Previous committed tasks remain committed.
|
|
193
|
-
4. Move to the next slice.
|
|
148
|
+
**Wave + per-task pseudocode (this is the executable shape — the orchestrator drives this verbatim, not just „shape but not concrete syntax"):**
|
|
194
149
|
|
|
195
150
|
```bash
|
|
196
|
-
# Pseudocode for the per-wave loop. The orchestrator uses its parallel-spawn
|
|
197
|
-
# primitive; this pseudocode shows the shape but not the concrete agent syntax.
|
|
198
151
|
for WAVE_INDEX in 0 1 2 ...; do
|
|
199
152
|
WAVE=$(echo "$INIT" | node -e "process.stdin.on('data', d => console.log(JSON.stringify(JSON.parse(d).waves[$WAVE_INDEX])))")
|
|
200
153
|
[ -z "$WAVE" ] || [ "$WAVE" = "undefined" ] && break
|
|
@@ -205,10 +158,9 @@ for WAVE_INDEX in 0 1 2 ...; do
|
|
|
205
158
|
echo "=== Wave $((WAVE_INDEX+1)): $SLICE_FULL_ID — tasks: $TASK_IDS ===" >&2
|
|
206
159
|
|
|
207
160
|
# Worktree-Isolation (ADR-0008): when workflow.worktree_isolation=true,
|
|
208
|
-
# create an isolated git worktree for this slice
|
|
209
|
-
#
|
|
210
|
-
#
|
|
211
|
-
# back on success. On failure: worktree stays in place for inspection.
|
|
161
|
+
# create an isolated git worktree for this slice. Nubosloop instances
|
|
162
|
+
# run inside the worktree (cwd = worktree path); commits land on the
|
|
163
|
+
# slice branch np/<slice-full-id>; FF-merged back on success.
|
|
212
164
|
SLICE_CWD="$PWD"
|
|
213
165
|
if [ "$WORKTREE_ISOLATION" = "true" ]; then
|
|
214
166
|
WT_CREATE=$(node .nubos-pilot/bin/np-tools.cjs worktree-create "$SLICE_FULL_ID")
|
|
@@ -216,27 +168,120 @@ for WAVE_INDEX in 0 1 2 ...; do
|
|
|
216
168
|
echo "[np:execute-phase] worktree created at $SLICE_CWD (branch np/$SLICE_FULL_ID)" >&2
|
|
217
169
|
fi
|
|
218
170
|
|
|
219
|
-
#
|
|
220
|
-
# The orchestrator's parallel primitive dispatches
|
|
221
|
-
# message (
|
|
171
|
+
# PARALLEL DISPATCH per task — one Nubosloop instance per task.
|
|
172
|
+
# The orchestrator's parallel primitive dispatches each task's loop
|
|
173
|
+
# body in a single message (one Agent block per task per LLM step).
|
|
222
174
|
for TASK_ID in $TASK_IDS; do
|
|
223
|
-
# IN PARALLEL:
|
|
224
|
-
|
|
175
|
+
# IN PARALLEL across tasks in the slice:
|
|
176
|
+
|
|
177
|
+
node .nubos-pilot/bin/np-tools.cjs checkpoint start "$TASK_ID" \
|
|
178
|
+
--phase "$PHASE" --plan "$SLICE_FULL_ID" --wave "$((WAVE_INDEX+1))"
|
|
225
179
|
|
|
226
180
|
TASK_JSON=$(node .nubos-pilot/bin/np-tools.cjs init execute-milestone execute-task "$PHASE" "$TASK_ID")
|
|
227
181
|
if [[ "$TASK_JSON" == @file:* ]]; then TASK_JSON=$(cat "${TASK_JSON#@file:}"); fi
|
|
182
|
+
TASK_QUERY=$(echo "$TASK_JSON" | node -e "process.stdin.on('data', d => { const j=JSON.parse(d); console.log(j.query || j.name || ''); })")
|
|
228
183
|
|
|
229
184
|
EXECUTOR_START=$(node .nubos-pilot/bin/np-tools.cjs metrics start-timestamp)
|
|
230
|
-
|
|
185
|
+
CONSENSUS_PATTERN=""
|
|
186
|
+
NEXT_ACTION=""
|
|
187
|
+
CACHE_HIT="false"
|
|
188
|
+
ROUND=1
|
|
189
|
+
|
|
190
|
+
while [ "$ROUND" -le "$LOOP_MAX_ROUNDS" ]; do
|
|
191
|
+
|
|
192
|
+
# === Step 1: pre-flight cache lookup (Round 1 only) ===
|
|
193
|
+
if [ "$ROUND" -eq 1 ]; then
|
|
194
|
+
PREFLIGHT=$(node .nubos-pilot/bin/np-tools.cjs loop-run-round "$TASK_ID" \
|
|
195
|
+
--phase preflight --query "$TASK_QUERY")
|
|
196
|
+
CACHE_HIT=$(echo "$PREFLIGHT" | node -e 'process.stdin.on("data",d=>console.log(JSON.parse(d).hit||false))')
|
|
197
|
+
fi
|
|
231
198
|
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
199
|
+
# === Step 2: Researcher-Schwarm (cache miss on R1, or re-route on R≥2) ===
|
|
200
|
+
# PARALLEL spawn of $SWARM_K agents/np-researcher.md (single message,
|
|
201
|
+
# $SWARM_K Agent blocks). Merge via lib/researcher-swarm.cjs::mergeConsensus.
|
|
202
|
+
# Result is injected into the next executor prompt as $CONSENSUS_PATTERN
|
|
203
|
+
# with provenance ([VERIFIED] on majority + spawn-citation, else [PROVISIONAL]).
|
|
204
|
+
if { [ "$ROUND" -eq 1 ] && [ "$CACHE_HIT" != "true" ]; } || [ "$NEXT_ACTION" = "researcher" ]; then
|
|
205
|
+
CONSENSUS_PATTERN="<merged consensus from $SWARM_K researchers>"
|
|
206
|
+
elif [ "$CACHE_HIT" = "true" ] && [ -z "$CONSENSUS_PATTERN" ]; then
|
|
207
|
+
CONSENSUS_PATTERN="<cached pattern from preflight ([CACHED] provenance)>"
|
|
208
|
+
fi
|
|
209
|
+
|
|
210
|
+
# === Step 3: Executor (R1) or Build-Fixer (R≥2) — LLM spawn extern ===
|
|
211
|
+
if [ "$ROUND" -eq 1 ]; then
|
|
212
|
+
EXECUTOR_AGENT="np-executor"
|
|
213
|
+
else
|
|
214
|
+
EXECUTOR_AGENT="np-build-fixer"
|
|
215
|
+
fi
|
|
216
|
+
EXECUTOR_MODEL=$(node .nubos-pilot/bin/np-tools.cjs resolve-model "$EXECUTOR_AGENT" --profile frontier)
|
|
217
|
+
# Spawn agents/${EXECUTOR_AGENT}.md (model resolved as $EXECUTOR_MODEL) with:
|
|
218
|
+
# - <files_to_read>: task plan, slice plan, prior slice SUMMARYs, CONTEXT.md
|
|
219
|
+
# - $CONSENSUS_PATTERN with provenance
|
|
220
|
+
# - On Round ≥ 2: prior critic findings + verify output excerpt
|
|
221
|
+
# - $LANG_DIRECTIVE + $AGENT_SKILLS_EXECUTOR (skill triggers from §Skills mapping)
|
|
222
|
+
# Agent edits ONLY paths in files_modified (D-04). Does NOT call commit-task.
|
|
223
|
+
|
|
224
|
+
node .nubos-pilot/bin/np-tools.cjs checkpoint transition "$TASK_ID" verifying
|
|
225
|
+
|
|
226
|
+
# === Step 4: Mechanical Checks + tool-use audit (orchestrator-side) ===
|
|
227
|
+
VERIFY_LOG="${TMPDIR:-/tmp}/np-verify-${TASK_ID}-r${ROUND}.log"
|
|
228
|
+
# Orchestrator (NOT the agent) runs the task's <verify> command + stack
|
|
229
|
+
# linters; redirect stdout+stderr to $VERIFY_LOG.
|
|
230
|
+
VERIFY_EXIT=$?
|
|
231
|
+
node .nubos-pilot/bin/np-tools.cjs loop-audit-tool-use "$TASK_ID" \
|
|
232
|
+
--round "$ROUND" --agent "$EXECUTOR_AGENT"
|
|
233
|
+
|
|
234
|
+
POST_EXEC=$(node .nubos-pilot/bin/np-tools.cjs loop-run-round "$TASK_ID" \
|
|
235
|
+
--phase post-executor \
|
|
236
|
+
--verify-exit-code "$VERIFY_EXIT" --verify-output-path "$VERIFY_LOG")
|
|
237
|
+
POST_EXEC_NEXT=$(echo "$POST_EXEC" | node -e 'process.stdin.on("data",d=>console.log(JSON.parse(d).next_action))')
|
|
238
|
+
|
|
239
|
+
# Verify-red short-circuits to build-fixer next round (skip critics).
|
|
240
|
+
if [ "$POST_EXEC_NEXT" = "spawn-build-fixer" ]; then
|
|
241
|
+
ROUND=$((ROUND+1))
|
|
242
|
+
continue
|
|
243
|
+
fi
|
|
237
244
|
|
|
238
|
-
|
|
239
|
-
|
|
245
|
+
# === Step 5: Critic-Schwarm — three agents in PARALLEL ===
|
|
246
|
+
# Spawn IN PARALLEL (single message, three Agent blocks):
|
|
247
|
+
# - agents/np-critic-style.md (haiku) → CRITIC_STYLE_JSON
|
|
248
|
+
# - agents/np-critic-tests.md (sonnet) → CRITIC_TESTS_JSON
|
|
249
|
+
# - agents/np-critic-acceptance.md (sonnet) → CRITIC_ACCEPTANCE_JSON
|
|
250
|
+
CRITIC_OUTPUTS_JSON=$(printf '[%s,%s,%s]' "$CRITIC_STYLE_JSON" "$CRITIC_TESTS_JSON" "$CRITIC_ACCEPTANCE_JSON")
|
|
251
|
+
|
|
252
|
+
# === Step 6: Route via loop-evaluate (post-critics) ===
|
|
253
|
+
POST_CRIT=$(node .nubos-pilot/bin/np-tools.cjs loop-run-round "$TASK_ID" \
|
|
254
|
+
--phase post-critics --critic-outputs "$CRITIC_OUTPUTS_JSON")
|
|
255
|
+
NEXT_ACTION=$(echo "$POST_CRIT" | node -e 'process.stdin.on("data",d=>console.log(JSON.parse(d).next_action))')
|
|
256
|
+
|
|
257
|
+
case "$NEXT_ACTION" in
|
|
258
|
+
commit) break ;;
|
|
259
|
+
executor) ROUND=$((ROUND+1)); continue ;;
|
|
260
|
+
researcher) ROUND=$((ROUND+1)); continue ;;
|
|
261
|
+
askuser) # spec from POST_CRIT.routing — block on user reply,
|
|
262
|
+
# then resume the same round (no ROUND increment).
|
|
263
|
+
node .nubos-pilot/bin/np-tools.cjs askuser --json "$ASKUSER_SPEC"
|
|
264
|
+
continue ;;
|
|
265
|
+
plan-checker) echo "[np:execute-phase] $TASK_ID hit locked-decision-violation — see loop-state for details." >&2
|
|
266
|
+
exit 2 ;;
|
|
267
|
+
stuck) node .nubos-pilot/bin/np-tools.cjs loop-run-round "$TASK_ID" \
|
|
268
|
+
--phase stuck --reason "max-rounds" --findings "$CRITIC_OUTPUTS_JSON"
|
|
269
|
+
echo "[np:execute-phase] $TASK_ID stuck after $LOOP_MAX_ROUNDS rounds." >&2
|
|
270
|
+
exit 3 ;;
|
|
271
|
+
esac
|
|
272
|
+
done
|
|
273
|
+
|
|
274
|
+
# Defensive: if the while loop exited without NEXT_ACTION=commit (shouldn't
|
|
275
|
+
# happen — loop-evaluate emits stuck at maxRounds), stamp stuck and bail.
|
|
276
|
+
if [ "$NEXT_ACTION" != "commit" ]; then
|
|
277
|
+
node .nubos-pilot/bin/np-tools.cjs loop-run-round "$TASK_ID" \
|
|
278
|
+
--phase stuck --reason "loop-exited-without-commit"
|
|
279
|
+
exit 3
|
|
280
|
+
fi
|
|
281
|
+
|
|
282
|
+
# === Step 7: atomic commit ===
|
|
283
|
+
node .nubos-pilot/bin/np-tools.cjs loop-run-round "$TASK_ID" --phase commit \
|
|
284
|
+
--learning-pattern "$CONSENSUS_PATTERN" --learning-outcome verified
|
|
240
285
|
node .nubos-pilot/bin/np-tools.cjs commit-task "$TASK_ID"
|
|
241
286
|
COMMIT_STATUS=$?
|
|
242
287
|
|
|
@@ -244,11 +289,11 @@ for WAVE_INDEX in 0 1 2 ...; do
|
|
|
244
289
|
EXECUTOR_STATUS=ok
|
|
245
290
|
[ "$COMMIT_STATUS" -ne 0 ] && EXECUTOR_STATUS=error
|
|
246
291
|
node .nubos-pilot/bin/np-tools.cjs metrics record \
|
|
247
|
-
--agent
|
|
292
|
+
--agent "$EXECUTOR_AGENT" --tier sonnet --resolved-model "$EXECUTOR_MODEL" \
|
|
248
293
|
--phase "$PHASE" --plan "$SLICE_FULL_ID" --task "$TASK_ID" \
|
|
249
294
|
--started "$EXECUTOR_START" --ended "$EXECUTOR_END" \
|
|
250
295
|
--tokens-in "${TOKENS_IN:-0}" --tokens-out "${TOKENS_OUT:-0}" \
|
|
251
|
-
--retry-count "$
|
|
296
|
+
--retry-count "$((ROUND-1))" --status "$EXECUTOR_STATUS" --runtime "$RUNTIME"
|
|
252
297
|
|
|
253
298
|
if [ "$COMMIT_STATUS" -ne 0 ]; then
|
|
254
299
|
echo "[np:execute-phase] commit-task failed for $TASK_ID — aborting wave $SLICE_FULL_ID." >&2
|
|
@@ -258,7 +303,7 @@ for WAVE_INDEX in 0 1 2 ...; do
|
|
|
258
303
|
exit "$COMMIT_STATUS"
|
|
259
304
|
fi
|
|
260
305
|
done
|
|
261
|
-
#
|
|
306
|
+
# Wait for all parallel Nubosloop instances in this wave to finish before next wave.
|
|
262
307
|
|
|
263
308
|
# After every task in the slice committed: aggregate per-task summaries into
|
|
264
309
|
# the slice-level S<NNN>-SUMMARY.md so /np:validate-phase can audit it.
|
|
@@ -294,16 +339,26 @@ After every slice completes, point the operator at `/np:validate-phase $PHASE` t
|
|
|
294
339
|
|
|
295
340
|
<!-- scope_guardrail -->
|
|
296
341
|
**Do:**
|
|
297
|
-
- Dispatch all tasks in a slice **in parallel**
|
|
298
|
-
- Move to next slice **only after** every task in the current slice
|
|
299
|
-
- Start one checkpoint per task before
|
|
300
|
-
-
|
|
342
|
+
- Dispatch all tasks in a slice **in parallel** — one Nubosloop instance per task.
|
|
343
|
+
- Move to next slice **only after** every task in the current slice committed (or `stuck`/`plan-checker` aborted the wave).
|
|
344
|
+
- Start one checkpoint per task before kicking off the loop.
|
|
345
|
+
- Run `loop-run-round --phase preflight` BEFORE every Round-1 executor spawn — never skip the cache lookup.
|
|
346
|
+
- Spawn `agents/np-executor.md` on Round 1, `agents/np-build-fixer.md` on Round ≥ 2 — once per round, with only that task's `files_modified` in scope (D-04, no scope expansion).
|
|
347
|
+
- Spawn the three Critic agents (`np-critic-style`, `np-critic-tests`, `np-critic-acceptance`) IN PARALLEL — single message, three Agent blocks per task per round.
|
|
348
|
+
- Run `loop-run-round --phase post-executor` AFTER mechanical checks; honor `next_action: spawn-build-fixer` (verify-red short-circuit, skip critics this round).
|
|
349
|
+
- Run `loop-run-round --phase post-critics` AFTER critics return, to obtain the routing `next_action`.
|
|
350
|
+
- Run `loop-audit-tool-use` per round per spawn — Rule 9 (search-knowledge / match-existing-learning) is mechanically enforced.
|
|
301
351
|
- Route every commit through `node .nubos-pilot/bin/np-tools.cjs commit-task` so `assertCommittablePaths` (D-25) runs.
|
|
302
|
-
- Hard-stop the wave when `commit-task` returns
|
|
352
|
+
- Hard-stop the wave when `commit-task` returns non-zero, OR a task hits `stuck`/`plan-checker`.
|
|
303
353
|
|
|
304
354
|
**Don't:**
|
|
305
355
|
- Run tasks across slices in parallel — slices are serial.
|
|
306
356
|
- Run intra-slice tasks serially — they're parallel by planner contract.
|
|
357
|
+
- Skip the Nubosloop and call `commit-task` directly after the executor (single-pass executor → commit is forbidden — ADR-0010).
|
|
358
|
+
- Spawn the Critic agents serially — they MUST run in parallel (single message, three Agent blocks).
|
|
359
|
+
- Use `np-executor` on Round ≥ 2 — use `np-build-fixer` (it gets prior critic findings + verify output excerpt).
|
|
360
|
+
- Skip `loop-audit-tool-use` — Rule 9 violations must surface as `rule-9-violation` findings, not be silenced.
|
|
361
|
+
- Extend a task's scope beyond `files_modified` — D-04 violations route to `plan-checker`, not post-hoc PLAN.md mutations.
|
|
307
362
|
- Invoke `git commit`, `git add`, or any bare git command from this workflow or the spawned agent (CLAUDE.md §Git operations).
|
|
308
363
|
- Bundle two tasks into one commit (ADR-0004 atomicity).
|
|
309
364
|
- Skip the checkpoint start step — it's the crash-safety primitive `resume-work` depends on.
|
|
@@ -313,18 +368,22 @@ After every slice completes, point the operator at `/np:validate-phase $PHASE` t
|
|
|
313
368
|
## Output
|
|
314
369
|
|
|
315
370
|
- One git commit per completed task (`task(<milestone-id>-<slice-id>-T<NNNN>): <name>`).
|
|
316
|
-
- Per-task checkpoint lifetime: `start` → (`transition verifying
|
|
371
|
+
- Per-task checkpoint lifetime: `start` → (`transition verifying`)+ → `pre-commit` (set by `loop-run-round --phase commit`) → `deleteCheckpoint` (inside commit-task on success).
|
|
372
|
+
- Per-task `nubosloop` state block on the checkpoint envelope: `last_phase`, `last_action`, `round`, `findings`, `committed_at` / `stuck_at` — surfaced on `np:dashboard`.
|
|
373
|
+
- Auto-`learning-log` entry per committed task (when `auto_log_learning=true`, default) — feeds future Round-1 cache hits.
|
|
317
374
|
- STATE.md updated via `startTask`'s coordinated lock-cycle (D-08).
|
|
318
|
-
- Per slice: updated `S<NNN>-SUMMARY.md` aggregated from task summaries (triggered
|
|
375
|
+
- Per slice: updated `S<NNN>-SUMMARY.md` aggregated from task summaries (triggered after the last task in the wave).
|
|
319
376
|
- Verified work surface for `/np:validate-phase $PHASE`.
|
|
377
|
+
|
|
320
378
|
## Definition of Done
|
|
321
379
|
|
|
322
380
|
This workflow exits successfully only when, per [`templates/COMPLETENESS.md`](../templates/COMPLETENESS.md):
|
|
323
381
|
|
|
324
|
-
- Rule 1 (Do the whole thing) — every task in every slice committed; no partial slices left.
|
|
325
|
-
- Rule 3 (Do it with tests) — every commit ships verify-green;
|
|
326
|
-
- Rule 4 (Do it with documentation) — `update-docs` ran for every committed task; stale module docs
|
|
327
|
-
- Rule
|
|
328
|
-
- Rule
|
|
382
|
+
- Rule 1 (Do the whole thing) — every task in every slice ran its Nubosloop to `next_action=commit` and committed; no partial slices, no `stuck` left silent.
|
|
383
|
+
- Rule 3 (Do it with tests) — every commit ships verify-green; mechanical checks per round are a hard gate; `commit-task` refuses commits without a `verifying` → `pre-commit` transition.
|
|
384
|
+
- Rule 4 (Do it with documentation) — `update-docs` ran for every committed task; stale module docs surface as a `np-critic-acceptance` finding and route the loop back, not forward.
|
|
385
|
+
- Rule 9 (Tool-use audit) — `loop-audit-tool-use` confirms every spawn invoked `search-knowledge` or `match-existing-learning` ≥ 1×; violations route as `rule-9-violation` findings into `loop-evaluate`.
|
|
386
|
+
- Rule 10 (Test before shipping) — verify-green is a hard gate per round, not advice.
|
|
387
|
+
- Rule 12 (Boil the ocean) — no task left in `stuck` state; the orchestrator escalates via askuser rather than silently downgrading or retrying past `loop.maxRounds`.
|
|
329
388
|
|
|
330
389
|
Any violation = workflow exits non-zero. The orchestrator does not relax these.
|