brainclaw 1.5.4 → 1.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +52 -28
- package/dist/brainclaw-vscode.vsix +0 -0
- package/dist/cli.js +159 -12
- package/dist/commands/assignment-resource.js +182 -0
- package/dist/commands/bootstrap-loop.js +206 -0
- package/dist/commands/init.js +158 -22
- package/dist/commands/loop.js +156 -0
- package/dist/commands/loops-handlers.js +110 -55
- package/dist/commands/mcp-read-handlers.js +45 -4
- package/dist/commands/mcp.js +628 -205
- package/dist/commands/questions.js +180 -0
- package/dist/commands/reply.js +190 -0
- package/dist/commands/session-end.js +105 -3
- package/dist/commands/session-start.js +32 -53
- package/dist/commands/setup.js +87 -48
- package/dist/commands/switch.js +21 -1
- package/dist/core/agentrun-reconciler.js +65 -0
- package/dist/core/agentruns.js +10 -0
- package/dist/core/assignments.js +29 -10
- package/dist/core/claims.js +29 -0
- package/dist/core/context.js +1 -1
- package/dist/core/coordination.js +1 -1
- package/dist/core/dispatch-status.js +219 -0
- package/dist/core/entity-operations.js +166 -10
- package/dist/core/entity-registry.js +11 -10
- package/dist/core/execution-adapters.js +38 -2
- package/dist/core/facade-schema.js +55 -0
- package/dist/core/federation-cloud.js +27 -12
- package/dist/core/federation-materialize.js +57 -0
- package/dist/core/instruction-templates.js +2 -0
- package/dist/core/loops/bootstrap-acquire.js +195 -0
- package/dist/core/loops/facade-schema.js +68 -1
- package/dist/core/loops/hooks/bootstrap-write.js +144 -0
- package/dist/core/loops/hooks/notify-operator.js +148 -0
- package/dist/core/loops/hooks/survey-source-reader.js +256 -0
- package/dist/core/loops/index.js +8 -2
- package/dist/core/loops/next-expected.js +63 -0
- package/dist/core/loops/presets/bootstrap.js +75 -0
- package/dist/core/loops/presets/index.js +16 -0
- package/dist/core/loops/store.js +224 -4
- package/dist/core/loops/types.js +346 -1
- package/dist/core/loops/verbs.js +739 -6
- package/dist/core/schema.js +31 -2
- package/dist/core/state.js +62 -0
- package/dist/core/store-resolution.js +26 -16
- package/dist/facts.js +7 -5
- package/dist/facts.json +6 -4
- package/docs/cli.md +115 -30
- package/docs/concepts/dispatch-lifecycle.md +228 -0
- package/docs/concepts/loop-engine.md +55 -0
- package/docs/concepts/multi-agent-workflows.md +167 -166
- package/docs/concepts/troubleshooting.md +10 -2
- package/docs/integrations/agents.md +14 -14
- package/docs/integrations/codex.md +15 -12
- package/docs/integrations/mcp.md +10 -4
- package/docs/integrations/overview.md +11 -0
- package/docs/playbooks/productivity/index.md +3 -3
- package/docs/quickstart-existing-project.md +48 -28
- package/docs/quickstart.md +42 -28
- package/package.json +1 -1
|
@@ -0,0 +1,256 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
const DEFAULT_MAX_BYTES = 50 * 1024;
|
|
4
|
+
const MAX_INDIVIDUAL_FILE_BYTES = 1024 * 1024;
|
|
5
|
+
export function readSurveySources(cwd, opts) {
|
|
6
|
+
const cap = opts?.maxBytes ?? DEFAULT_MAX_BYTES;
|
|
7
|
+
const log = [];
|
|
8
|
+
const result = {
|
|
9
|
+
excerpts: [],
|
|
10
|
+
total_byte_count: 0,
|
|
11
|
+
cap_exceeded: false,
|
|
12
|
+
cap_bytes: cap,
|
|
13
|
+
reasoning_log: log,
|
|
14
|
+
};
|
|
15
|
+
let rootEntries;
|
|
16
|
+
try {
|
|
17
|
+
rootEntries = fs.readdirSync(cwd, { withFileTypes: true });
|
|
18
|
+
}
|
|
19
|
+
catch (err) {
|
|
20
|
+
log.push(`could not read project root: ${err.message}`);
|
|
21
|
+
return result;
|
|
22
|
+
}
|
|
23
|
+
const readmeFiles = [];
|
|
24
|
+
const licenseFiles = [];
|
|
25
|
+
for (const entry of rootEntries) {
|
|
26
|
+
if (!entry.isFile())
|
|
27
|
+
continue;
|
|
28
|
+
const upper = entry.name.toUpperCase();
|
|
29
|
+
if (/^README.*\.MD$/.test(upper)) {
|
|
30
|
+
readmeFiles.push(entry.name);
|
|
31
|
+
}
|
|
32
|
+
else if (/^LICENSE(\.MD|\.TXT)?$/.test(upper)) {
|
|
33
|
+
licenseFiles.push(entry.name);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
readmeFiles.sort();
|
|
37
|
+
licenseFiles.sort();
|
|
38
|
+
const candidates = [...readmeFiles, ...licenseFiles];
|
|
39
|
+
const entryPoint = detectEntryPoint(cwd, rootEntries, log);
|
|
40
|
+
if (entryPoint) {
|
|
41
|
+
if (!candidates.includes(entryPoint))
|
|
42
|
+
candidates.push(entryPoint);
|
|
43
|
+
}
|
|
44
|
+
else {
|
|
45
|
+
log.push('no manifest-referenced entry point found');
|
|
46
|
+
}
|
|
47
|
+
for (const relPath of candidates) {
|
|
48
|
+
const absPath = path.join(cwd, relPath);
|
|
49
|
+
let stat;
|
|
50
|
+
try {
|
|
51
|
+
stat = fs.statSync(absPath);
|
|
52
|
+
}
|
|
53
|
+
catch (err) {
|
|
54
|
+
log.push(`skipped ${relPath}: ${err.message}`);
|
|
55
|
+
continue;
|
|
56
|
+
}
|
|
57
|
+
if (!stat.isFile()) {
|
|
58
|
+
log.push(`skipped ${relPath}: not a regular file`);
|
|
59
|
+
continue;
|
|
60
|
+
}
|
|
61
|
+
if (stat.size > MAX_INDIVIDUAL_FILE_BYTES) {
|
|
62
|
+
log.push(`skipped ${relPath}: size ${stat.size} bytes exceeds 1MB individual safety cap`);
|
|
63
|
+
result.cap_exceeded = true;
|
|
64
|
+
continue;
|
|
65
|
+
}
|
|
66
|
+
let content;
|
|
67
|
+
try {
|
|
68
|
+
content = fs.readFileSync(absPath, 'utf8');
|
|
69
|
+
}
|
|
70
|
+
catch (err) {
|
|
71
|
+
log.push(`skipped ${relPath}: UTF-8 read failed (${err.message})`);
|
|
72
|
+
continue;
|
|
73
|
+
}
|
|
74
|
+
const remaining = cap - result.total_byte_count;
|
|
75
|
+
if (remaining <= 0) {
|
|
76
|
+
log.push(`skipped ${relPath}: cap of ${cap} bytes already reached`);
|
|
77
|
+
result.cap_exceeded = true;
|
|
78
|
+
break;
|
|
79
|
+
}
|
|
80
|
+
const normalized = relPath.replace(/\\/g, '/');
|
|
81
|
+
const fileBytes = Buffer.byteLength(content, 'utf8');
|
|
82
|
+
if (fileBytes <= remaining) {
|
|
83
|
+
result.excerpts.push({
|
|
84
|
+
file: normalized,
|
|
85
|
+
byte_count: fileBytes,
|
|
86
|
+
body_truncated: false,
|
|
87
|
+
body: content,
|
|
88
|
+
});
|
|
89
|
+
result.total_byte_count += fileBytes;
|
|
90
|
+
log.push(`included ${normalized} (${fileBytes} bytes)`);
|
|
91
|
+
}
|
|
92
|
+
else {
|
|
93
|
+
const truncated = truncateToBytes(content, remaining);
|
|
94
|
+
const truncatedBytes = Buffer.byteLength(truncated, 'utf8');
|
|
95
|
+
result.excerpts.push({
|
|
96
|
+
file: normalized,
|
|
97
|
+
byte_count: truncatedBytes,
|
|
98
|
+
body_truncated: true,
|
|
99
|
+
body: truncated,
|
|
100
|
+
});
|
|
101
|
+
result.total_byte_count += truncatedBytes;
|
|
102
|
+
result.cap_exceeded = true;
|
|
103
|
+
log.push(`truncated ${normalized} from ${fileBytes} to ${truncatedBytes} bytes to fit cap of ${cap}`);
|
|
104
|
+
break;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
return result;
|
|
108
|
+
}
|
|
109
|
+
function truncateToBytes(s, maxBytes) {
|
|
110
|
+
const buf = Buffer.from(s, 'utf8');
|
|
111
|
+
if (buf.length <= maxBytes)
|
|
112
|
+
return s;
|
|
113
|
+
let end = maxBytes;
|
|
114
|
+
while (end > 0 && (buf[end] & 0xc0) === 0x80)
|
|
115
|
+
end--;
|
|
116
|
+
return buf.subarray(0, end).toString('utf8');
|
|
117
|
+
}
|
|
118
|
+
function detectEntryPoint(cwd, rootEntries, log) {
|
|
119
|
+
const fileNames = new Set();
|
|
120
|
+
for (const entry of rootEntries) {
|
|
121
|
+
if (entry.isFile())
|
|
122
|
+
fileNames.add(entry.name);
|
|
123
|
+
}
|
|
124
|
+
for (const name of fileNames) {
|
|
125
|
+
if (name.endsWith('.spec')) {
|
|
126
|
+
const found = parsePyinstallerSpec(path.join(cwd, name), log);
|
|
127
|
+
if (found)
|
|
128
|
+
return found;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
if (fileNames.has('package.json')) {
|
|
132
|
+
const found = parsePackageJson(path.join(cwd, 'package.json'), log);
|
|
133
|
+
if (found)
|
|
134
|
+
return found;
|
|
135
|
+
}
|
|
136
|
+
if (fileNames.has('pyproject.toml')) {
|
|
137
|
+
const found = parsePyproject(path.join(cwd, 'pyproject.toml'), log);
|
|
138
|
+
if (found)
|
|
139
|
+
return found;
|
|
140
|
+
}
|
|
141
|
+
if (fileNames.has('Cargo.toml')) {
|
|
142
|
+
const found = parseCargoToml(path.join(cwd, 'Cargo.toml'), cwd, log);
|
|
143
|
+
if (found)
|
|
144
|
+
return found;
|
|
145
|
+
}
|
|
146
|
+
if (fileNames.has('go.mod')) {
|
|
147
|
+
if (fileNames.has('main.go'))
|
|
148
|
+
return 'main.go';
|
|
149
|
+
const found = findGoCmdMain(cwd);
|
|
150
|
+
if (found)
|
|
151
|
+
return found;
|
|
152
|
+
}
|
|
153
|
+
return undefined;
|
|
154
|
+
}
|
|
155
|
+
function parsePyinstallerSpec(file, log) {
|
|
156
|
+
let text;
|
|
157
|
+
try {
|
|
158
|
+
text = fs.readFileSync(file, 'utf8');
|
|
159
|
+
}
|
|
160
|
+
catch (err) {
|
|
161
|
+
log.push(`pyinstaller .spec read failed: ${err.message}`);
|
|
162
|
+
return undefined;
|
|
163
|
+
}
|
|
164
|
+
const m = /Analysis\(\s*\[\s*['"]([^'"]+)['"]/.exec(text);
|
|
165
|
+
return m ? m[1] : undefined;
|
|
166
|
+
}
|
|
167
|
+
function parsePackageJson(file, log) {
|
|
168
|
+
let parsed;
|
|
169
|
+
try {
|
|
170
|
+
parsed = JSON.parse(fs.readFileSync(file, 'utf8'));
|
|
171
|
+
}
|
|
172
|
+
catch (err) {
|
|
173
|
+
log.push(`package.json parse failed: ${err.message}`);
|
|
174
|
+
return undefined;
|
|
175
|
+
}
|
|
176
|
+
if (!parsed || typeof parsed !== 'object')
|
|
177
|
+
return undefined;
|
|
178
|
+
const obj = parsed;
|
|
179
|
+
if (typeof obj.main === 'string')
|
|
180
|
+
return obj.main;
|
|
181
|
+
if (typeof obj.bin === 'string')
|
|
182
|
+
return obj.bin;
|
|
183
|
+
if (obj.bin && typeof obj.bin === 'object') {
|
|
184
|
+
for (const v of Object.values(obj.bin)) {
|
|
185
|
+
if (typeof v === 'string')
|
|
186
|
+
return v;
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
return undefined;
|
|
190
|
+
}
|
|
191
|
+
function parsePyproject(file, log) {
|
|
192
|
+
let text;
|
|
193
|
+
try {
|
|
194
|
+
text = fs.readFileSync(file, 'utf8');
|
|
195
|
+
}
|
|
196
|
+
catch (err) {
|
|
197
|
+
log.push(`pyproject.toml read failed: ${err.message}`);
|
|
198
|
+
return undefined;
|
|
199
|
+
}
|
|
200
|
+
for (const header of ['[project.scripts]', '[tool.poetry.scripts]']) {
|
|
201
|
+
const idx = text.indexOf(header);
|
|
202
|
+
if (idx === -1)
|
|
203
|
+
continue;
|
|
204
|
+
const rest = text.slice(idx + header.length);
|
|
205
|
+
const nextSectionRel = rest.search(/\n\s*\[/);
|
|
206
|
+
const section = nextSectionRel >= 0 ? rest.slice(0, nextSectionRel) : rest;
|
|
207
|
+
for (const line of section.split('\n')) {
|
|
208
|
+
const trimmed = line.trim();
|
|
209
|
+
if (!trimmed || trimmed.startsWith('#'))
|
|
210
|
+
continue;
|
|
211
|
+
const m = /^[A-Za-z_][\w-]*\s*=\s*['"]([^'"]+)['"]/.exec(trimmed);
|
|
212
|
+
if (m)
|
|
213
|
+
return moduleSpecToPath(m[1]);
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
return undefined;
|
|
217
|
+
}
|
|
218
|
+
function moduleSpecToPath(spec) {
|
|
219
|
+
const modulePart = spec.split(':')[0];
|
|
220
|
+
return modulePart.split('.').join('/') + '.py';
|
|
221
|
+
}
|
|
222
|
+
function parseCargoToml(file, cwd, log) {
|
|
223
|
+
let text;
|
|
224
|
+
try {
|
|
225
|
+
text = fs.readFileSync(file, 'utf8');
|
|
226
|
+
}
|
|
227
|
+
catch (err) {
|
|
228
|
+
log.push(`Cargo.toml read failed: ${err.message}`);
|
|
229
|
+
return undefined;
|
|
230
|
+
}
|
|
231
|
+
const binMatch = /\[\[bin\]\][\s\S]*?path\s*=\s*['"]([^'"]+)['"]/.exec(text);
|
|
232
|
+
if (binMatch)
|
|
233
|
+
return binMatch[1];
|
|
234
|
+
if (fs.existsSync(path.join(cwd, 'src', 'main.rs')))
|
|
235
|
+
return 'src/main.rs';
|
|
236
|
+
return undefined;
|
|
237
|
+
}
|
|
238
|
+
function findGoCmdMain(cwd) {
|
|
239
|
+
const cmdDir = path.join(cwd, 'cmd');
|
|
240
|
+
let entries;
|
|
241
|
+
try {
|
|
242
|
+
entries = fs.readdirSync(cmdDir, { withFileTypes: true });
|
|
243
|
+
}
|
|
244
|
+
catch {
|
|
245
|
+
return undefined;
|
|
246
|
+
}
|
|
247
|
+
for (const entry of entries) {
|
|
248
|
+
if (!entry.isDirectory())
|
|
249
|
+
continue;
|
|
250
|
+
const candidate = path.join(cmdDir, entry.name, 'main.go');
|
|
251
|
+
if (fs.existsSync(candidate))
|
|
252
|
+
return path.posix.join('cmd', entry.name, 'main.go');
|
|
253
|
+
}
|
|
254
|
+
return undefined;
|
|
255
|
+
}
|
|
256
|
+
//# sourceMappingURL=survey-source-reader.js.map
|
package/dist/core/loops/index.js
CHANGED
|
@@ -1,7 +1,13 @@
|
|
|
1
1
|
export * from './types.js';
|
|
2
|
-
export { closeLoop, ensureLoopsDir, generateLoopId, generateMutationId, generateSlotId, getLoop, listLoopEvents, listLoops, openLoop, } from './store.js';
|
|
3
|
-
export { add_artifact, advance, complete_turn, evaluatePhaseAdvanceGate, evaluateStopCondition, pause, resume, turn, } from './verbs.js';
|
|
2
|
+
export { AwaitingFileApplyApprovalError, closeLoop, ensureLoopsDir, generateLoopId, generateMutationId, generateSlotId, getLoop, listLoopEvents, listLoops, openLoop, writeThreadFile, } from './store.js';
|
|
3
|
+
export { add_artifact, advance, complete_turn, evaluatePhaseAdvanceGate, evaluateStopCondition, pause, provideInput, reconcileOpenQuestions, requestInput, resume, sweepPauseTimeouts, turn, } from './verbs.js';
|
|
4
4
|
export { decideNextPhase, artifactsInIteration, noNewCritiqueInIteration, hasCriticSignalInIteration, } from './iteration-engine.js';
|
|
5
|
+
export { computeNextExpected, } from './next-expected.js';
|
|
5
6
|
export { buildIdeationBrief, } from './brief-assembly.js';
|
|
7
|
+
export { BOOTSTRAP_PRESET } from './presets/bootstrap.js';
|
|
8
|
+
export { writeProjectMdSafe, } from './hooks/bootstrap-write.js';
|
|
9
|
+
export { notifyOperatorOnInputRequested } from './hooks/notify-operator.js';
|
|
10
|
+
export { readSurveySources, } from './hooks/survey-source-reader.js';
|
|
6
11
|
export { acquireLock, hashRequest, recordConflict, withLoopLock, DEFAULT_MAX_MUTATION_DURATION_MS, IDEMPOTENCY_TTL_MS, LEASE_GRACE_MS, LEASE_WINDOW_MS, IdempotencyKeyReusedError, IdempotencyOwnerMismatchError, LockLostError, LockTimeoutError, VersionConflictError, } from './lock.js';
|
|
12
|
+
export { acquireBootstrapLoop, findExistingBootstrapLoop, BootstrapCoordinationInProgressError, } from './bootstrap-acquire.js';
|
|
7
13
|
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
export function computeNextExpected(loop) {
|
|
2
|
+
if (loop.status === 'completed' || loop.status === 'cancelled' || loop.status === 'blocked') {
|
|
3
|
+
return null;
|
|
4
|
+
}
|
|
5
|
+
// pln#508 step 3 fix from codex review (loop-handlers.ts:252): paused
|
|
6
|
+
// loops with open operator_questions should hint provide_input, not
|
|
7
|
+
// advance/close. open_questions check fires regardless of status so it
|
|
8
|
+
// also catches the slot-scope case where loop.status === 'open' but a
|
|
9
|
+
// slot is in waiting_input.
|
|
10
|
+
if (loop.open_questions.length > 0) {
|
|
11
|
+
return {
|
|
12
|
+
action: 'provide_input',
|
|
13
|
+
intent: 'bclaw_loop.provide_input',
|
|
14
|
+
reason: loop.status === 'paused' ? loop.pause_reason : 'awaiting_operator',
|
|
15
|
+
blocking_on: [...loop.open_questions],
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
if (loop.status === 'paused') {
|
|
19
|
+
return null;
|
|
20
|
+
}
|
|
21
|
+
const currentPhaseSlots = loop.slots.filter((s) => (s.phase ?? loop.current_phase) === loop.current_phase);
|
|
22
|
+
const openSlots = currentPhaseSlots.filter((s) => s.status === 'open');
|
|
23
|
+
if (openSlots.length > 0) {
|
|
24
|
+
const first = openSlots[0];
|
|
25
|
+
return {
|
|
26
|
+
action: 'turn',
|
|
27
|
+
intent: 'bclaw_loop.turn',
|
|
28
|
+
phase: loop.current_phase,
|
|
29
|
+
slot_id: first.slot_id,
|
|
30
|
+
role: first.role,
|
|
31
|
+
blocking_on: openSlots.map((s) => s.slot_id),
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
const assignedOrWorking = currentPhaseSlots.filter((s) => s.status === 'assigned' || s.status === 'working');
|
|
35
|
+
if (assignedOrWorking.length > 0) {
|
|
36
|
+
return {
|
|
37
|
+
action: 'complete_turn',
|
|
38
|
+
intent: 'bclaw_loop.complete_turn',
|
|
39
|
+
phase: loop.current_phase,
|
|
40
|
+
slot_id: assignedOrWorking[0].slot_id,
|
|
41
|
+
role: assignedOrWorking[0].role,
|
|
42
|
+
blocking_on: assignedOrWorking.map((s) => s.slot_id),
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
const phaseNames = loop.phases.map((p) => p.name);
|
|
46
|
+
const currentIndex = phaseNames.indexOf(loop.current_phase);
|
|
47
|
+
if (currentIndex >= 0 && currentIndex + 1 < phaseNames.length) {
|
|
48
|
+
return {
|
|
49
|
+
action: 'advance',
|
|
50
|
+
intent: 'bclaw_loop.advance',
|
|
51
|
+
from_phase: loop.current_phase,
|
|
52
|
+
to_phase: phaseNames[currentIndex + 1],
|
|
53
|
+
blocking_on: [],
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
return {
|
|
57
|
+
action: 'close',
|
|
58
|
+
intent: 'bclaw_loop.close',
|
|
59
|
+
reason: 'terminal_phase_reached',
|
|
60
|
+
blocking_on: [],
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
//# sourceMappingURL=next-expected.js.map
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* pln#511 step 1 — bootstrap preset.
|
|
3
|
+
*
|
|
4
|
+
* Phase chain (Phase 0 spec):
|
|
5
|
+
* survey → produce signals_report from existing project memory.
|
|
6
|
+
* propose → first-draft PROJECT.md from survey + freeform context.
|
|
7
|
+
* clarify → at most one round of operator questions; advance once
|
|
8
|
+
* open_questions drains OR the cap fires.
|
|
9
|
+
* review_draft→ wait for the operator's verdict / answers.
|
|
10
|
+
* converge → emit the final project_md_final and close.
|
|
11
|
+
*
|
|
12
|
+
* `max_operator_questions=3` and `max_pause_duration='P7D'` match the
|
|
13
|
+
* Phase 0 spec defaults (mitigates feedback_agent_autonomy_gap.md — agents
|
|
14
|
+
* must not defer everything to the human).
|
|
15
|
+
*/
|
|
16
|
+
export const BOOTSTRAP_PRESET = {
|
|
17
|
+
phases: [
|
|
18
|
+
// pln#516 step 1 — survey reads source. The bootstrap champion SHOULD
|
|
19
|
+
// call `readSurveySources(cwd)` (exported from ../hooks/survey-source-reader.js)
|
|
20
|
+
// to populate the source_excerpts portion of its signals_report. The engine
|
|
21
|
+
// does NOT invoke this automatically; the champion drives it (RefBasedArtifactBody
|
|
22
|
+
// is unchanged — see types.ts). Empirical motivation: TranslaVox cold-start
|
|
23
|
+
// missed the actual GCP Speech+Translate pipeline because survey scanned only
|
|
24
|
+
// topology + manifests (can_0160d6c4).
|
|
25
|
+
{
|
|
26
|
+
name: 'survey',
|
|
27
|
+
context_filter: ['project_vision', 'decisions', 'plans', 'feedback'],
|
|
28
|
+
advance_gate: { kind: 'artifact_produced', phase: 'survey', type: 'signals_report' },
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
name: 'propose',
|
|
32
|
+
context_filter: ['*'],
|
|
33
|
+
advance_gate: { kind: 'artifact_produced', phase: 'propose', type: 'project_md_draft' },
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
name: 'clarify',
|
|
37
|
+
context_filter: ['critique_history', 'runtime_notes', 'feedback'],
|
|
38
|
+
// pln#516 step 2 — wrap the original `any` exit condition under a
|
|
39
|
+
// `min_iterations >= 1` floor so the gate refuses to auto-traverse at
|
|
40
|
+
// entry. Without the floor `no_open_questions` is trivially true while
|
|
41
|
+
// `open_questions` is still `[]` (it only fills once the champion calls
|
|
42
|
+
// requestInput), and `advance(propose→clarify)` slid straight into
|
|
43
|
+
// review_draft (can_d5a41770, run_4b0500c6).
|
|
44
|
+
advance_gate: {
|
|
45
|
+
kind: 'all',
|
|
46
|
+
conditions: [
|
|
47
|
+
{ kind: 'min_iterations', n: 1 },
|
|
48
|
+
{
|
|
49
|
+
kind: 'any',
|
|
50
|
+
conditions: [
|
|
51
|
+
{ kind: 'no_open_questions' },
|
|
52
|
+
{ kind: 'max_iterations', n: 1 },
|
|
53
|
+
],
|
|
54
|
+
},
|
|
55
|
+
],
|
|
56
|
+
},
|
|
57
|
+
},
|
|
58
|
+
{
|
|
59
|
+
name: 'review_draft',
|
|
60
|
+
context_filter: ['*'],
|
|
61
|
+
advance_gate: { kind: 'artifact_produced', phase: 'review_draft', type: 'operator_answer' },
|
|
62
|
+
},
|
|
63
|
+
{
|
|
64
|
+
name: 'converge',
|
|
65
|
+
context_filter: ['*'],
|
|
66
|
+
},
|
|
67
|
+
],
|
|
68
|
+
stop_condition: { kind: 'artifact_produced', phase: 'converge', type: 'project_md_final' },
|
|
69
|
+
protocol: {
|
|
70
|
+
preset: 'bootstrap',
|
|
71
|
+
max_operator_questions: 3,
|
|
72
|
+
max_pause_duration: 'P7D',
|
|
73
|
+
},
|
|
74
|
+
};
|
|
75
|
+
//# sourceMappingURL=bootstrap.js.map
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { BOOTSTRAP_PRESET } from './bootstrap.js';
|
|
2
|
+
/**
|
|
3
|
+
* pln#511 step 2 — loop preset registry.
|
|
4
|
+
*
|
|
5
|
+
* The bclaw_coordinate(intent='ideate') handler resolves a caller-supplied
|
|
6
|
+
* `preset` string against this map. Adding a new preset is a one-line
|
|
7
|
+
* change here (plus the preset module itself). The handler intentionally
|
|
8
|
+
* does not import individual presets — only this registry — so unknown
|
|
9
|
+
* names produce a deterministic `unknown_preset` error referencing the
|
|
10
|
+
* keys exported from this file.
|
|
11
|
+
*/
|
|
12
|
+
export const PRESETS = Object.freeze({
|
|
13
|
+
bootstrap: BOOTSTRAP_PRESET,
|
|
14
|
+
});
|
|
15
|
+
export { BOOTSTRAP_PRESET };
|
|
16
|
+
//# sourceMappingURL=index.js.map
|