let-them-talk 5.2.5 → 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 -7214
- 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,951 @@
|
|
|
1
|
+
const fs = require('fs');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
|
|
4
|
+
const {
|
|
5
|
+
DEFAULT_MARKDOWN_WORKSPACE_DIR_NAME,
|
|
6
|
+
isWithinDir,
|
|
7
|
+
} = require('../data-dir');
|
|
8
|
+
|
|
9
|
+
const MARKDOWN_WORKSPACE_SCHEMA = 'markdown-workspace/v1';
|
|
10
|
+
const GENERATED_BY = 'let-them-talk-markdown-export';
|
|
11
|
+
|
|
12
|
+
function ensureDir(dirPath) {
|
|
13
|
+
fs.mkdirSync(dirPath, { recursive: true });
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function toPosixPath(filePath) {
|
|
17
|
+
return filePath.split(path.sep).join('/');
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function yamlScalar(value) {
|
|
21
|
+
if (value === null || value === undefined) return 'null';
|
|
22
|
+
if (typeof value === 'number' || typeof value === 'boolean') return String(value);
|
|
23
|
+
return JSON.stringify(String(value));
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function renderFrontmatter(frontmatter) {
|
|
27
|
+
const lines = ['---'];
|
|
28
|
+
for (const [key, value] of Object.entries(frontmatter)) {
|
|
29
|
+
lines.push(`${key}: ${yamlScalar(value)}`);
|
|
30
|
+
}
|
|
31
|
+
lines.push('---', '');
|
|
32
|
+
return lines.join('\n');
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function renderMarkdown(frontmatter, body) {
|
|
36
|
+
const normalizedBody = typeof body === 'string' ? body.trimEnd() : '';
|
|
37
|
+
return `${renderFrontmatter(frontmatter)}${normalizedBody}\n`;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function writeMarkdownFile(outputRoot, relativePath, frontmatter, body, filesWritten) {
|
|
41
|
+
const absolutePath = path.join(outputRoot, relativePath);
|
|
42
|
+
ensureDir(path.dirname(absolutePath));
|
|
43
|
+
fs.writeFileSync(absolutePath, renderMarkdown(frontmatter, body));
|
|
44
|
+
filesWritten.push(toPosixPath(relativePath));
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function truncate(value, maxLength = 240) {
|
|
48
|
+
const stringValue = String(value == null ? '' : value);
|
|
49
|
+
return stringValue.length > maxLength
|
|
50
|
+
? `${stringValue.slice(0, maxLength - 3)}...`
|
|
51
|
+
: stringValue;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function sortByTimestampDescending(entries, getTimestamp) {
|
|
55
|
+
return [...entries].sort((left, right) => {
|
|
56
|
+
const leftTimestamp = Date.parse(getTimestamp(left) || '') || 0;
|
|
57
|
+
const rightTimestamp = Date.parse(getTimestamp(right) || '') || 0;
|
|
58
|
+
return rightTimestamp - leftTimestamp;
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function buildFrontmatter(options = {}) {
|
|
63
|
+
return {
|
|
64
|
+
ltt_schema: MARKDOWN_WORKSPACE_SCHEMA,
|
|
65
|
+
doc_kind: options.docKind,
|
|
66
|
+
authoritative: false,
|
|
67
|
+
branch: options.branch === undefined ? null : options.branch,
|
|
68
|
+
projection_of: options.projectionOf,
|
|
69
|
+
source_surface: options.sourceSurface,
|
|
70
|
+
source_scope: options.sourceScope,
|
|
71
|
+
source_sequence: options.sourceSequence === undefined ? null : options.sourceSequence,
|
|
72
|
+
generated_at: options.generatedAt,
|
|
73
|
+
generated_by: GENERATED_BY,
|
|
74
|
+
...(options.extra || {}),
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function formatTarget(target) {
|
|
79
|
+
if (Array.isArray(target)) return target.join(', ');
|
|
80
|
+
return target || 'broadcast';
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function groupMessagesByChannel(messages) {
|
|
84
|
+
const grouped = new Map();
|
|
85
|
+
for (const message of messages) {
|
|
86
|
+
const channel = message && typeof message.channel === 'string' && message.channel.trim()
|
|
87
|
+
? message.channel.trim()
|
|
88
|
+
: 'general';
|
|
89
|
+
if (!grouped.has(channel)) grouped.set(channel, []);
|
|
90
|
+
grouped.get(channel).push(message);
|
|
91
|
+
}
|
|
92
|
+
return grouped;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function formatBulletList(items, emptyLine) {
|
|
96
|
+
if (!items || items.length === 0) return `${emptyLine}\n`;
|
|
97
|
+
return `${items.map((item) => `- ${item}`).join('\n')}\n`;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function renderWorkspaceReadme(branches, generatedAt) {
|
|
101
|
+
return [
|
|
102
|
+
'# Let Them Talk markdown workspace',
|
|
103
|
+
'',
|
|
104
|
+
'This workspace is generated from canonical and compatibility read models.',
|
|
105
|
+
'It is non-authoritative and safe to rebuild.',
|
|
106
|
+
'',
|
|
107
|
+
`Generated at: ${generatedAt}`,
|
|
108
|
+
`Known branches: ${branches.length}`,
|
|
109
|
+
'',
|
|
110
|
+
'## Layout',
|
|
111
|
+
'',
|
|
112
|
+
'- `branches/` contains branch metadata plus branch-safe exported surfaces.',
|
|
113
|
+
'- `project/notes/` contains cross-branch summaries built from branch-local governance surfaces.',
|
|
114
|
+
'',
|
|
115
|
+
'Manual edits here do not change runtime state.',
|
|
116
|
+
].join('\n');
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
function renderBranchesIndex(branches) {
|
|
120
|
+
const items = branches.map((branchInfo) => {
|
|
121
|
+
const branch = branchInfo.branch;
|
|
122
|
+
const branchMode = 'includes branch-local governance pages';
|
|
123
|
+
const runtimeNote = branchInfo.runtime_present ? 'runtime present' : 'runtime pending';
|
|
124
|
+
return `[${branch}](./${branch}/metadata.md) , ${runtimeNote} , ${branchMode}`;
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
return [
|
|
128
|
+
'# Branch index',
|
|
129
|
+
'',
|
|
130
|
+
'Known branches resolved from the branch registry plus runtime branch directories.',
|
|
131
|
+
'',
|
|
132
|
+
formatBulletList(items, 'No branches found.').trimEnd(),
|
|
133
|
+
].join('\n');
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
function renderBranchMetadata(branchInfo) {
|
|
137
|
+
return [
|
|
138
|
+
`# Branch metadata: ${branchInfo.branch}`,
|
|
139
|
+
'',
|
|
140
|
+
`- Created at: ${branchInfo.created_at || 'unknown'}`,
|
|
141
|
+
`- Created by: ${branchInfo.created_by || 'unknown'}`,
|
|
142
|
+
`- Parent branch: ${branchInfo.forked_from || 'none'}`,
|
|
143
|
+
`- Source marker: ${branchInfo.branch_source}`,
|
|
144
|
+
`- Registry entry present: ${branchInfo.listed_in_registry ? 'yes' : 'no'}`,
|
|
145
|
+
`- Runtime branch present: ${branchInfo.runtime_present ? 'yes' : 'no'}`,
|
|
146
|
+
`- Message count snapshot: ${branchInfo.message_count == null ? 'unknown' : branchInfo.message_count}`,
|
|
147
|
+
'',
|
|
148
|
+
'## Exported pages',
|
|
149
|
+
'',
|
|
150
|
+
'- `conversations/`',
|
|
151
|
+
'- `sessions/`',
|
|
152
|
+
'- `evidence/`',
|
|
153
|
+
'- `workspaces/`',
|
|
154
|
+
'- `plans/`',
|
|
155
|
+
'- `decisions/`',
|
|
156
|
+
'',
|
|
157
|
+
'## Compatibility omissions',
|
|
158
|
+
'',
|
|
159
|
+
'None.',
|
|
160
|
+
].join('\n');
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
function renderConversationIndex(branch, channelEntries, messageCount) {
|
|
164
|
+
const items = channelEntries.map(({ name, count }) => `[${name}](./channels/${name}.md) , ${count} messages`);
|
|
165
|
+
|
|
166
|
+
return [
|
|
167
|
+
`# Conversations: ${branch}`,
|
|
168
|
+
'',
|
|
169
|
+
`Total messages across visible channels: ${messageCount}`,
|
|
170
|
+
'',
|
|
171
|
+
'## Channels',
|
|
172
|
+
'',
|
|
173
|
+
formatBulletList(items, 'No channels available.').trimEnd(),
|
|
174
|
+
].join('\n');
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
function renderConversationTranscript(branch, channelName, messages, channelInfo) {
|
|
178
|
+
const lines = [
|
|
179
|
+
`# Channel transcript: ${channelName}`,
|
|
180
|
+
'',
|
|
181
|
+
`- Branch: ${branch}`,
|
|
182
|
+
`- Description: ${channelInfo && channelInfo.description ? channelInfo.description : 'none'}`,
|
|
183
|
+
`- Members: ${channelInfo && Array.isArray(channelInfo.members) ? channelInfo.members.join(', ') : 'unknown'}`,
|
|
184
|
+
`- Message count: ${messages.length}`,
|
|
185
|
+
'',
|
|
186
|
+
];
|
|
187
|
+
|
|
188
|
+
if (messages.length === 0) {
|
|
189
|
+
lines.push('No messages recorded for this channel yet.');
|
|
190
|
+
return lines.join('\n');
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
for (const message of messages) {
|
|
194
|
+
lines.push(`## ${message.timestamp || 'unknown-time'} , ${message.from || 'unknown'} -> ${formatTarget(message.to)}`);
|
|
195
|
+
lines.push('');
|
|
196
|
+
lines.push(`Acked: ${message.acked ? 'yes' : 'no'}`);
|
|
197
|
+
lines.push('');
|
|
198
|
+
lines.push(String(message.content || '').trim() || '_No content_');
|
|
199
|
+
lines.push('');
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
return lines.join('\n');
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
function renderDecisionIndex(decisions) {
|
|
206
|
+
const ordered = sortByTimestampDescending(decisions, (entry) => entry && (entry.decided_at || entry.created_at));
|
|
207
|
+
const items = ordered.map((entry) => {
|
|
208
|
+
const topic = entry && entry.topic ? `[${entry.topic}] ` : '';
|
|
209
|
+
const decidedAt = entry && (entry.decided_at || entry.created_at) ? ` (${entry.decided_at || entry.created_at})` : '';
|
|
210
|
+
return `${topic}${truncate(entry && (entry.decision || entry.title || entry.id || 'Untitled decision'))}${decidedAt}`;
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
return [
|
|
214
|
+
'# Decisions',
|
|
215
|
+
'',
|
|
216
|
+
'Branch-local decision summaries exported from the decision log.',
|
|
217
|
+
'',
|
|
218
|
+
formatBulletList(items, 'No decisions recorded.').trimEnd(),
|
|
219
|
+
].join('\n');
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
function renderSessionIndex(branch, sessions) {
|
|
223
|
+
const ordered = sortByTimestampDescending(sessions, (entry) => entry && (entry.updated_at || entry.last_activity_at || entry.started_at));
|
|
224
|
+
const items = ordered.map((session) => `[${session.session_id}](./${session.session_id}.md) , ${session.agent_name || 'unknown'} , ${session.state || 'unknown'}`);
|
|
225
|
+
|
|
226
|
+
return [
|
|
227
|
+
`# Sessions: ${branch}`,
|
|
228
|
+
'',
|
|
229
|
+
'Branch-local session manifests exported from the runtime session read model.',
|
|
230
|
+
'',
|
|
231
|
+
formatBulletList(items, 'No sessions recorded for this branch.').trimEnd(),
|
|
232
|
+
].join('\n');
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
function renderSessionDetail(session) {
|
|
236
|
+
return [
|
|
237
|
+
`# Session ${session.session_id}`,
|
|
238
|
+
'',
|
|
239
|
+
`- Agent: ${session.agent_name || 'unknown'}`,
|
|
240
|
+
`- Provider: ${session.provider || 'unknown'}`,
|
|
241
|
+
`- State: ${session.state || 'unknown'}`,
|
|
242
|
+
`- Created at: ${session.created_at || 'unknown'}`,
|
|
243
|
+
`- Started at: ${session.started_at || 'unknown'}`,
|
|
244
|
+
`- Resumed at: ${session.resumed_at || 'unknown'}`,
|
|
245
|
+
`- Updated at: ${session.updated_at || 'unknown'}`,
|
|
246
|
+
`- Last activity: ${session.last_activity_at || 'unknown'}`,
|
|
247
|
+
`- Ended at: ${session.ended_at || 'not ended'}`,
|
|
248
|
+
`- Resume count: ${Number.isInteger(session.resume_count) ? session.resume_count : 0}`,
|
|
249
|
+
`- Transition reason: ${session.transition_reason || 'unknown'}`,
|
|
250
|
+
`- Recovery snapshot file: ${session.recovery_snapshot_file || 'none'}`,
|
|
251
|
+
'',
|
|
252
|
+
'## Manifest',
|
|
253
|
+
'',
|
|
254
|
+
'```json',
|
|
255
|
+
JSON.stringify(session, null, 2),
|
|
256
|
+
'```',
|
|
257
|
+
].join('\n');
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
function renderEvidenceIndex(records) {
|
|
261
|
+
const ordered = sortByTimestampDescending(records, (entry) => entry && entry.recorded_at);
|
|
262
|
+
const lines = [
|
|
263
|
+
'# Evidence',
|
|
264
|
+
'',
|
|
265
|
+
'Branch-local evidence records exported from the evidence store.',
|
|
266
|
+
'',
|
|
267
|
+
];
|
|
268
|
+
|
|
269
|
+
if (ordered.length === 0) {
|
|
270
|
+
lines.push('No evidence records found.');
|
|
271
|
+
return lines.join('\n');
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
for (const record of ordered) {
|
|
275
|
+
lines.push(`## ${record.recorded_at || 'unknown-time'} , ${record.evidence_id || 'unknown-evidence'}`);
|
|
276
|
+
lines.push('');
|
|
277
|
+
lines.push(`- Summary: ${record.summary || 'none'}`);
|
|
278
|
+
lines.push(`- Confidence: ${record.confidence == null ? 'unknown' : record.confidence}`);
|
|
279
|
+
lines.push(`- Subject kind: ${record.subject_kind || 'unknown'}`);
|
|
280
|
+
lines.push(`- Files changed: ${Array.isArray(record.files_changed) && record.files_changed.length > 0 ? record.files_changed.join(', ') : 'none'}`);
|
|
281
|
+
lines.push(`- Recorded by session: ${record.recorded_by_session || 'none'}`);
|
|
282
|
+
lines.push('');
|
|
283
|
+
lines.push(record.verification || 'No verification note recorded.');
|
|
284
|
+
lines.push('');
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
return lines.join('\n');
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
function renderWorkspaceIndex(workspaces) {
|
|
291
|
+
const items = workspaces.map((workspace) => `[${workspace.agent}](./agents/${workspace.agent}.md) , ${workspace.key_count} keys`);
|
|
292
|
+
return [
|
|
293
|
+
'# Workspaces',
|
|
294
|
+
'',
|
|
295
|
+
'Branch-local agent workspaces exported as generated notes.',
|
|
296
|
+
'',
|
|
297
|
+
formatBulletList(items, 'No workspace files found.').trimEnd(),
|
|
298
|
+
].join('\n');
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
function renderWorkspaceAgent(workspace) {
|
|
302
|
+
const lines = [
|
|
303
|
+
`# Workspace: ${workspace.agent}`,
|
|
304
|
+
'',
|
|
305
|
+
`Top-level keys: ${workspace.key_count}`,
|
|
306
|
+
'',
|
|
307
|
+
];
|
|
308
|
+
|
|
309
|
+
const keys = Object.keys(workspace.data || {}).sort((left, right) => left.localeCompare(right));
|
|
310
|
+
if (keys.length === 0) {
|
|
311
|
+
lines.push('No workspace keys recorded.');
|
|
312
|
+
return lines.join('\n');
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
for (const key of keys) {
|
|
316
|
+
const value = workspace.data[key];
|
|
317
|
+
lines.push(`## ${key}`);
|
|
318
|
+
lines.push('');
|
|
319
|
+
lines.push('```json');
|
|
320
|
+
lines.push(truncate(JSON.stringify(value, null, 2), 1200));
|
|
321
|
+
lines.push('```');
|
|
322
|
+
lines.push('');
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
return lines.join('\n');
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
function getPlanViewWorkflows(view) {
|
|
329
|
+
return view && Array.isArray(view.workflows) ? view.workflows : [];
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
function getPlanViewPrimaryWorkflow(view) {
|
|
333
|
+
const workflows = getPlanViewWorkflows(view);
|
|
334
|
+
return workflows.length === 1 ? workflows[0] : null;
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
function countCompletedPlanSteps(steps) {
|
|
338
|
+
return (Array.isArray(steps) ? steps : []).filter((step) => {
|
|
339
|
+
const status = step && typeof step.status === 'string' ? step.status : '';
|
|
340
|
+
return status === 'done' || status === 'completed';
|
|
341
|
+
}).length;
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
function renderPlanStatus(view) {
|
|
345
|
+
const workflows = getPlanViewWorkflows(view);
|
|
346
|
+
if (workflows.length === 0) {
|
|
347
|
+
return [
|
|
348
|
+
'# Plan status',
|
|
349
|
+
'',
|
|
350
|
+
'No active or paused workflows for this branch.',
|
|
351
|
+
].join('\n');
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
const lines = [
|
|
355
|
+
'# Plan status',
|
|
356
|
+
'',
|
|
357
|
+
`- Active or paused workflows: ${workflows.length}`,
|
|
358
|
+
'',
|
|
359
|
+
'## Workflows',
|
|
360
|
+
'',
|
|
361
|
+
];
|
|
362
|
+
|
|
363
|
+
for (const workflow of workflows) {
|
|
364
|
+
const steps = Array.isArray(workflow.steps) ? workflow.steps : [];
|
|
365
|
+
lines.push(`### ${workflow.name || workflow.id || 'Unknown workflow'}`);
|
|
366
|
+
lines.push('');
|
|
367
|
+
lines.push(`- Workflow ID: ${workflow.workflow_id || workflow.id || 'unknown'}`);
|
|
368
|
+
lines.push(`- Status: ${workflow.status || 'unknown'}`);
|
|
369
|
+
lines.push(`- Progress: ${countCompletedPlanSteps(steps)}/${steps.length}`);
|
|
370
|
+
lines.push(`- Autonomous: ${workflow.autonomous === true ? 'yes' : 'no'}`);
|
|
371
|
+
lines.push(`- Parallel: ${workflow.parallel === true ? 'yes' : 'no'}`);
|
|
372
|
+
lines.push('');
|
|
373
|
+
lines.push('#### Steps');
|
|
374
|
+
lines.push('');
|
|
375
|
+
if (steps.length > 0) {
|
|
376
|
+
lines.push(...steps.map((step) => `- #${step.id} ${step.description} , ${step.status || 'unknown'} , ${step.assignee || 'unassigned'}`));
|
|
377
|
+
} else {
|
|
378
|
+
lines.push('No steps available.');
|
|
379
|
+
}
|
|
380
|
+
lines.push('');
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
return lines.join('\n').trimEnd();
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
function renderPlanReport(view) {
|
|
387
|
+
const workflows = getPlanViewWorkflows(view);
|
|
388
|
+
if (workflows.length === 0) {
|
|
389
|
+
return [
|
|
390
|
+
'# Plan report',
|
|
391
|
+
'',
|
|
392
|
+
'No workflow report is available for this branch.',
|
|
393
|
+
].join('\n');
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
const totals = view && view.totals && typeof view.totals === 'object' ? view.totals : {};
|
|
397
|
+
const lines = [
|
|
398
|
+
'# Plan report',
|
|
399
|
+
'',
|
|
400
|
+
`- Workflows: ${totals.workflows == null ? workflows.length : totals.workflows}`,
|
|
401
|
+
`- Active workflows: ${totals.active_workflows == null ? workflows.filter((workflow) => workflow && workflow.status === 'active').length : totals.active_workflows}`,
|
|
402
|
+
`- Completed workflows: ${totals.completed_workflows == null ? workflows.filter((workflow) => workflow && workflow.status === 'completed').length : totals.completed_workflows}`,
|
|
403
|
+
`- Paused workflows: ${totals.paused_workflows == null ? workflows.filter((workflow) => workflow && workflow.status === 'paused').length : totals.paused_workflows}`,
|
|
404
|
+
`- Generated at: ${view && view.generated_at ? view.generated_at : 'unknown'}`,
|
|
405
|
+
'',
|
|
406
|
+
'## Workflows',
|
|
407
|
+
'',
|
|
408
|
+
];
|
|
409
|
+
|
|
410
|
+
for (const workflow of workflows) {
|
|
411
|
+
const steps = Array.isArray(workflow.steps) ? workflow.steps : [];
|
|
412
|
+
const flagged = steps.filter((step) => step && step.flagged === true);
|
|
413
|
+
lines.push(`### ${workflow.name || workflow.id || 'Unknown workflow'}`);
|
|
414
|
+
lines.push('');
|
|
415
|
+
lines.push(`- Workflow ID: ${workflow.workflow_id || workflow.id || 'unknown'}`);
|
|
416
|
+
lines.push(`- Status: ${workflow.status || 'unknown'}`);
|
|
417
|
+
lines.push(`- Progress: ${countCompletedPlanSteps(steps)}/${steps.length}`);
|
|
418
|
+
lines.push(`- Completed at: ${workflow.completed_at || 'not completed'}`);
|
|
419
|
+
lines.push('');
|
|
420
|
+
lines.push('#### Flagged steps');
|
|
421
|
+
lines.push('');
|
|
422
|
+
if (flagged.length > 0) {
|
|
423
|
+
lines.push(...flagged.map((step) => `- #${step.id} ${step.description} , ${step.reason || 'no reason recorded'}`));
|
|
424
|
+
} else {
|
|
425
|
+
lines.push('No flagged steps.');
|
|
426
|
+
}
|
|
427
|
+
lines.push('');
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
return lines.join('\n').trimEnd();
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
function renderProjectNote(view) {
|
|
434
|
+
const branches = Array.isArray(view && view.branches) ? view.branches : [];
|
|
435
|
+
const lines = [
|
|
436
|
+
'# Project notes',
|
|
437
|
+
'',
|
|
438
|
+
'Cross-branch summary of branch-local project guidance surfaces.',
|
|
439
|
+
'',
|
|
440
|
+
`- Branches summarized: ${branches.length}`,
|
|
441
|
+
'',
|
|
442
|
+
];
|
|
443
|
+
|
|
444
|
+
if (branches.length === 0) {
|
|
445
|
+
lines.push('No branch-local project notes found.');
|
|
446
|
+
return lines.join('\n');
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
for (const entry of branches) {
|
|
450
|
+
const kbEntries = Object.entries(entry.view && entry.view.knowledge_base ? entry.view.knowledge_base : {}).sort(([left], [right]) => left.localeCompare(right));
|
|
451
|
+
const progressEntries = Object.entries(entry.view && entry.view.progress ? entry.view.progress : {}).sort(([left], [right]) => left.localeCompare(right));
|
|
452
|
+
const rules = entry.view && Array.isArray(entry.view.rules) ? entry.view.rules : [];
|
|
453
|
+
lines.push(`## Branch: ${entry.branch}`);
|
|
454
|
+
lines.push('');
|
|
455
|
+
lines.push(`- Knowledge base entries: ${kbEntries.length}`);
|
|
456
|
+
lines.push(`- Rules: ${rules.length}`);
|
|
457
|
+
lines.push(`- Progress entries: ${progressEntries.length}`);
|
|
458
|
+
lines.push('');
|
|
459
|
+
lines.push('### Rules');
|
|
460
|
+
lines.push('');
|
|
461
|
+
lines.push(formatBulletList(rules.map((rule) => `${rule.active === false ? '[inactive] ' : ''}${truncate(rule.text || rule.id || 'Untitled rule')}`), 'No rules found.').trimEnd());
|
|
462
|
+
lines.push('');
|
|
463
|
+
lines.push('### Progress');
|
|
464
|
+
lines.push('');
|
|
465
|
+
lines.push(formatBulletList(progressEntries.map(([key, value]) => {
|
|
466
|
+
if (value && typeof value === 'object' && !Array.isArray(value)) {
|
|
467
|
+
const percent = value.percent == null ? 'unknown' : `${value.percent}%`;
|
|
468
|
+
const notes = value.notes ? ` , ${truncate(value.notes)}` : '';
|
|
469
|
+
return `${key}: ${percent}${notes}`;
|
|
470
|
+
}
|
|
471
|
+
return `${key}: ${truncate(value)}`;
|
|
472
|
+
}), 'No progress entries found.').trimEnd());
|
|
473
|
+
lines.push('');
|
|
474
|
+
lines.push('### Knowledge base');
|
|
475
|
+
lines.push('');
|
|
476
|
+
lines.push(formatBulletList(kbEntries.map(([key, value]) => `${key}: ${truncate(value && value.content ? value.content : value)}`), 'No knowledge base entries found.').trimEnd());
|
|
477
|
+
lines.push('');
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
return lines.join('\n').trimEnd();
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
function renderTeamNote(view) {
|
|
484
|
+
const branches = Array.isArray(view && view.branches) ? view.branches : [];
|
|
485
|
+
const lines = [
|
|
486
|
+
'# Team notes',
|
|
487
|
+
'',
|
|
488
|
+
'Cross-branch summary of branch-local collaboration governance surfaces.',
|
|
489
|
+
'',
|
|
490
|
+
`- Branches summarized: ${branches.length}`,
|
|
491
|
+
'',
|
|
492
|
+
];
|
|
493
|
+
|
|
494
|
+
if (branches.length === 0) {
|
|
495
|
+
lines.push('No branch-local team notes found.');
|
|
496
|
+
return lines.join('\n');
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
for (const entry of branches) {
|
|
500
|
+
const decisions = Array.isArray(entry.view && entry.view.decisions) ? entry.view.decisions : [];
|
|
501
|
+
const reviews = Array.isArray(entry.view && entry.view.reviews) ? entry.view.reviews : [];
|
|
502
|
+
const dependencies = Array.isArray(entry.view && entry.view.dependencies) ? entry.view.dependencies : [];
|
|
503
|
+
const votes = Array.isArray(entry.view && entry.view.votes) ? entry.view.votes : [];
|
|
504
|
+
lines.push(`## Branch: ${entry.branch}`);
|
|
505
|
+
lines.push('');
|
|
506
|
+
lines.push(`- Decisions: ${decisions.length}`);
|
|
507
|
+
lines.push(`- Reviews: ${reviews.length}`);
|
|
508
|
+
lines.push(`- Dependencies: ${dependencies.length}`);
|
|
509
|
+
lines.push(`- Votes: ${votes.length}`);
|
|
510
|
+
lines.push('');
|
|
511
|
+
lines.push('### Decisions');
|
|
512
|
+
lines.push('');
|
|
513
|
+
lines.push(formatBulletList(decisions.map((decision) => truncate(decision.decision || decision.title || decision.id || 'Untitled decision')), 'No decisions found.').trimEnd());
|
|
514
|
+
lines.push('');
|
|
515
|
+
lines.push('### Reviews');
|
|
516
|
+
lines.push('');
|
|
517
|
+
lines.push(formatBulletList(reviews.map((review) => `${review.status || 'unknown'}: ${truncate(review.file_path || review.file || review.id || 'unknown review')}`), 'No reviews found.').trimEnd());
|
|
518
|
+
lines.push('');
|
|
519
|
+
lines.push('### Dependencies');
|
|
520
|
+
lines.push('');
|
|
521
|
+
lines.push(formatBulletList(dependencies.map((dependency) => `${dependency.task_id || dependency.id || 'unknown task'} -> ${dependency.depends_on || dependency.dependsOn || 'unknown dependency'}`), 'No dependencies found.').trimEnd());
|
|
522
|
+
lines.push('');
|
|
523
|
+
lines.push('### Votes');
|
|
524
|
+
lines.push('');
|
|
525
|
+
lines.push(formatBulletList(votes.map((vote) => `${truncate(vote.question || vote.id || 'Untitled vote')} , ${vote.status || 'unknown'}`), 'No votes found.').trimEnd());
|
|
526
|
+
lines.push('');
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
return lines.join('\n').trimEnd();
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
function resolveBranchSource(branchInfo) {
|
|
533
|
+
if (branchInfo.branch === 'main') return 'root';
|
|
534
|
+
if (branchInfo.fork_point) return branchInfo.fork_point;
|
|
535
|
+
if (branchInfo.forked_from) return 'fork';
|
|
536
|
+
if (branchInfo.runtime_present && !branchInfo.listed_in_registry) return 'runtime-directory';
|
|
537
|
+
return 'registry';
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
function assertSafeOutputRoot(options = {}) {
|
|
541
|
+
const {
|
|
542
|
+
projectRoot,
|
|
543
|
+
outputRoot,
|
|
544
|
+
runtimeDataDir,
|
|
545
|
+
} = options;
|
|
546
|
+
|
|
547
|
+
const resolvedProjectRoot = path.resolve(projectRoot);
|
|
548
|
+
const resolvedOutputRoot = path.resolve(outputRoot);
|
|
549
|
+
|
|
550
|
+
if (resolvedOutputRoot === resolvedProjectRoot) {
|
|
551
|
+
throw new Error('Markdown workspace output root must not be the project root.');
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
if (isWithinDir(resolvedOutputRoot, resolvedProjectRoot)) {
|
|
555
|
+
throw new Error('Markdown workspace output root must not contain the project root.');
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
if (runtimeDataDir) {
|
|
559
|
+
const resolvedRuntimeDataDir = path.resolve(runtimeDataDir);
|
|
560
|
+
if (isWithinDir(resolvedRuntimeDataDir, resolvedOutputRoot)) {
|
|
561
|
+
throw new Error('Markdown workspace output root must stay outside the canonical runtime data directory.');
|
|
562
|
+
}
|
|
563
|
+
if (isWithinDir(resolvedOutputRoot, resolvedRuntimeDataDir)) {
|
|
564
|
+
throw new Error('Markdown workspace output root must not contain the canonical runtime data directory.');
|
|
565
|
+
}
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
return {
|
|
569
|
+
resolvedProjectRoot,
|
|
570
|
+
resolvedOutputRoot,
|
|
571
|
+
};
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
function exportMarkdownWorkspace(options = {}) {
|
|
575
|
+
const {
|
|
576
|
+
projectRoot,
|
|
577
|
+
outputRoot,
|
|
578
|
+
generatedAt = new Date().toISOString(),
|
|
579
|
+
branches = null,
|
|
580
|
+
readModel,
|
|
581
|
+
runtimeDataDir = null,
|
|
582
|
+
} = options;
|
|
583
|
+
|
|
584
|
+
if (!readModel) throw new Error('exportMarkdownWorkspace requires readModel');
|
|
585
|
+
if (!projectRoot) throw new Error('exportMarkdownWorkspace requires projectRoot');
|
|
586
|
+
if (!outputRoot) throw new Error('exportMarkdownWorkspace requires outputRoot');
|
|
587
|
+
|
|
588
|
+
const { resolvedOutputRoot } = assertSafeOutputRoot({
|
|
589
|
+
projectRoot,
|
|
590
|
+
outputRoot,
|
|
591
|
+
runtimeDataDir,
|
|
592
|
+
});
|
|
593
|
+
|
|
594
|
+
const branchRegistry = (branches || readModel.listBranches())
|
|
595
|
+
.map((entry) => ({
|
|
596
|
+
...entry,
|
|
597
|
+
branch_source: resolveBranchSource(entry),
|
|
598
|
+
}));
|
|
599
|
+
const branchProjectNotes = [];
|
|
600
|
+
const branchTeamNotes = [];
|
|
601
|
+
const filesWritten = [];
|
|
602
|
+
const omissions = [];
|
|
603
|
+
|
|
604
|
+
fs.rmSync(resolvedOutputRoot, { recursive: true, force: true });
|
|
605
|
+
ensureDir(resolvedOutputRoot);
|
|
606
|
+
|
|
607
|
+
writeMarkdownFile(
|
|
608
|
+
resolvedOutputRoot,
|
|
609
|
+
'README.md',
|
|
610
|
+
buildFrontmatter({
|
|
611
|
+
docKind: 'workspace-readme',
|
|
612
|
+
branch: null,
|
|
613
|
+
projectionOf: 'markdown-workspace',
|
|
614
|
+
sourceSurface: 'createCanonicalState().exportMarkdownWorkspace()',
|
|
615
|
+
sourceScope: 'runtime_global',
|
|
616
|
+
sourceSequence: null,
|
|
617
|
+
generatedAt,
|
|
618
|
+
}),
|
|
619
|
+
renderWorkspaceReadme(branchRegistry, generatedAt),
|
|
620
|
+
filesWritten
|
|
621
|
+
);
|
|
622
|
+
|
|
623
|
+
writeMarkdownFile(
|
|
624
|
+
resolvedOutputRoot,
|
|
625
|
+
path.join('branches', 'index.md'),
|
|
626
|
+
buildFrontmatter({
|
|
627
|
+
docKind: 'branch-index',
|
|
628
|
+
branch: null,
|
|
629
|
+
projectionOf: 'branch-registry',
|
|
630
|
+
sourceSurface: 'createCanonicalState().listMarkdownBranches()',
|
|
631
|
+
sourceScope: 'runtime_global',
|
|
632
|
+
sourceSequence: null,
|
|
633
|
+
generatedAt,
|
|
634
|
+
}),
|
|
635
|
+
renderBranchesIndex(branchRegistry),
|
|
636
|
+
filesWritten
|
|
637
|
+
);
|
|
638
|
+
|
|
639
|
+
for (const branchInfo of branchRegistry) {
|
|
640
|
+
const branch = branchInfo.branch;
|
|
641
|
+
const branchSequence = readModel.getBranchEventSequence(branch);
|
|
642
|
+
const messages = readModel.getConversationMessages({ branch });
|
|
643
|
+
const channelsView = readModel.getChannelsView({ branch }) || {};
|
|
644
|
+
const groupedMessages = groupMessagesByChannel(messages);
|
|
645
|
+
const channelNames = new Set(['general']);
|
|
646
|
+
for (const channelName of Object.keys(channelsView)) channelNames.add(channelName);
|
|
647
|
+
for (const channelName of groupedMessages.keys()) channelNames.add(channelName);
|
|
648
|
+
const orderedChannelNames = [...channelNames].sort((left, right) => {
|
|
649
|
+
if (left === right) return 0;
|
|
650
|
+
if (left === 'general') return -1;
|
|
651
|
+
if (right === 'general') return 1;
|
|
652
|
+
return left.localeCompare(right);
|
|
653
|
+
});
|
|
654
|
+
const channelEntries = orderedChannelNames.map((channelName) => ({
|
|
655
|
+
name: channelName,
|
|
656
|
+
count: (groupedMessages.get(channelName) || []).length,
|
|
657
|
+
}));
|
|
658
|
+
const sessions = readModel.listBranchSessions(branch) || [];
|
|
659
|
+
const evidenceStore = readModel.readEvidence(branch) || { records: [] };
|
|
660
|
+
const evidenceRecords = Array.isArray(evidenceStore.records) ? evidenceStore.records : [];
|
|
661
|
+
const decisions = readModel.listDecisions ? (readModel.listDecisions({ branch }) || []) : [];
|
|
662
|
+
const projectNotes = readModel.getProjectNotesView ? (readModel.getProjectNotesView({ branch }) || {}) : {};
|
|
663
|
+
const teamNotes = readModel.getTeamNotesView ? (readModel.getTeamNotesView({ branch }) || {}) : {};
|
|
664
|
+
const planStatus = readModel.getPlanStatusView ? readModel.getPlanStatusView({ branch }) : null;
|
|
665
|
+
const planReport = readModel.getPlanReportView ? readModel.getPlanReportView({ branch }) : null;
|
|
666
|
+
const primaryPlanStatusWorkflow = getPlanViewPrimaryWorkflow(planStatus);
|
|
667
|
+
const primaryPlanReportWorkflow = getPlanViewPrimaryWorkflow(planReport);
|
|
668
|
+
|
|
669
|
+
branchProjectNotes.push({ branch, view: projectNotes });
|
|
670
|
+
branchTeamNotes.push({ branch, view: teamNotes });
|
|
671
|
+
|
|
672
|
+
writeMarkdownFile(
|
|
673
|
+
resolvedOutputRoot,
|
|
674
|
+
path.join('branches', branch, 'metadata.md'),
|
|
675
|
+
buildFrontmatter({
|
|
676
|
+
docKind: 'branch-metadata',
|
|
677
|
+
branch,
|
|
678
|
+
projectionOf: 'branch-metadata',
|
|
679
|
+
sourceSurface: 'createCanonicalState().listMarkdownBranches()',
|
|
680
|
+
sourceScope: 'runtime_global',
|
|
681
|
+
sourceSequence: null,
|
|
682
|
+
generatedAt,
|
|
683
|
+
extra: {
|
|
684
|
+
branch_parent: branchInfo.forked_from || null,
|
|
685
|
+
branch_source: branchInfo.branch_source,
|
|
686
|
+
},
|
|
687
|
+
}),
|
|
688
|
+
renderBranchMetadata(branchInfo),
|
|
689
|
+
filesWritten
|
|
690
|
+
);
|
|
691
|
+
|
|
692
|
+
writeMarkdownFile(
|
|
693
|
+
resolvedOutputRoot,
|
|
694
|
+
path.join('branches', branch, 'conversations', 'index.md'),
|
|
695
|
+
buildFrontmatter({
|
|
696
|
+
docKind: 'conversation-index',
|
|
697
|
+
branch,
|
|
698
|
+
projectionOf: 'conversation-history',
|
|
699
|
+
sourceSurface: 'createCanonicalState().getConversationMessages() + createCanonicalState().getChannelsView()',
|
|
700
|
+
sourceScope: 'branch_local',
|
|
701
|
+
sourceSequence: branchSequence,
|
|
702
|
+
generatedAt,
|
|
703
|
+
extra: {
|
|
704
|
+
channel_count: channelEntries.length,
|
|
705
|
+
message_count: messages.length,
|
|
706
|
+
},
|
|
707
|
+
}),
|
|
708
|
+
renderConversationIndex(branch, channelEntries, messages.length),
|
|
709
|
+
filesWritten
|
|
710
|
+
);
|
|
711
|
+
|
|
712
|
+
for (const channelName of orderedChannelNames) {
|
|
713
|
+
writeMarkdownFile(
|
|
714
|
+
resolvedOutputRoot,
|
|
715
|
+
path.join('branches', branch, 'conversations', 'channels', `${channelName}.md`),
|
|
716
|
+
buildFrontmatter({
|
|
717
|
+
docKind: 'conversation-transcript',
|
|
718
|
+
branch,
|
|
719
|
+
projectionOf: 'conversation-channel-transcript',
|
|
720
|
+
sourceSurface: 'createCanonicalState().getConversationMessages() + createCanonicalState().getChannelsView()',
|
|
721
|
+
sourceScope: 'branch_local',
|
|
722
|
+
sourceSequence: branchSequence,
|
|
723
|
+
generatedAt,
|
|
724
|
+
extra: {
|
|
725
|
+
channel: channelName,
|
|
726
|
+
message_count: (groupedMessages.get(channelName) || []).length,
|
|
727
|
+
},
|
|
728
|
+
}),
|
|
729
|
+
renderConversationTranscript(branch, channelName, groupedMessages.get(channelName) || [], channelsView[channelName]),
|
|
730
|
+
filesWritten
|
|
731
|
+
);
|
|
732
|
+
}
|
|
733
|
+
|
|
734
|
+
writeMarkdownFile(
|
|
735
|
+
resolvedOutputRoot,
|
|
736
|
+
path.join('branches', branch, 'sessions', 'index.md'),
|
|
737
|
+
buildFrontmatter({
|
|
738
|
+
docKind: 'session-index',
|
|
739
|
+
branch,
|
|
740
|
+
projectionOf: 'session-manifest-index',
|
|
741
|
+
sourceSurface: 'createCanonicalState().listBranchSessions()',
|
|
742
|
+
sourceScope: 'branch_local',
|
|
743
|
+
sourceSequence: branchSequence,
|
|
744
|
+
generatedAt,
|
|
745
|
+
extra: {
|
|
746
|
+
session_count: sessions.length,
|
|
747
|
+
},
|
|
748
|
+
}),
|
|
749
|
+
renderSessionIndex(branch, sessions),
|
|
750
|
+
filesWritten
|
|
751
|
+
);
|
|
752
|
+
|
|
753
|
+
for (const session of sessions) {
|
|
754
|
+
const sessionManifest = readModel.getBranchSessionManifest(session.session_id, branch) || session;
|
|
755
|
+
writeMarkdownFile(
|
|
756
|
+
resolvedOutputRoot,
|
|
757
|
+
path.join('branches', branch, 'sessions', `${session.session_id}.md`),
|
|
758
|
+
buildFrontmatter({
|
|
759
|
+
docKind: 'session-detail',
|
|
760
|
+
branch,
|
|
761
|
+
projectionOf: 'session-manifest',
|
|
762
|
+
sourceSurface: 'createCanonicalState().getBranchSessionManifest()',
|
|
763
|
+
sourceScope: 'branch_local',
|
|
764
|
+
sourceSequence: branchSequence,
|
|
765
|
+
generatedAt,
|
|
766
|
+
extra: {
|
|
767
|
+
session_id: session.session_id,
|
|
768
|
+
session_state: session.state || sessionManifest.state || null,
|
|
769
|
+
},
|
|
770
|
+
}),
|
|
771
|
+
renderSessionDetail(sessionManifest),
|
|
772
|
+
filesWritten
|
|
773
|
+
);
|
|
774
|
+
}
|
|
775
|
+
|
|
776
|
+
writeMarkdownFile(
|
|
777
|
+
resolvedOutputRoot,
|
|
778
|
+
path.join('branches', branch, 'plans', 'status.md'),
|
|
779
|
+
buildFrontmatter({
|
|
780
|
+
docKind: 'plan-status',
|
|
781
|
+
branch,
|
|
782
|
+
projectionOf: 'plan-status-view',
|
|
783
|
+
sourceSurface: 'createCanonicalState().getPlanStatusView({ branch })',
|
|
784
|
+
sourceScope: 'branch_local',
|
|
785
|
+
sourceSequence: null,
|
|
786
|
+
generatedAt,
|
|
787
|
+
extra: {
|
|
788
|
+
workflow_id: primaryPlanStatusWorkflow && (primaryPlanStatusWorkflow.workflow_id || primaryPlanStatusWorkflow.id) ? (primaryPlanStatusWorkflow.workflow_id || primaryPlanStatusWorkflow.id) : null,
|
|
789
|
+
workflow_status: primaryPlanStatusWorkflow && primaryPlanStatusWorkflow.status ? primaryPlanStatusWorkflow.status : null,
|
|
790
|
+
},
|
|
791
|
+
}),
|
|
792
|
+
renderPlanStatus(planStatus),
|
|
793
|
+
filesWritten
|
|
794
|
+
);
|
|
795
|
+
|
|
796
|
+
writeMarkdownFile(
|
|
797
|
+
resolvedOutputRoot,
|
|
798
|
+
path.join('branches', branch, 'plans', 'report.md'),
|
|
799
|
+
buildFrontmatter({
|
|
800
|
+
docKind: 'plan-report',
|
|
801
|
+
branch,
|
|
802
|
+
projectionOf: 'plan-report-view',
|
|
803
|
+
sourceSurface: 'createCanonicalState().getPlanReportView({ branch })',
|
|
804
|
+
sourceScope: 'branch_local',
|
|
805
|
+
sourceSequence: null,
|
|
806
|
+
generatedAt,
|
|
807
|
+
extra: {
|
|
808
|
+
workflow_id: primaryPlanReportWorkflow && (primaryPlanReportWorkflow.workflow_id || primaryPlanReportWorkflow.id) ? (primaryPlanReportWorkflow.workflow_id || primaryPlanReportWorkflow.id) : null,
|
|
809
|
+
workflow_status: primaryPlanReportWorkflow && primaryPlanReportWorkflow.status ? primaryPlanReportWorkflow.status : null,
|
|
810
|
+
},
|
|
811
|
+
}),
|
|
812
|
+
renderPlanReport(planReport),
|
|
813
|
+
filesWritten
|
|
814
|
+
);
|
|
815
|
+
|
|
816
|
+
writeMarkdownFile(
|
|
817
|
+
resolvedOutputRoot,
|
|
818
|
+
path.join('branches', branch, 'evidence', 'index.md'),
|
|
819
|
+
buildFrontmatter({
|
|
820
|
+
docKind: 'evidence-index',
|
|
821
|
+
branch,
|
|
822
|
+
projectionOf: 'evidence-records',
|
|
823
|
+
sourceSurface: 'createCanonicalState().readEvidence()',
|
|
824
|
+
sourceScope: 'branch_local',
|
|
825
|
+
sourceSequence: branchSequence,
|
|
826
|
+
generatedAt,
|
|
827
|
+
extra: {
|
|
828
|
+
evidence_count: evidenceRecords.length,
|
|
829
|
+
},
|
|
830
|
+
}),
|
|
831
|
+
renderEvidenceIndex(evidenceRecords),
|
|
832
|
+
filesWritten
|
|
833
|
+
);
|
|
834
|
+
|
|
835
|
+
const workspaces = readModel.listWorkspaces ? (readModel.listWorkspaces({ branch }) || []) : [];
|
|
836
|
+
|
|
837
|
+
writeMarkdownFile(
|
|
838
|
+
resolvedOutputRoot,
|
|
839
|
+
path.join('branches', branch, 'workspaces', 'index.md'),
|
|
840
|
+
buildFrontmatter({
|
|
841
|
+
docKind: 'workspace-index',
|
|
842
|
+
branch,
|
|
843
|
+
projectionOf: 'workspace-directory',
|
|
844
|
+
sourceSurface: 'createCanonicalState().listWorkspaces({ branch })',
|
|
845
|
+
sourceScope: 'branch_local',
|
|
846
|
+
sourceSequence: branchSequence,
|
|
847
|
+
generatedAt,
|
|
848
|
+
extra: {
|
|
849
|
+
agent_count: workspaces.length,
|
|
850
|
+
},
|
|
851
|
+
}),
|
|
852
|
+
renderWorkspaceIndex(workspaces),
|
|
853
|
+
filesWritten
|
|
854
|
+
);
|
|
855
|
+
|
|
856
|
+
for (const workspace of workspaces) {
|
|
857
|
+
writeMarkdownFile(
|
|
858
|
+
resolvedOutputRoot,
|
|
859
|
+
path.join('branches', branch, 'workspaces', 'agents', `${workspace.agent}.md`),
|
|
860
|
+
buildFrontmatter({
|
|
861
|
+
docKind: 'workspace-agent',
|
|
862
|
+
branch,
|
|
863
|
+
projectionOf: 'workspace-agent-state',
|
|
864
|
+
sourceSurface: 'createCanonicalState().listWorkspaces({ branch })',
|
|
865
|
+
sourceScope: 'branch_local',
|
|
866
|
+
sourceSequence: branchSequence,
|
|
867
|
+
generatedAt,
|
|
868
|
+
extra: {
|
|
869
|
+
agent: workspace.agent,
|
|
870
|
+
key_count: workspace.key_count,
|
|
871
|
+
},
|
|
872
|
+
}),
|
|
873
|
+
renderWorkspaceAgent(workspace),
|
|
874
|
+
filesWritten
|
|
875
|
+
);
|
|
876
|
+
}
|
|
877
|
+
|
|
878
|
+
writeMarkdownFile(
|
|
879
|
+
resolvedOutputRoot,
|
|
880
|
+
path.join('branches', branch, 'decisions', 'index.md'),
|
|
881
|
+
buildFrontmatter({
|
|
882
|
+
docKind: 'decision-index',
|
|
883
|
+
branch,
|
|
884
|
+
projectionOf: 'decision-log',
|
|
885
|
+
sourceSurface: 'createCanonicalState().listDecisions({ branch })',
|
|
886
|
+
sourceScope: 'branch_local',
|
|
887
|
+
sourceSequence: branchSequence,
|
|
888
|
+
generatedAt,
|
|
889
|
+
extra: {
|
|
890
|
+
decision_count: decisions.length,
|
|
891
|
+
},
|
|
892
|
+
}),
|
|
893
|
+
renderDecisionIndex(decisions),
|
|
894
|
+
filesWritten
|
|
895
|
+
);
|
|
896
|
+
}
|
|
897
|
+
|
|
898
|
+
writeMarkdownFile(
|
|
899
|
+
resolvedOutputRoot,
|
|
900
|
+
path.join('project', 'notes', 'project-notes.md'),
|
|
901
|
+
buildFrontmatter({
|
|
902
|
+
docKind: 'project-note',
|
|
903
|
+
branch: null,
|
|
904
|
+
projectionOf: 'project-branch-notes-summary',
|
|
905
|
+
sourceSurface: 'createCanonicalState().listMarkdownBranches() + createCanonicalState().getProjectNotesView({ branch })',
|
|
906
|
+
sourceScope: 'runtime_global',
|
|
907
|
+
sourceSequence: null,
|
|
908
|
+
generatedAt,
|
|
909
|
+
extra: {
|
|
910
|
+
note_scope: 'branch_local_summary',
|
|
911
|
+
},
|
|
912
|
+
}),
|
|
913
|
+
renderProjectNote({ branches: branchProjectNotes }),
|
|
914
|
+
filesWritten
|
|
915
|
+
);
|
|
916
|
+
|
|
917
|
+
writeMarkdownFile(
|
|
918
|
+
resolvedOutputRoot,
|
|
919
|
+
path.join('project', 'notes', 'team-notes.md'),
|
|
920
|
+
buildFrontmatter({
|
|
921
|
+
docKind: 'team-note',
|
|
922
|
+
branch: null,
|
|
923
|
+
projectionOf: 'team-branch-notes-summary',
|
|
924
|
+
sourceSurface: 'createCanonicalState().listMarkdownBranches() + createCanonicalState().getTeamNotesView({ branch })',
|
|
925
|
+
sourceScope: 'runtime_global',
|
|
926
|
+
sourceSequence: null,
|
|
927
|
+
generatedAt,
|
|
928
|
+
extra: {
|
|
929
|
+
note_scope: 'branch_local_summary',
|
|
930
|
+
},
|
|
931
|
+
}),
|
|
932
|
+
renderTeamNote({ branches: branchTeamNotes }),
|
|
933
|
+
filesWritten
|
|
934
|
+
);
|
|
935
|
+
|
|
936
|
+
return {
|
|
937
|
+
success: true,
|
|
938
|
+
generated_at: generatedAt,
|
|
939
|
+
output_root: resolvedOutputRoot,
|
|
940
|
+
branch_count: branchRegistry.length,
|
|
941
|
+
files_written: filesWritten,
|
|
942
|
+
omissions,
|
|
943
|
+
};
|
|
944
|
+
}
|
|
945
|
+
|
|
946
|
+
module.exports = {
|
|
947
|
+
DEFAULT_MARKDOWN_WORKSPACE_DIR_NAME,
|
|
948
|
+
GENERATED_BY,
|
|
949
|
+
MARKDOWN_WORKSPACE_SCHEMA,
|
|
950
|
+
exportMarkdownWorkspace,
|
|
951
|
+
};
|