let-them-talk 5.3.0 → 5.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +3 -1
- package/README.md +158 -592
- package/SECURITY.md +3 -3
- package/USAGE.md +151 -0
- package/agent-contracts.js +447 -0
- package/api-agents.js +760 -0
- package/autonomy/decision-v2.js +380 -0
- package/autonomy/watchdog-policy.js +572 -0
- package/cli.js +454 -298
- package/conversation-templates/autonomous-feature.json +83 -22
- package/conversation-templates/code-review.json +69 -21
- package/conversation-templates/debug-squad.json +69 -21
- package/conversation-templates/feature-build.json +69 -21
- package/conversation-templates/research-write.json +69 -21
- package/dashboard.html +3148 -174
- package/dashboard.js +823 -786
- package/data-dir.js +58 -0
- package/docs/architecture/branch-semantics.md +157 -0
- package/docs/architecture/canonical-event-schema.md +88 -0
- package/docs/architecture/markdown-workspace.md +183 -0
- package/docs/architecture/runtime-contract.md +459 -0
- package/docs/architecture/runtime-migration-hardening.md +64 -0
- package/events/hooks.js +154 -0
- package/events/log.js +457 -0
- package/events/replay.js +33 -0
- package/events/schema.js +432 -0
- package/managed-team-integration.js +261 -0
- package/office/agents.js +704 -597
- package/office/animation.js +1 -1
- package/office/assets/arcade-cabinet.js +141 -0
- package/office/assets/archway.js +77 -0
- package/office/assets/bar-counter.js +91 -0
- package/office/assets/bar-stool.js +71 -0
- package/office/assets/beanbag.js +64 -0
- package/office/assets/bench.js +99 -0
- package/office/assets/bollard.js +87 -0
- package/office/assets/cactus.js +100 -0
- package/office/assets/carpet-tile.js +46 -0
- package/office/assets/chair.js +123 -0
- package/office/assets/chandelier.js +107 -0
- package/office/assets/coffee-machine.js +95 -0
- package/office/assets/coffee-table.js +81 -0
- package/office/assets/column.js +95 -0
- package/office/assets/desk-lamp.js +102 -0
- package/office/assets/desk.js +76 -0
- package/office/assets/dining-table.js +105 -0
- package/office/assets/door.js +70 -0
- package/office/assets/dual-monitor.js +72 -0
- package/office/assets/fence.js +76 -0
- package/office/assets/filing-cabinet.js +111 -0
- package/office/assets/floor-lamp.js +69 -0
- package/office/assets/floor-tile.js +54 -0
- package/office/assets/flower-pot.js +76 -0
- package/office/assets/foosball.js +95 -0
- package/office/assets/fridge.js +99 -0
- package/office/assets/gaming-chair.js +154 -0
- package/office/assets/gaming-desk.js +105 -0
- package/office/assets/glass-door.js +72 -0
- package/office/assets/glass-wall.js +64 -0
- package/office/assets/half-wall.js +49 -0
- package/office/assets/hanging-plant.js +112 -0
- package/office/assets/index.js +151 -0
- package/office/assets/indoor-tree.js +90 -0
- package/office/assets/l-sofa.js +153 -0
- package/office/assets/marble-floor.js +64 -0
- package/office/assets/materials.js +40 -0
- package/office/assets/meeting-table.js +88 -0
- package/office/assets/microwave.js +94 -0
- package/office/assets/monitor.js +67 -0
- package/office/assets/neon-strip.js +73 -0
- package/office/assets/painting.js +84 -0
- package/office/assets/palm-tree.js +108 -0
- package/office/assets/pc-tower.js +91 -0
- package/office/assets/pendant-light.js +67 -0
- package/office/assets/ping-pong.js +114 -0
- package/office/assets/plant.js +72 -0
- package/office/assets/planter-box.js +95 -0
- package/office/assets/pool-table.js +94 -0
- package/office/assets/printer.js +113 -0
- package/office/assets/reception-desk.js +133 -0
- package/office/assets/rug.js +78 -0
- package/office/assets/sculpture.js +85 -0
- package/office/assets/server-rack.js +98 -0
- package/office/assets/sink.js +109 -0
- package/office/assets/sofa.js +106 -0
- package/office/assets/speaker.js +83 -0
- package/office/assets/spotlight.js +83 -0
- package/office/assets/street-lamp.js +97 -0
- package/office/assets/trash-can.js +83 -0
- package/office/assets/treadmill.js +126 -0
- package/office/assets/trophy.js +89 -0
- package/office/assets/tv-screen.js +79 -0
- package/office/assets/vase.js +84 -0
- package/office/assets/wall-clock.js +84 -0
- package/office/assets/wall.js +53 -0
- package/office/assets/water-cooler.js +146 -0
- package/office/assets/whiteboard.js +115 -0
- package/office/assets.js +3 -431
- package/office/builder.js +791 -355
- package/office/campus-env.js +1012 -1119
- package/office/environment.js +2 -0
- package/office/gallery.js +997 -0
- package/office/index.js +165 -61
- package/office/navigation.js +173 -152
- package/office/player.js +178 -68
- package/office/robot-character.js +272 -0
- package/office/spectator-camera.js +33 -10
- package/office/state.js +2 -0
- package/office/world-save.js +35 -4
- package/package.json +57 -3
- package/providers/comfyui.js +383 -0
- package/providers/dalle.js +79 -0
- package/providers/gemini.js +181 -0
- package/providers/ollama.js +184 -0
- package/providers/replicate.js +115 -0
- package/providers/zai.js +183 -0
- package/runtime-descriptor.js +270 -0
- package/scripts/check-agent-contract-advisory.js +132 -0
- package/scripts/check-api-agent-parity.js +277 -0
- package/scripts/check-autonomy-v2-decision.js +207 -0
- package/scripts/check-autonomy-v2-execution.js +588 -0
- package/scripts/check-autonomy-v2-watchdog.js +224 -0
- package/scripts/check-branch-fork-snapshot.js +337 -0
- package/scripts/check-branch-isolation.js +787 -0
- package/scripts/check-branch-semantics.js +139 -0
- package/scripts/check-dashboard-control-plane.js +1304 -0
- package/scripts/check-docs-onboarding.js +490 -0
- package/scripts/check-event-schema.js +276 -0
- package/scripts/check-evidence-completion.js +239 -0
- package/scripts/check-invariants.js +992 -0
- package/scripts/check-lifecycle-hooks.js +525 -0
- package/scripts/check-managed-team-integration.js +166 -0
- package/scripts/check-markdown-workspace-export.js +548 -0
- package/scripts/check-markdown-workspace-safety.js +347 -0
- package/scripts/check-markdown-workspace.js +136 -0
- package/scripts/check-message-replay.js +429 -0
- package/scripts/check-migration-hardening.js +300 -0
- package/scripts/check-performance-indexing.js +272 -0
- package/scripts/check-provider-capabilities.js +316 -0
- package/scripts/check-runtime-contract.js +109 -0
- package/scripts/check-session-aware-context.js +172 -0
- package/scripts/check-session-lifecycle.js +210 -0
- package/scripts/export-markdown-workspace.js +84 -0
- package/scripts/fixtures/message-replay/clean.jsonl +2 -0
- package/scripts/fixtures/message-replay/corrupt-correction-payload.jsonl +1 -0
- package/scripts/fixtures/message-replay/corrupt-jsonl.jsonl +1 -0
- package/scripts/fixtures/message-replay/corrupt-payload.jsonl +1 -0
- package/scripts/fixtures/message-replay/out-of-order.jsonl +2 -0
- package/scripts/migrate-legacy-to-canonical.js +201 -0
- package/scripts/run-verification-suite.js +242 -0
- package/scripts/sync-packaged-docs.js +69 -0
- package/server.js +9546 -7216
- package/state/agents.js +161 -0
- package/state/canonical.js +3068 -0
- package/state/dashboard-queries.js +441 -0
- package/state/evidence.js +56 -0
- package/state/io.js +69 -0
- package/state/markdown-workspace.js +951 -0
- package/state/messages.js +669 -0
- package/state/sessions.js +683 -0
- package/state/tasks-workflows.js +92 -0
- package/templates/debate.json +2 -2
- package/templates/managed.json +4 -4
- package/templates/pair.json +2 -2
- package/templates/review.json +2 -2
- package/templates/team.json +3 -3
|
@@ -0,0 +1,548 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
const os = require('os');
|
|
5
|
+
const path = require('path');
|
|
6
|
+
const { spawnSync } = require('child_process');
|
|
7
|
+
|
|
8
|
+
const { createCanonicalState, createBranchPathResolvers } = require(path.resolve(__dirname, '..', 'state', 'canonical.js'));
|
|
9
|
+
const { createCanonicalEventLog } = require(path.resolve(__dirname, '..', 'events', 'log.js'));
|
|
10
|
+
const { createStateIo } = require(path.resolve(__dirname, '..', 'state', 'io.js'));
|
|
11
|
+
const { createSessionsState } = require(path.resolve(__dirname, '..', 'state', 'sessions.js'));
|
|
12
|
+
|
|
13
|
+
const EXPORT_SCRIPT = path.resolve(__dirname, 'export-markdown-workspace.js');
|
|
14
|
+
|
|
15
|
+
function fail(lines, exitCode = 1) {
|
|
16
|
+
fs.writeSync(2, lines.join('\n') + '\n');
|
|
17
|
+
process.exit(exitCode);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function assert(condition, message, problems) {
|
|
21
|
+
if (!condition) problems.push(message);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function writeJson(filePath, value) {
|
|
25
|
+
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
26
|
+
fs.writeFileSync(filePath, JSON.stringify(value, null, 2));
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function writeJsonl(filePath, entries) {
|
|
30
|
+
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
31
|
+
const lines = entries.map((entry) => JSON.stringify(entry));
|
|
32
|
+
fs.writeFileSync(filePath, lines.length > 0 ? `${lines.join('\n')}\n` : '');
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function readFile(filePath) {
|
|
36
|
+
return fs.readFileSync(filePath, 'utf8');
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function parseFrontmatter(markdown) {
|
|
40
|
+
const match = markdown.match(/^---\n([\s\S]*?)\n---\n/);
|
|
41
|
+
if (!match) return null;
|
|
42
|
+
const result = {};
|
|
43
|
+
for (const line of match[1].split(/\r?\n/)) {
|
|
44
|
+
const index = line.indexOf(':');
|
|
45
|
+
if (index === -1) continue;
|
|
46
|
+
const key = line.slice(0, index).trim();
|
|
47
|
+
const raw = line.slice(index + 1).trim();
|
|
48
|
+
if (raw === 'null') {
|
|
49
|
+
result[key] = null;
|
|
50
|
+
} else if (raw === 'true') {
|
|
51
|
+
result[key] = true;
|
|
52
|
+
} else if (raw === 'false') {
|
|
53
|
+
result[key] = false;
|
|
54
|
+
} else if (/^-?\d+(\.\d+)?$/.test(raw)) {
|
|
55
|
+
result[key] = Number(raw);
|
|
56
|
+
} else if ((raw.startsWith('"') && raw.endsWith('"')) || (raw.startsWith("'") && raw.endsWith("'"))) {
|
|
57
|
+
try {
|
|
58
|
+
result[key] = JSON.parse(raw);
|
|
59
|
+
} catch {
|
|
60
|
+
result[key] = raw.slice(1, -1);
|
|
61
|
+
}
|
|
62
|
+
} else {
|
|
63
|
+
result[key] = raw;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
return result;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function main() {
|
|
70
|
+
const tempRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'letthemtalk-markdown-export-'));
|
|
71
|
+
const projectRoot = tempRoot;
|
|
72
|
+
const dataDir = path.join(projectRoot, '.agent-bridge');
|
|
73
|
+
const outputRoot = path.join(projectRoot, '.agent-bridge-markdown');
|
|
74
|
+
const branchPaths = createBranchPathResolvers(dataDir);
|
|
75
|
+
const io = createStateIo({ dataDir });
|
|
76
|
+
const eventLog = createCanonicalEventLog({ dataDir });
|
|
77
|
+
const sessionsState = createSessionsState({ io, branchPaths, canonicalEventLog: eventLog });
|
|
78
|
+
const canonicalState = createCanonicalState({ dataDir, processPid: process.pid });
|
|
79
|
+
const problems = [];
|
|
80
|
+
|
|
81
|
+
try {
|
|
82
|
+
const now = '2026-04-16T12:00:00.000Z';
|
|
83
|
+
const mainSessionTime = '2026-04-16T12:01:00.000Z';
|
|
84
|
+
const featureSessionTime = '2026-04-16T12:02:00.000Z';
|
|
85
|
+
|
|
86
|
+
writeJson(path.join(dataDir, 'branches.json'), {
|
|
87
|
+
main: {
|
|
88
|
+
created_at: now,
|
|
89
|
+
created_by: 'alpha',
|
|
90
|
+
message_count: 2,
|
|
91
|
+
},
|
|
92
|
+
feature_docs: {
|
|
93
|
+
created_at: '2026-04-16T12:00:30.000Z',
|
|
94
|
+
created_by: 'beta',
|
|
95
|
+
forked_from: 'main',
|
|
96
|
+
fork_point: 'msg_main_1',
|
|
97
|
+
message_count: 1,
|
|
98
|
+
},
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
writeJson(path.join(dataDir, 'agents.json'), {
|
|
102
|
+
alpha: { pid: process.pid, provider: 'claude', timestamp: now, last_activity: now },
|
|
103
|
+
beta: { pid: process.pid, provider: 'codex', timestamp: now, last_activity: now },
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
writeJson(path.join(dataDir, 'profiles.json'), {
|
|
107
|
+
alpha: { display_name: 'Alpha' },
|
|
108
|
+
beta: { display_name: 'Beta' },
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
canonicalState.appendMessage({
|
|
112
|
+
id: 'msg_main_1',
|
|
113
|
+
from: 'alpha',
|
|
114
|
+
to: 'beta',
|
|
115
|
+
content: 'Main branch hello',
|
|
116
|
+
timestamp: '2026-04-16T12:03:00.000Z',
|
|
117
|
+
}, { branch: 'main', actorAgent: 'alpha', sessionId: 'session_alpha_main' });
|
|
118
|
+
|
|
119
|
+
canonicalState.appendMessage({
|
|
120
|
+
id: 'msg_feature_1',
|
|
121
|
+
from: 'beta',
|
|
122
|
+
to: 'alpha',
|
|
123
|
+
content: 'Feature branch hello',
|
|
124
|
+
timestamp: '2026-04-16T12:04:00.000Z',
|
|
125
|
+
}, { branch: 'feature_docs', actorAgent: 'beta', sessionId: 'session_beta_feature' });
|
|
126
|
+
|
|
127
|
+
writeJson(branchPaths.getChannelsFile('main'), {
|
|
128
|
+
general: { description: 'General channel', members: ['*'] },
|
|
129
|
+
ops: { description: 'Ops sync', members: ['alpha'] },
|
|
130
|
+
});
|
|
131
|
+
writeJson(branchPaths.getChannelsFile('feature_docs'), {
|
|
132
|
+
general: { description: 'General channel', members: ['*'] },
|
|
133
|
+
dev: { description: 'Feature development', members: ['alpha', 'beta'] },
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
writeJsonl(branchPaths.getChannelHistoryFile('ops', 'main'), [{
|
|
137
|
+
id: 'msg_main_ops_1',
|
|
138
|
+
from: 'alpha',
|
|
139
|
+
to: 'alpha',
|
|
140
|
+
channel: 'ops',
|
|
141
|
+
content: 'Ops-only update',
|
|
142
|
+
timestamp: '2026-04-16T12:03:30.000Z',
|
|
143
|
+
}]);
|
|
144
|
+
|
|
145
|
+
writeJsonl(branchPaths.getChannelHistoryFile('dev', 'feature_docs'), [{
|
|
146
|
+
id: 'msg_feature_dev_1',
|
|
147
|
+
from: 'beta',
|
|
148
|
+
to: 'alpha',
|
|
149
|
+
channel: 'dev',
|
|
150
|
+
content: 'Feature dev note',
|
|
151
|
+
timestamp: '2026-04-16T12:04:30.000Z',
|
|
152
|
+
}]);
|
|
153
|
+
|
|
154
|
+
writeJson(branchPaths.getAcksFile('main'), {
|
|
155
|
+
msg_main_1: true,
|
|
156
|
+
msg_main_ops_1: false,
|
|
157
|
+
});
|
|
158
|
+
writeJson(branchPaths.getAcksFile('feature_docs'), {
|
|
159
|
+
msg_feature_1: true,
|
|
160
|
+
msg_feature_dev_1: false,
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
const mainSession = sessionsState.activateSession({
|
|
164
|
+
agentName: 'alpha',
|
|
165
|
+
branchName: 'main',
|
|
166
|
+
provider: 'claude',
|
|
167
|
+
reason: 'register',
|
|
168
|
+
at: mainSessionTime,
|
|
169
|
+
});
|
|
170
|
+
sessionsState.transitionSession({
|
|
171
|
+
sessionId: mainSession.session.session_id,
|
|
172
|
+
branchName: 'main',
|
|
173
|
+
state: 'completed',
|
|
174
|
+
reason: 'graceful_exit',
|
|
175
|
+
at: '2026-04-16T12:01:30.000Z',
|
|
176
|
+
});
|
|
177
|
+
const featureSession = sessionsState.activateSession({
|
|
178
|
+
agentName: 'beta',
|
|
179
|
+
branchName: 'feature_docs',
|
|
180
|
+
provider: 'codex',
|
|
181
|
+
reason: 'register',
|
|
182
|
+
at: featureSessionTime,
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
writeJson(branchPaths.getEvidenceFile('main'), {
|
|
186
|
+
schema_version: 1,
|
|
187
|
+
updated_at: '2026-04-16T12:05:00.000Z',
|
|
188
|
+
records: [{
|
|
189
|
+
evidence_id: 'evidence_main_1',
|
|
190
|
+
branch_id: 'main',
|
|
191
|
+
subject_kind: 'task',
|
|
192
|
+
task_id: 'task_main',
|
|
193
|
+
task_title: 'Main task',
|
|
194
|
+
summary: 'Validated the main branch fixture',
|
|
195
|
+
verification: 'Ran the markdown export validator fixture.',
|
|
196
|
+
files_changed: ['agent-bridge/state/canonical.js'],
|
|
197
|
+
confidence: 92,
|
|
198
|
+
learnings: 'Keep export one-way.',
|
|
199
|
+
recorded_at: '2026-04-16T12:05:00.000Z',
|
|
200
|
+
recorded_by: 'alpha',
|
|
201
|
+
recorded_by_session: mainSession.session.session_id,
|
|
202
|
+
source_tool: 'fixture',
|
|
203
|
+
}],
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
writeJson(branchPaths.getEvidenceFile('feature_docs'), {
|
|
207
|
+
schema_version: 1,
|
|
208
|
+
updated_at: '2026-04-16T12:05:30.000Z',
|
|
209
|
+
records: [{
|
|
210
|
+
evidence_id: 'evidence_feature_1',
|
|
211
|
+
branch_id: 'feature_docs',
|
|
212
|
+
subject_kind: 'workflow',
|
|
213
|
+
workflow_id: 'wf_feature',
|
|
214
|
+
summary: 'Validated the feature branch fixture',
|
|
215
|
+
verification: 'Confirmed the branch-local export stays isolated.',
|
|
216
|
+
files_changed: ['agent-bridge/scripts/export-markdown-workspace.js'],
|
|
217
|
+
confidence: 88,
|
|
218
|
+
learnings: 'Do not fabricate shared pages on non-main branches.',
|
|
219
|
+
recorded_at: '2026-04-16T12:05:30.000Z',
|
|
220
|
+
recorded_by: 'beta',
|
|
221
|
+
recorded_by_session: featureSession.session.session_id,
|
|
222
|
+
source_tool: 'fixture',
|
|
223
|
+
}],
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
writeJson(branchPaths.getDecisionsFile('main'), [{
|
|
227
|
+
id: 'dec_markdown_1',
|
|
228
|
+
decision: 'Keep markdown export projection-only.',
|
|
229
|
+
reasoning: 'Markdown is a generated workspace, not a runtime input.',
|
|
230
|
+
topic: 'markdown',
|
|
231
|
+
decided_by: 'alpha',
|
|
232
|
+
decided_at: '2026-04-16T12:06:00.000Z',
|
|
233
|
+
}]);
|
|
234
|
+
writeJson(branchPaths.getDecisionsFile('feature_docs'), [{
|
|
235
|
+
id: 'dec_feature_1',
|
|
236
|
+
decision: 'Keep feature export isolated.',
|
|
237
|
+
reasoning: 'Feature branches should not inherit shared governance summaries.',
|
|
238
|
+
topic: 'branching',
|
|
239
|
+
decided_by: 'beta',
|
|
240
|
+
decided_at: '2026-04-16T12:06:10.000Z',
|
|
241
|
+
}]);
|
|
242
|
+
|
|
243
|
+
writeJson(branchPaths.getKnowledgeBaseFile('main'), {
|
|
244
|
+
markdown_contract: {
|
|
245
|
+
content: 'Use canonical read surfaces for export assembly.',
|
|
246
|
+
updated_by: 'alpha',
|
|
247
|
+
updated_at: '2026-04-16T12:06:30.000Z',
|
|
248
|
+
},
|
|
249
|
+
});
|
|
250
|
+
writeJson(branchPaths.getKnowledgeBaseFile('feature_docs'), {
|
|
251
|
+
feature_export: {
|
|
252
|
+
content: 'Feature branch exports should summarize only feature-branch governance state.',
|
|
253
|
+
updated_by: 'beta',
|
|
254
|
+
updated_at: '2026-04-16T12:06:35.000Z',
|
|
255
|
+
},
|
|
256
|
+
});
|
|
257
|
+
|
|
258
|
+
writeJson(branchPaths.getRulesFile('main'), [{
|
|
259
|
+
id: 'rule_markdown_one_way',
|
|
260
|
+
text: 'Do not import markdown back into runtime state.',
|
|
261
|
+
category: 'safety',
|
|
262
|
+
active: true,
|
|
263
|
+
created_by: 'alpha',
|
|
264
|
+
created_at: '2026-04-16T12:06:45.000Z',
|
|
265
|
+
}]);
|
|
266
|
+
writeJson(branchPaths.getRulesFile('feature_docs'), [{
|
|
267
|
+
id: 'rule_feature_isolation',
|
|
268
|
+
text: 'Keep feature governance surfaces branch-local.',
|
|
269
|
+
category: 'workflow',
|
|
270
|
+
active: true,
|
|
271
|
+
created_by: 'beta',
|
|
272
|
+
created_at: '2026-04-16T12:06:50.000Z',
|
|
273
|
+
}]);
|
|
274
|
+
|
|
275
|
+
writeJson(branchPaths.getProgressFile('main'), {
|
|
276
|
+
markdown_workspace: {
|
|
277
|
+
percent: 60,
|
|
278
|
+
notes: 'One-way export slice in progress.',
|
|
279
|
+
},
|
|
280
|
+
});
|
|
281
|
+
writeJson(branchPaths.getProgressFile('feature_docs'), {
|
|
282
|
+
feature_export: {
|
|
283
|
+
percent: 85,
|
|
284
|
+
notes: 'Feature branch export summary nearly complete.',
|
|
285
|
+
},
|
|
286
|
+
});
|
|
287
|
+
|
|
288
|
+
writeJson(branchPaths.getReviewsFile('main'), [{
|
|
289
|
+
id: 'review_1',
|
|
290
|
+
file_path: 'agent-bridge/state/markdown-workspace.js',
|
|
291
|
+
status: 'approved',
|
|
292
|
+
}]);
|
|
293
|
+
writeJson(branchPaths.getReviewsFile('feature_docs'), [{
|
|
294
|
+
id: 'review_feature_1',
|
|
295
|
+
file_path: 'agent-bridge/scripts/export-markdown-workspace.js',
|
|
296
|
+
status: 'changes_requested',
|
|
297
|
+
}]);
|
|
298
|
+
|
|
299
|
+
writeJson(branchPaths.getDependenciesFile('main'), [{
|
|
300
|
+
task_id: 'task_markdown_export',
|
|
301
|
+
depends_on: 'task_markdown_contract',
|
|
302
|
+
}]);
|
|
303
|
+
writeJson(branchPaths.getDependenciesFile('feature_docs'), [{
|
|
304
|
+
task_id: 'task_feature_export',
|
|
305
|
+
depends_on: 'task_feature_contract',
|
|
306
|
+
}]);
|
|
307
|
+
|
|
308
|
+
writeJson(branchPaths.getVotesFile('main'), [{
|
|
309
|
+
id: 'vote_1',
|
|
310
|
+
question: 'Ship the markdown export slice?',
|
|
311
|
+
status: 'resolved',
|
|
312
|
+
result: 'yes',
|
|
313
|
+
}]);
|
|
314
|
+
writeJson(branchPaths.getVotesFile('feature_docs'), [{
|
|
315
|
+
id: 'vote_feature_1',
|
|
316
|
+
question: 'Ship the feature export slice?',
|
|
317
|
+
status: 'open',
|
|
318
|
+
result: null,
|
|
319
|
+
}]);
|
|
320
|
+
|
|
321
|
+
writeJson(branchPaths.getWorkspaceFile('alpha', 'main'), {
|
|
322
|
+
draft: { content: 'Summarize export findings.' },
|
|
323
|
+
retry_history: [],
|
|
324
|
+
});
|
|
325
|
+
writeJson(branchPaths.getWorkspaceFile('beta', 'main'), {
|
|
326
|
+
notes: 'Main branch workspace note',
|
|
327
|
+
});
|
|
328
|
+
writeJson(branchPaths.getWorkspaceFile('alpha', 'feature_docs'), {
|
|
329
|
+
notes: 'Feature branch alpha workspace note',
|
|
330
|
+
});
|
|
331
|
+
writeJson(branchPaths.getWorkspaceFile('beta', 'feature_docs'), {
|
|
332
|
+
notes: 'Feature branch workspace note',
|
|
333
|
+
});
|
|
334
|
+
|
|
335
|
+
writeJson(path.join(dataDir, 'workflows.json'), [
|
|
336
|
+
{
|
|
337
|
+
id: 'wf_active',
|
|
338
|
+
name: 'Active markdown export',
|
|
339
|
+
status: 'active',
|
|
340
|
+
autonomous: true,
|
|
341
|
+
parallel: false,
|
|
342
|
+
paused: false,
|
|
343
|
+
created_at: '2026-04-16T12:07:00.000Z',
|
|
344
|
+
steps: [{
|
|
345
|
+
id: 1,
|
|
346
|
+
description: 'Write the exporter',
|
|
347
|
+
assignee: 'alpha',
|
|
348
|
+
status: 'in_progress',
|
|
349
|
+
depends_on: [],
|
|
350
|
+
started_at: '2026-04-16T12:07:10.000Z',
|
|
351
|
+
completed_at: null,
|
|
352
|
+
flagged: false,
|
|
353
|
+
verification: null,
|
|
354
|
+
}],
|
|
355
|
+
},
|
|
356
|
+
{
|
|
357
|
+
id: 'wf_report',
|
|
358
|
+
name: 'Completed markdown export',
|
|
359
|
+
status: 'completed',
|
|
360
|
+
autonomous: true,
|
|
361
|
+
parallel: true,
|
|
362
|
+
paused: false,
|
|
363
|
+
created_at: '2026-04-16T12:08:00.000Z',
|
|
364
|
+
completed_at: '2026-04-16T12:09:00.000Z',
|
|
365
|
+
steps: [{
|
|
366
|
+
id: 1,
|
|
367
|
+
description: 'Validate output',
|
|
368
|
+
assignee: 'beta',
|
|
369
|
+
status: 'done',
|
|
370
|
+
depends_on: [],
|
|
371
|
+
started_at: '2026-04-16T12:08:10.000Z',
|
|
372
|
+
completed_at: '2026-04-16T12:08:50.000Z',
|
|
373
|
+
flagged: false,
|
|
374
|
+
verification: { confidence: 90 },
|
|
375
|
+
}],
|
|
376
|
+
},
|
|
377
|
+
]);
|
|
378
|
+
|
|
379
|
+
writeJson(branchPaths.getWorkflowsFile('feature_docs'), [
|
|
380
|
+
{
|
|
381
|
+
id: 'wf_feature_active',
|
|
382
|
+
name: 'Feature branch export follow-up',
|
|
383
|
+
status: 'active',
|
|
384
|
+
autonomous: false,
|
|
385
|
+
parallel: false,
|
|
386
|
+
paused: false,
|
|
387
|
+
created_at: '2026-04-16T12:10:00.000Z',
|
|
388
|
+
steps: [{
|
|
389
|
+
id: 1,
|
|
390
|
+
description: 'Document branch-local plans',
|
|
391
|
+
assignee: 'beta',
|
|
392
|
+
status: 'in_progress',
|
|
393
|
+
depends_on: [],
|
|
394
|
+
started_at: '2026-04-16T12:10:10.000Z',
|
|
395
|
+
completed_at: null,
|
|
396
|
+
flagged: false,
|
|
397
|
+
verification: null,
|
|
398
|
+
}],
|
|
399
|
+
},
|
|
400
|
+
]);
|
|
401
|
+
|
|
402
|
+
const exportRun = spawnSync(process.execPath, [
|
|
403
|
+
EXPORT_SCRIPT,
|
|
404
|
+
'--data-dir',
|
|
405
|
+
dataDir,
|
|
406
|
+
'--output',
|
|
407
|
+
outputRoot,
|
|
408
|
+
], {
|
|
409
|
+
cwd: projectRoot,
|
|
410
|
+
encoding: 'utf8',
|
|
411
|
+
});
|
|
412
|
+
|
|
413
|
+
assert(exportRun.status === 0, `Export script should exit 0. stderr: ${exportRun.stderr || '<empty>'}`, problems);
|
|
414
|
+
|
|
415
|
+
let summary = null;
|
|
416
|
+
try {
|
|
417
|
+
summary = JSON.parse(exportRun.stdout || '{}');
|
|
418
|
+
} catch {
|
|
419
|
+
problems.push('Export script should print JSON summary output.');
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
const expectedFiles = [
|
|
423
|
+
'README.md',
|
|
424
|
+
'branches/index.md',
|
|
425
|
+
'branches/main/metadata.md',
|
|
426
|
+
'branches/main/conversations/index.md',
|
|
427
|
+
'branches/main/conversations/channels/general.md',
|
|
428
|
+
'branches/main/conversations/channels/ops.md',
|
|
429
|
+
'branches/main/decisions/index.md',
|
|
430
|
+
'branches/main/sessions/index.md',
|
|
431
|
+
`branches/main/sessions/${mainSession.session.session_id}.md`,
|
|
432
|
+
'branches/main/evidence/index.md',
|
|
433
|
+
'branches/main/workspaces/index.md',
|
|
434
|
+
'branches/main/workspaces/agents/alpha.md',
|
|
435
|
+
'branches/main/plans/status.md',
|
|
436
|
+
'branches/main/plans/report.md',
|
|
437
|
+
'branches/feature_docs/metadata.md',
|
|
438
|
+
'branches/feature_docs/conversations/index.md',
|
|
439
|
+
'branches/feature_docs/conversations/channels/general.md',
|
|
440
|
+
'branches/feature_docs/conversations/channels/dev.md',
|
|
441
|
+
'branches/feature_docs/decisions/index.md',
|
|
442
|
+
'branches/feature_docs/sessions/index.md',
|
|
443
|
+
`branches/feature_docs/sessions/${featureSession.session.session_id}.md`,
|
|
444
|
+
'branches/feature_docs/workspaces/index.md',
|
|
445
|
+
'branches/feature_docs/workspaces/agents/alpha.md',
|
|
446
|
+
'branches/feature_docs/workspaces/agents/beta.md',
|
|
447
|
+
'branches/feature_docs/plans/status.md',
|
|
448
|
+
'branches/feature_docs/plans/report.md',
|
|
449
|
+
'branches/feature_docs/evidence/index.md',
|
|
450
|
+
'project/notes/project-notes.md',
|
|
451
|
+
'project/notes/team-notes.md',
|
|
452
|
+
];
|
|
453
|
+
|
|
454
|
+
for (const relativePath of expectedFiles) {
|
|
455
|
+
assert(fs.existsSync(path.join(outputRoot, relativePath)), `Expected exported file missing: ${relativePath}`, problems);
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
const readmeFrontmatter = parseFrontmatter(readFile(path.join(outputRoot, 'README.md')));
|
|
459
|
+
const mainConversationFrontmatter = parseFrontmatter(readFile(path.join(outputRoot, 'branches', 'main', 'conversations', 'index.md')));
|
|
460
|
+
const featureMetadataFrontmatter = parseFrontmatter(readFile(path.join(outputRoot, 'branches', 'feature_docs', 'metadata.md')));
|
|
461
|
+
const decisionsFrontmatter = parseFrontmatter(readFile(path.join(outputRoot, 'branches', 'main', 'decisions', 'index.md')));
|
|
462
|
+
const featureDecisionsFrontmatter = parseFrontmatter(readFile(path.join(outputRoot, 'branches', 'feature_docs', 'decisions', 'index.md')));
|
|
463
|
+
const workspaceFrontmatter = parseFrontmatter(readFile(path.join(outputRoot, 'branches', 'main', 'workspaces', 'agents', 'alpha.md')));
|
|
464
|
+
const featureWorkspaceFrontmatter = parseFrontmatter(readFile(path.join(outputRoot, 'branches', 'feature_docs', 'workspaces', 'agents', 'beta.md')));
|
|
465
|
+
const mainPlanStatusFrontmatter = parseFrontmatter(readFile(path.join(outputRoot, 'branches', 'main', 'plans', 'status.md')));
|
|
466
|
+
const mainPlanReportFrontmatter = parseFrontmatter(readFile(path.join(outputRoot, 'branches', 'main', 'plans', 'report.md')));
|
|
467
|
+
const featurePlanStatusFrontmatter = parseFrontmatter(readFile(path.join(outputRoot, 'branches', 'feature_docs', 'plans', 'status.md')));
|
|
468
|
+
const featurePlanReportFrontmatter = parseFrontmatter(readFile(path.join(outputRoot, 'branches', 'feature_docs', 'plans', 'report.md')));
|
|
469
|
+
const projectNoteFrontmatter = parseFrontmatter(readFile(path.join(outputRoot, 'project', 'notes', 'project-notes.md')));
|
|
470
|
+
const teamNoteFrontmatter = parseFrontmatter(readFile(path.join(outputRoot, 'project', 'notes', 'team-notes.md')));
|
|
471
|
+
|
|
472
|
+
assert(readmeFrontmatter && readmeFrontmatter.authoritative === false, 'README frontmatter must mark the markdown workspace non-authoritative.', problems);
|
|
473
|
+
assert(readmeFrontmatter && readmeFrontmatter.generated_by === 'let-them-talk-markdown-export', 'README frontmatter must carry the generator name.', problems);
|
|
474
|
+
assert(mainConversationFrontmatter && mainConversationFrontmatter.source_scope === 'branch_local', 'Conversation index frontmatter must mark branch-local source scope.', problems);
|
|
475
|
+
assert(mainConversationFrontmatter && Number.isInteger(mainConversationFrontmatter.source_sequence) && mainConversationFrontmatter.source_sequence > 0, 'Conversation index frontmatter should record the latest branch event sequence.', problems);
|
|
476
|
+
assert(featureMetadataFrontmatter && featureMetadataFrontmatter.branch_parent === 'main', 'Feature branch metadata frontmatter must carry branch_parent.', problems);
|
|
477
|
+
assert(featureMetadataFrontmatter && featureMetadataFrontmatter.branch_source === 'msg_main_1', 'Feature branch metadata frontmatter must carry branch_source.', problems);
|
|
478
|
+
assert(decisionsFrontmatter && decisionsFrontmatter.source_scope === 'branch_local', 'Decision export frontmatter must mark branch-local scope.', problems);
|
|
479
|
+
assert(decisionsFrontmatter && decisionsFrontmatter.decision_count === 1, 'Decision export frontmatter must report the decision count.', problems);
|
|
480
|
+
assert(featureDecisionsFrontmatter && featureDecisionsFrontmatter.source_scope === 'branch_local', 'Feature decision export frontmatter must mark branch-local scope.', problems);
|
|
481
|
+
assert(featureDecisionsFrontmatter && featureDecisionsFrontmatter.decision_count === 1, 'Feature decision export frontmatter must report the feature decision count.', problems);
|
|
482
|
+
assert(workspaceFrontmatter && workspaceFrontmatter.source_scope === 'branch_local', 'Workspace export frontmatter must mark branch-local scope.', problems);
|
|
483
|
+
assert(workspaceFrontmatter && workspaceFrontmatter.source_surface === 'createCanonicalState().listWorkspaces({ branch })', 'Workspace export frontmatter must name the branch-local workspace read surface.', problems);
|
|
484
|
+
assert(workspaceFrontmatter && workspaceFrontmatter.key_count === 2, 'Workspace agent frontmatter must report the top-level key count.', problems);
|
|
485
|
+
assert(featureWorkspaceFrontmatter && featureWorkspaceFrontmatter.source_scope === 'branch_local', 'Feature workspace export frontmatter must mark branch-local scope.', problems);
|
|
486
|
+
assert(featureWorkspaceFrontmatter && featureWorkspaceFrontmatter.key_count === 1, 'Feature workspace export frontmatter must report the feature branch key count.', problems);
|
|
487
|
+
assert(mainPlanStatusFrontmatter && mainPlanStatusFrontmatter.source_scope === 'branch_local', 'Main plan status frontmatter must mark branch-local scope.', problems);
|
|
488
|
+
assert(mainPlanStatusFrontmatter && mainPlanStatusFrontmatter.source_surface === 'createCanonicalState().getPlanStatusView({ branch })', 'Main plan status frontmatter must name the branch-local source surface.', problems);
|
|
489
|
+
assert(mainPlanStatusFrontmatter && mainPlanStatusFrontmatter.workflow_id === 'wf_active', 'Main plan status frontmatter should record the active branch workflow id when one workflow is present.', problems);
|
|
490
|
+
assert(mainPlanStatusFrontmatter && mainPlanStatusFrontmatter.workflow_status === 'active', 'Main plan status frontmatter should record the active branch workflow status when one workflow is present.', problems);
|
|
491
|
+
assert(mainPlanReportFrontmatter && mainPlanReportFrontmatter.source_scope === 'branch_local', 'Main plan report frontmatter must mark branch-local scope.', problems);
|
|
492
|
+
assert(mainPlanReportFrontmatter && mainPlanReportFrontmatter.workflow_id === null, 'Main plan report frontmatter should leave workflow_id null when the branch report aggregates multiple workflows.', problems);
|
|
493
|
+
assert(featurePlanStatusFrontmatter && featurePlanStatusFrontmatter.source_scope === 'branch_local', 'Feature plan status frontmatter must mark branch-local scope.', problems);
|
|
494
|
+
assert(featurePlanStatusFrontmatter && featurePlanStatusFrontmatter.workflow_id === 'wf_feature_active', 'Feature plan status frontmatter should record the feature branch workflow id.', problems);
|
|
495
|
+
assert(featurePlanReportFrontmatter && featurePlanReportFrontmatter.workflow_id === 'wf_feature_active', 'Feature plan report frontmatter should record the feature branch workflow id when one workflow is present.', problems);
|
|
496
|
+
assert(projectNoteFrontmatter && projectNoteFrontmatter.note_scope === 'branch_local_summary', 'Project note frontmatter must label branch-local summary scope truthfully.', problems);
|
|
497
|
+
assert(projectNoteFrontmatter && projectNoteFrontmatter.source_scope === 'runtime_global', 'Project note frontmatter must describe the cross-branch summary source scope truthfully.', problems);
|
|
498
|
+
assert(teamNoteFrontmatter && teamNoteFrontmatter.note_scope === 'branch_local_summary', 'Team note frontmatter must label branch-local summary scope truthfully.', problems);
|
|
499
|
+
assert(teamNoteFrontmatter && teamNoteFrontmatter.source_scope === 'runtime_global', 'Team note frontmatter must describe the cross-branch summary source scope truthfully.', problems);
|
|
500
|
+
|
|
501
|
+
assert(readFile(path.join(outputRoot, 'branches', 'main', 'conversations', 'channels', 'general.md')).includes('Main branch hello'), 'Main general transcript should include readable general-channel content.', problems);
|
|
502
|
+
assert(readFile(path.join(outputRoot, 'branches', 'main', 'conversations', 'channels', 'ops.md')).includes('Ops-only update'), 'Main ops transcript should include readable non-general channel content.', problems);
|
|
503
|
+
assert(readFile(path.join(outputRoot, 'branches', 'feature_docs', 'conversations', 'channels', 'dev.md')).includes('Feature dev note'), 'Feature branch transcript should include readable feature channel content.', problems);
|
|
504
|
+
assert(readFile(path.join(outputRoot, 'branches', 'feature_docs', 'metadata.md')).includes('- `plans/`'), 'Feature branch metadata should list plans as an exported branch-local page.', problems);
|
|
505
|
+
assert(!readFile(path.join(outputRoot, 'branches', 'feature_docs', 'metadata.md')).includes('plans/ omitted'), 'Feature branch metadata must not describe plans as omitted.', problems);
|
|
506
|
+
assert(readFile(path.join(outputRoot, 'branches', 'feature_docs', 'metadata.md')).includes('- `workspaces/`'), 'Feature branch metadata should list workspaces as an exported branch-local page.', problems);
|
|
507
|
+
assert(!readFile(path.join(outputRoot, 'branches', 'feature_docs', 'metadata.md')).includes('workspaces/ omitted'), 'Feature branch metadata must not describe workspaces as omitted.', problems);
|
|
508
|
+
assert(readFile(path.join(outputRoot, 'branches', 'feature_docs', 'metadata.md')).includes('- `decisions/`'), 'Feature branch metadata should list decisions as an exported branch-local page.', problems);
|
|
509
|
+
assert(readFile(path.join(outputRoot, 'branches', 'main', 'decisions', 'index.md')).includes('Keep markdown export projection-only.'), 'Main decision export should include the main branch decision.', problems);
|
|
510
|
+
assert(readFile(path.join(outputRoot, 'branches', 'feature_docs', 'decisions', 'index.md')).includes('Keep feature export isolated.'), 'Feature decision export should include the feature branch decision.', problems);
|
|
511
|
+
assert(!readFile(path.join(outputRoot, 'branches', 'feature_docs', 'decisions', 'index.md')).includes('Keep markdown export projection-only.'), 'Feature decision export must stay isolated from main-branch decisions.', problems);
|
|
512
|
+
assert(readFile(path.join(outputRoot, 'branches', 'main', 'plans', 'status.md')).includes('Active markdown export'), 'Main plan status should summarize the branch-local active workflow.', problems);
|
|
513
|
+
assert(readFile(path.join(outputRoot, 'branches', 'main', 'plans', 'report.md')).includes('Completed markdown export'), 'Main plan report should summarize completed workflows for the branch.', problems);
|
|
514
|
+
assert(readFile(path.join(outputRoot, 'branches', 'feature_docs', 'plans', 'status.md')).includes('Feature branch export follow-up'), 'Feature plan status should summarize the feature branch workflow.', problems);
|
|
515
|
+
assert(readFile(path.join(outputRoot, 'branches', 'feature_docs', 'plans', 'report.md')).includes('Feature branch export follow-up'), 'Feature plan report should summarize the feature branch workflow.', problems);
|
|
516
|
+
assert(!readFile(path.join(outputRoot, 'branches', 'feature_docs', 'plans', 'status.md')).includes('Active markdown export'), 'Feature plan status must stay isolated from main-branch workflows.', problems);
|
|
517
|
+
assert(!readFile(path.join(outputRoot, 'branches', 'feature_docs', 'plans', 'report.md')).includes('Completed markdown export'), 'Feature plan report must stay isolated from main-branch workflows.', problems);
|
|
518
|
+
assert(readFile(path.join(outputRoot, 'branches', 'feature_docs', 'workspaces', 'agents', 'beta.md')).includes('Feature branch workspace note'), 'Feature workspace export should include readable branch-local workspace content.', problems);
|
|
519
|
+
assert(!readFile(path.join(outputRoot, 'branches', 'feature_docs', 'workspaces', 'agents', 'beta.md')).includes('Main branch workspace note'), 'Feature workspace export must exclude main-branch workspace content.', problems);
|
|
520
|
+
assert(readFile(path.join(outputRoot, 'branches', 'feature_docs', 'evidence', 'index.md')).includes('Validated the feature branch fixture'), 'Evidence index should include readable evidence summaries.', problems);
|
|
521
|
+
assert(readFile(path.join(outputRoot, 'project', 'notes', 'project-notes.md')).includes('## Branch: main'), 'Project note should include the main branch summary.', problems);
|
|
522
|
+
assert(readFile(path.join(outputRoot, 'project', 'notes', 'project-notes.md')).includes('## Branch: feature_docs'), 'Project note should include the feature branch summary.', problems);
|
|
523
|
+
assert(readFile(path.join(outputRoot, 'project', 'notes', 'project-notes.md')).includes('Do not import markdown back into runtime state.'), 'Project note should summarize main-branch rules truthfully.', problems);
|
|
524
|
+
assert(readFile(path.join(outputRoot, 'project', 'notes', 'project-notes.md')).includes('Keep feature governance surfaces branch-local.'), 'Project note should summarize feature-branch rules truthfully.', problems);
|
|
525
|
+
assert(readFile(path.join(outputRoot, 'project', 'notes', 'team-notes.md')).includes('Keep markdown export projection-only.'), 'Team note should summarize main-branch decisions truthfully.', problems);
|
|
526
|
+
assert(readFile(path.join(outputRoot, 'project', 'notes', 'team-notes.md')).includes('Keep feature export isolated.'), 'Team note should summarize feature-branch decisions truthfully.', problems);
|
|
527
|
+
|
|
528
|
+
assert(summary && summary.success === true, 'Export summary should report success.', problems);
|
|
529
|
+
assert(summary && summary.branch_count === 2, 'Export summary should report the exported branch count.', problems);
|
|
530
|
+
assert(summary && Array.isArray(summary.omissions) && summary.omissions.length === 0, 'Export summary should not record governance omissions after the branch-local migration.', problems);
|
|
531
|
+
assert(summary && !summary.omissions.some((entry) => typeof entry.path === 'string' && entry.path.includes('/workspaces/')), 'Export summary must not treat branch-local workspace pages as omissions.', problems);
|
|
532
|
+
assert(summary && !summary.omissions.some((entry) => typeof entry.path === 'string' && entry.path.includes('/plans/')), 'Export summary must not treat branch-local plan pages as omissions.', problems);
|
|
533
|
+
|
|
534
|
+
if (problems.length > 0) {
|
|
535
|
+
fail(['Markdown workspace export validation failed.', ...problems], 1);
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
console.log([
|
|
539
|
+
'Markdown workspace export validation passed.',
|
|
540
|
+
'Validated projection-only markdown export structure, branch-local governance pages, cross-branch note summaries, frontmatter truthfulness, and readable content.',
|
|
541
|
+
`Output root: ${outputRoot}`,
|
|
542
|
+
].join('\n'));
|
|
543
|
+
} finally {
|
|
544
|
+
fs.rmSync(tempRoot, { recursive: true, force: true });
|
|
545
|
+
}
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
main();
|