@aopslabs/aops 0.2.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.
Files changed (171) hide show
  1. package/LICENSE +19 -0
  2. package/NOTICE +7 -0
  3. package/README.md +203 -0
  4. package/assets/agent-assets/core/SKILL.md +49 -0
  5. package/assets/agent-assets/core/references/agentspace/SKILL.md +78 -0
  6. package/assets/agent-assets/core/references/aops-cli-core/SKILL.md +108 -0
  7. package/assets/agent-assets/core/references/chatv3/SKILL.md +48 -0
  8. package/assets/agent-assets/core/references/collaborative-work/SKILL.md +85 -0
  9. package/assets/agent-assets/core/references/discuss/SKILL.md +66 -0
  10. package/assets/agent-assets/core/references/docman/SKILL.md +69 -0
  11. package/assets/agent-assets/core/references/hosted-chat/SKILL.md +64 -0
  12. package/assets/agent-assets/core/references/mission/SKILL.md +54 -0
  13. package/assets/agent-assets/core/references/projectman/SKILL.md +86 -0
  14. package/assets/agent-assets/core/references/sys/SKILL.md +36 -0
  15. package/assets/agent-assets/core/references/view/SKILL.md +58 -0
  16. package/assets/agent-assets/core/references/working-disciplines/SKILL.md +69 -0
  17. package/assets/agent-assets/core/user-guides/agent-assets.md +81 -0
  18. package/assets/agent-assets/core/user-guides/agentspace.md +1098 -0
  19. package/assets/agent-assets/core/user-guides/aops-cli.md +1107 -0
  20. package/assets/agent-assets/core/user-guides/aops-system.md +63 -0
  21. package/assets/agent-assets/core/user-guides/chatv3.md +113 -0
  22. package/assets/agent-assets/core/user-guides/docman.md +162 -0
  23. package/assets/agent-assets/core/user-guides/projectman.md +212 -0
  24. package/assets/agent-assets/core/user-guides/sys.md +94 -0
  25. package/assets/agent-assets/core/user-guides/working-disciplines.md +588 -0
  26. package/assets/agent-assets/gateway/aops/SKILL.md +14 -0
  27. package/assets/r6-remote-session-baseline.json +19 -0
  28. package/assets/skills/aops-install/SKILL.md +231 -0
  29. package/assets/skills/aops-install/agents/openai.yaml +4 -0
  30. package/dist/commands/activity.js +352 -0
  31. package/dist/commands/agent-profile.js +653 -0
  32. package/dist/commands/agent.js +710 -0
  33. package/dist/commands/agents-md.js +125 -0
  34. package/dist/commands/api.js +84 -0
  35. package/dist/commands/archive.js +873 -0
  36. package/dist/commands/artifact.js +535 -0
  37. package/dist/commands/assets.js +1414 -0
  38. package/dist/commands/auth/device-login.js +1 -0
  39. package/dist/commands/auth/legacy-export.js +177 -0
  40. package/dist/commands/auth/login.js +426 -0
  41. package/dist/commands/chat.js +1363 -0
  42. package/dist/commands/chatv3.js +1684 -0
  43. package/dist/commands/checkpoint.js +549 -0
  44. package/dist/commands/commercial-artifact.js +475 -0
  45. package/dist/commands/commercial-license.js +594 -0
  46. package/dist/commands/community-auth.js +62 -0
  47. package/dist/commands/community-cockpit.js +185 -0
  48. package/dist/commands/community-console.js +109 -0
  49. package/dist/commands/community-doctor.js +335 -0
  50. package/dist/commands/community-server.js +2062 -0
  51. package/dist/commands/community-setup.js +284 -0
  52. package/dist/commands/discuss.js +1448 -0
  53. package/dist/commands/doc.js +5219 -0
  54. package/dist/commands/experience.js +922 -0
  55. package/dist/commands/host.js +1127 -0
  56. package/dist/commands/init.js +118 -0
  57. package/dist/commands/memory.js +3129 -0
  58. package/dist/commands/mission.js +562 -0
  59. package/dist/commands/official-catalog.js +134 -0
  60. package/dist/commands/plan.js +84 -0
  61. package/dist/commands/playbook.js +415 -0
  62. package/dist/commands/pm/context.js +6 -0
  63. package/dist/commands/pm/index.js +701 -0
  64. package/dist/commands/pm/projectman.js +6266 -0
  65. package/dist/commands/project.js +1080 -0
  66. package/dist/commands/prompt.js +1024 -0
  67. package/dist/commands/repo-sync.js +1907 -0
  68. package/dist/commands/resource.js +527 -0
  69. package/dist/commands/skill.js +1211 -0
  70. package/dist/commands/start-disciplines.js +510 -0
  71. package/dist/commands/start.js +1343 -0
  72. package/dist/commands/target.js +309 -0
  73. package/dist/commands/version.js +42 -0
  74. package/dist/commands/view.js +1852 -0
  75. package/dist/launchers/cockpit.js +28 -0
  76. package/dist/launchers/options.js +74 -0
  77. package/dist/launchers/server.js +41 -0
  78. package/dist/lib/agent-assets/envelope.js +83 -0
  79. package/dist/lib/agent-assets/gateway.js +15 -0
  80. package/dist/lib/agent-assets/guards.js +23 -0
  81. package/dist/lib/agent-assets/hosted-discovery.js +148 -0
  82. package/dist/lib/agent-assets/hosted-package-input.js +154 -0
  83. package/dist/lib/agent-assets/legacy-pointer-migration.js +677 -0
  84. package/dist/lib/agent-assets/native-fs.js +589 -0
  85. package/dist/lib/agent-assets/package-manifest.js +324 -0
  86. package/dist/lib/agent-assets/portable-path.js +67 -0
  87. package/dist/lib/agent-assets/release-input.js +177 -0
  88. package/dist/lib/agent-assets/roots.js +45 -0
  89. package/dist/lib/agent-assets/runtime-binding-reader.js +545 -0
  90. package/dist/lib/agent-assets/runtime-targets.js +50 -0
  91. package/dist/lib/agent-assets/store-reader.js +1212 -0
  92. package/dist/lib/agent-assets/store-types.js +1 -0
  93. package/dist/lib/agent-assets/store-writer.js +1484 -0
  94. package/dist/lib/agent-assets/types.js +1 -0
  95. package/dist/lib/agent-assets/unicode-case-folding-v15-1.generated.js +1548 -0
  96. package/dist/lib/commercial-final-readiness.js +504 -0
  97. package/dist/lib/commercial-lifecycle-smoke-isolation.js +236 -0
  98. package/dist/lib/commercial-managed-postgres-docker-adapter.js +274 -0
  99. package/dist/lib/commercial-setup-readiness-adapter.js +179 -0
  100. package/dist/lib/community-client-contract.js +164 -0
  101. package/dist/lib/community-cockpit-lifecycle.js +601 -0
  102. package/dist/lib/community-command-abort.js +59 -0
  103. package/dist/lib/community-diagnostic.js +237 -0
  104. package/dist/lib/community-docker-adapter.js +498 -0
  105. package/dist/lib/community-home.js +31 -0
  106. package/dist/lib/community-instance-contract.js +148 -0
  107. package/dist/lib/community-instance-layout.js +97 -0
  108. package/dist/lib/community-lifecycle.js +1631 -0
  109. package/dist/lib/community-migration-snapshot.js +770 -0
  110. package/dist/lib/community-native-application-recovery.js +425 -0
  111. package/dist/lib/community-native-child.js +240 -0
  112. package/dist/lib/community-native-database-recovery.js +456 -0
  113. package/dist/lib/community-native-lifecycle.js +2458 -0
  114. package/dist/lib/community-native-migration.js +339 -0
  115. package/dist/lib/community-native-postgres.js +596 -0
  116. package/dist/lib/community-operation-journal.js +773 -0
  117. package/dist/lib/community-operation-lock.js +596 -0
  118. package/dist/lib/community-release-resolver.js +291 -0
  119. package/dist/lib/community-release-verifier.js +475 -0
  120. package/dist/lib/community-repo-discovery.js +252 -0
  121. package/dist/lib/community-setup-server-env.js +203 -0
  122. package/dist/lib/external-url.js +110 -0
  123. package/dist/lib/official-catalog-gateway.js +108 -0
  124. package/dist/lib/official-catalog.js +364 -0
  125. package/dist/lib/server-bootstrap.js +240 -0
  126. package/dist/lib/setup-agent-assets-bridge.js +176 -0
  127. package/dist/lib/setup-agent-assets-release.js +52 -0
  128. package/dist/lib/setup-external-postgres.js +108 -0
  129. package/dist/lib/setup-init-orchestrator.js +990 -0
  130. package/dist/lib/setup-install-guide.js +52 -0
  131. package/dist/lib/setup-installer-release.js +47 -0
  132. package/dist/lib/setup-local-postgres.js +329 -0
  133. package/dist/lib/setup-official-catalog-bridge.js +148 -0
  134. package/dist/lib/setup-readiness.js +888 -0
  135. package/dist/lib/tui-launcher.js +59 -0
  136. package/dist/main.js +150 -0
  137. package/dist/seeds/agents-md.js +54 -0
  138. package/dist/utils/agent-gateway.js +275 -0
  139. package/dist/utils/agents-md.js +281 -0
  140. package/dist/utils/api.js +255 -0
  141. package/dist/utils/chatv3-session-store.js +185 -0
  142. package/dist/utils/command.js +67 -0
  143. package/dist/utils/community-host-runtime.js +376 -0
  144. package/dist/utils/config.js +1015 -0
  145. package/dist/utils/discussion-workspace.js +552 -0
  146. package/dist/utils/experience-workspace.js +326 -0
  147. package/dist/utils/guide-paths.js +67 -0
  148. package/dist/utils/hosted-sugar.js +56 -0
  149. package/dist/utils/hosted-workspace.js +229 -0
  150. package/dist/utils/memory-workspace.js +537 -0
  151. package/dist/utils/operator-runtime.js +199 -0
  152. package/dist/utils/password-crypto.js +39 -0
  153. package/dist/utils/playbook-workspace.js +479 -0
  154. package/dist/utils/project-context.js +255 -0
  155. package/dist/utils/prompts.js +70 -0
  156. package/dist/utils/refresh-race.js +28 -0
  157. package/dist/utils/repo-config.js +109 -0
  158. package/dist/utils/repo-first-projectman.js +194 -0
  159. package/dist/utils/repo-first-storage.js +13 -0
  160. package/dist/utils/repo-first-view.js +305 -0
  161. package/dist/utils/session-guidance.js +278 -0
  162. package/dist/utils/session-state.js +137 -0
  163. package/dist/utils/table.js +39 -0
  164. package/dist/utils/transient-fs.js +49 -0
  165. package/dist/utils/workspace-root.js +1 -0
  166. package/launchers/aops-cockpit.ps1 +15 -0
  167. package/launchers/aops-cockpit.sh +16 -0
  168. package/launchers/aops-server.ps1 +15 -0
  169. package/launchers/aops-server.sh +16 -0
  170. package/native/tui/win32-x64/aops-tui.exe +0 -0
  171. package/package.json +81 -0
@@ -0,0 +1,1852 @@
1
+ import { Buffer } from 'node:buffer';
2
+ import fs from 'node:fs/promises';
3
+ import path from 'node:path';
4
+ import { Command } from 'commander';
5
+ import { compactPayload } from '../utils/command.js';
6
+ import { readAopsRepoConfig } from '../utils/repo-config.js';
7
+ import { resolveProjectBindingContext } from '../utils/project-context.js';
8
+ import { invokeHostedToolWithApiState, requireApiState, unwrapHostedToolResult, } from '../utils/agent-gateway.js';
9
+ import { resolveRepoFirstAgentspacePaths, resolveRepoFirstProjectmanPaths, } from '../utils/repo-first-projectman.js';
10
+ import { resolveRepoFirstWorkspaceRelativeRoot } from '../utils/repo-first-storage.js';
11
+ import { resolveHostedWorkspacePaths } from '../utils/hosted-workspace.js';
12
+ import { readLocalMemoryEntryFiles } from '../utils/memory-workspace.js';
13
+ import { readSessionStateNudges } from '../utils/session-state.js';
14
+ import { normalizeDiscussionOutputRecord, normalizeDiscussionTopicRecord, normalizeDiscussionTurnRecord, readDiscussionTopicRecords, } from '../utils/discussion-workspace.js';
15
+ import { readExperienceItemFiles } from '../utils/experience-workspace.js';
16
+ import { ViewSelectorError, candidateFromRecord, effectiveLocalState, formatDate, idAliases, isRecord, matchesEqualCi, normalizeMaxBytes, normalizeViewOptions, readMarkdownRecords, recordArray, recordId, recordLabel, recordSlug, renderFooter, renderKeyValues, renderMarkdownTable, repoRelative, resolveRecordSelector, sameIdOrPrefix, shortId, slugify, stringValue, summarizeText, } from '../utils/repo-first-view.js';
17
+ const MEMORY_SUBJECT_ALIASES = {
18
+ project: ['agentspace.project', 'projectman.plan', 'project'],
19
+ board: ['projectman.board'],
20
+ sprint: ['projectman.sprint'],
21
+ task: ['projectman.kanban-task'],
22
+ ktask: ['projectman.kanban-task'],
23
+ utask: ['projectman.utask'],
24
+ issue: ['projectman.issue'],
25
+ feedback: ['projectman.feedback'],
26
+ review: ['projectman.review-request'],
27
+ 'review-request': ['projectman.review-request'],
28
+ };
29
+ function resolveMemorySubjectAliases(value) {
30
+ const normalized = value.toLowerCase();
31
+ return MEMORY_SUBJECT_ALIASES[normalized] ?? [normalized];
32
+ }
33
+ function findBoardByReference(boards, reference) {
34
+ const needle = reference.toLowerCase();
35
+ return boards.find((board) => {
36
+ if (matchesEqualCi(board.frontmatter.slug, reference))
37
+ return true;
38
+ if (matchesEqualCi(board.frontmatter.name, reference))
39
+ return true;
40
+ return idAliases(board).some((alias) => alias.toLowerCase() === needle || (needle.length >= 8 && alias.toLowerCase().startsWith(needle)));
41
+ });
42
+ }
43
+ function findRecordByReference(records, reference) {
44
+ const needle = reference.toLowerCase();
45
+ return records.find((record) => {
46
+ if (matchesEqualCi(record.frontmatter.slug, reference))
47
+ return true;
48
+ if (matchesEqualCi(record.frontmatter.name, reference))
49
+ return true;
50
+ if (matchesEqualCi(record.frontmatter.title, reference))
51
+ return true;
52
+ return idAliases(record).some((alias) => alias.toLowerCase() === needle || (needle.length >= 8 && alias.toLowerCase().startsWith(needle)));
53
+ });
54
+ }
55
+ function recordMatchesBoard(workspace, record, boardRef) {
56
+ const board = findBoardByReference(workspace.boards, boardRef);
57
+ if (!board)
58
+ return false;
59
+ const aliases = idAliases(board);
60
+ if (aliases.some((alias) => sameIdOrPrefix(record.frontmatter.boardLocalId, [alias]) || sameIdOrPrefix(record.frontmatter.boardId, [alias]))) {
61
+ return true;
62
+ }
63
+ if (record.frontmatter.entityType === 'projectman.sprint' || record.frontmatter.entityType === 'projectman.issue' || record.frontmatter.entityType === 'projectman.feedback' || record.frontmatter.entityType === 'projectman.review-request') {
64
+ const taskAliases = [record.frontmatter.kanbanTaskLocalId, record.frontmatter.kanbanTaskId, record.frontmatter.taskId];
65
+ return workspace.tasks.some((task) => {
66
+ const taskIdMatchesRecord = taskAliases.some((value) => sameIdOrPrefix(value, idAliases(task)));
67
+ if (!taskIdMatchesRecord)
68
+ return false;
69
+ return aliases.some((alias) => sameIdOrPrefix(task.frontmatter.boardLocalId, [alias]) || sameIdOrPrefix(task.frontmatter.boardId, [alias]));
70
+ });
71
+ }
72
+ return false;
73
+ }
74
+ function recordMatchesSprint(record, sprintRef, sprints) {
75
+ const sprint = findRecordByReference(sprints, sprintRef);
76
+ if (!sprint)
77
+ return false;
78
+ const aliases = idAliases(sprint);
79
+ return [record.frontmatter.sprintLocalId, record.frontmatter.sprintId].some((value) => sameIdOrPrefix(value, aliases));
80
+ }
81
+ function recordMatchesTask(record, taskRef, tasks) {
82
+ const task = findRecordByReference(tasks, taskRef);
83
+ if (!task)
84
+ return false;
85
+ const aliases = idAliases(task);
86
+ return [record.frontmatter.kanbanTaskLocalId, record.frontmatter.kanbanTaskId, record.frontmatter.taskId].some((value) => sameIdOrPrefix(value, aliases));
87
+ }
88
+ function applyPmFilters(records, workspace, filter, options = {}) {
89
+ let filtered = records;
90
+ if (filter.status) {
91
+ filtered = filtered.filter((record) => {
92
+ if (matchesEqualCi(record.frontmatter.status, filter.status))
93
+ return true;
94
+ const resolved = options.taskColumnResolver?.(record);
95
+ if (resolved && matchesEqualCi(resolved, filter.status))
96
+ return true;
97
+ return matchesEqualCi(record.frontmatter.columnSlug, filter.status)
98
+ || matchesEqualCi(record.frontmatter.boardColumnId, filter.status);
99
+ });
100
+ }
101
+ if (filter.severity) {
102
+ filtered = filtered.filter((record) => matchesEqualCi(record.frontmatter.severity, filter.severity));
103
+ }
104
+ if (filter.board) {
105
+ filtered = filtered.filter((record) => recordMatchesBoard(workspace, record, filter.board));
106
+ }
107
+ if (filter.sprint) {
108
+ filtered = filtered.filter((record) => recordMatchesSprint(record, filter.sprint, workspace.sprints));
109
+ }
110
+ if (filter.task) {
111
+ filtered = filtered.filter((record) => recordMatchesTask(record, filter.task, workspace.tasks));
112
+ }
113
+ return filtered;
114
+ }
115
+ function pmFilterFrom(options) {
116
+ return {
117
+ status: stringValue(options.status),
118
+ severity: stringValue(options.severity),
119
+ board: stringValue(options.board),
120
+ sprint: stringValue(options.sprint),
121
+ task: stringValue(options.task),
122
+ };
123
+ }
124
+ function filterTitle(base, filter) {
125
+ const parts = Object.entries(filter)
126
+ .filter(([, value]) => Boolean(value))
127
+ .map(([label, value]) => `${label}=${value}`);
128
+ return parts.length > 0 ? `${base} (${parts.join(', ')})` : base;
129
+ }
130
+ const VIEW_SURFACE = 'local-cache-view';
131
+ const HOSTED_VIEW_SURFACE = 'hosted-view';
132
+ const DOCMAN_MIRROR_COMMENT = /^<!--\s*READ-ONLY MIRROR:[\s\S]*?-->\s*/i;
133
+ const RELEASE_NOTES_LINE = /\n_Release Notes:_ [^\n]*\n/gi;
134
+ function parseIntegerOption(value, fallback) {
135
+ const parsed = typeof value === 'number' ? value : Number.parseInt(String(value ?? ''), 10);
136
+ return Number.isFinite(parsed) ? parsed : fallback;
137
+ }
138
+ function addViewOptions(cmd) {
139
+ return cmd
140
+ .option('--json', 'Output stable JSON envelope')
141
+ .option('--style <style>', 'Markdown style: agent, compact, wide', 'agent')
142
+ .option('--link-mode <mode>', 'Link mode: none, relative, absolute', 'none')
143
+ .option('--max-items <n>', 'Maximum rows/items returned or rendered in each section', (value) => parseIntegerOption(value, 25), 25)
144
+ .option('--max-bytes <n>', 'Maximum markdown bytes; hard-capped at 32768', (value) => parseIntegerOption(value, 32768), 32768)
145
+ .option('--include-archived', 'Include board and sprint records with archivedAt set')
146
+ .option('--api-base-url <url>', 'API base URL for hosted view commands')
147
+ .option('--timeout-ms <ms>', 'Request timeout for hosted view commands', (value) => parseIntegerOption(value, 0))
148
+ .option('--tenant-id <id>', 'Hosted tenant id header')
149
+ .option('--locale <locale>', 'Hosted locale header')
150
+ .option('--fallback-locale <locale>', 'Hosted fallback locale header')
151
+ .option('--scope-id <id>', 'Hosted scope id override')
152
+ .option('--scope-resolution <mode>', 'Hosted scope resolution: explicit or cascade')
153
+ .option('--project-id <id>', 'Project id used to resolve repo-local context')
154
+ .option('--project-name <name>', 'Project name used to resolve repo-local context')
155
+ .option('--project-slug <slug>', 'Project slug used to resolve repo-local context');
156
+ }
157
+ function commandOptions(cmd) {
158
+ return cmd.optsWithGlobals();
159
+ }
160
+ function printJson(payload) {
161
+ console.log(JSON.stringify(payload, null, 2));
162
+ }
163
+ function finalizeMarkdown(body, footer, maxBytes) {
164
+ const limit = normalizeMaxBytes(maxBytes);
165
+ const normalFooter = `${renderFooter({ ...footer, truncated: false })}\n`;
166
+ const normal = `${body.trimEnd()}${normalFooter}`;
167
+ if (Buffer.byteLength(normal, 'utf8') <= limit)
168
+ return { markdown: normal, truncated: false };
169
+ const truncationNote = `\n\nTruncated: output exceeded ${limit} bytes. Use --max-items or a narrower selector.\n`;
170
+ const truncatedFooter = `${renderFooter({ ...footer, truncated: true })}\n`;
171
+ const budget = Math.max(0, limit - Buffer.byteLength(truncationNote, 'utf8') - Buffer.byteLength(truncatedFooter, 'utf8'));
172
+ let sliced = body.trimEnd();
173
+ while (Buffer.byteLength(sliced, 'utf8') > budget && sliced.length > 0) {
174
+ sliced = sliced.slice(0, Math.max(0, Math.floor(sliced.length * 0.9)));
175
+ }
176
+ return {
177
+ markdown: `${sliced.trimEnd()}${truncationNote}${truncatedFooter}`,
178
+ truncated: true,
179
+ };
180
+ }
181
+ function selectorTable(candidates) {
182
+ return renderMarkdownTable(['UID', 'Label', 'Slug', 'Type', 'Path'], candidates.map((candidate) => [
183
+ shortId(candidate.id),
184
+ candidate.label,
185
+ candidate.slug ?? '-',
186
+ candidate.type ?? '-',
187
+ candidate.path ?? '-',
188
+ ]));
189
+ }
190
+ async function emitView(command, options, builder) {
191
+ const viewOptions = normalizeViewOptions(options);
192
+ let context;
193
+ try {
194
+ context = await resolveViewContext(options);
195
+ const built = await builder(context, viewOptions);
196
+ const finalized = finalizeMarkdown(built.markdown, built.footer, viewOptions.maxBytes);
197
+ const surface = built.surface ?? VIEW_SURFACE;
198
+ const envelope = {
199
+ ok: true,
200
+ command,
201
+ surface,
202
+ resolvedContext: context,
203
+ result: built.result,
204
+ diagnostics: {
205
+ style: viewOptions.style,
206
+ linkMode: viewOptions.linkMode,
207
+ maxItems: viewOptions.maxItems,
208
+ maxBytes: viewOptions.maxBytes,
209
+ truncated: finalized.truncated,
210
+ },
211
+ };
212
+ if (viewOptions.json) {
213
+ printJson(envelope);
214
+ }
215
+ else {
216
+ process.stdout.write(finalized.markdown);
217
+ }
218
+ }
219
+ catch (error) {
220
+ process.exitCode = 1;
221
+ if (error instanceof ViewSelectorError) {
222
+ const payload = {
223
+ ok: false,
224
+ command,
225
+ surface: VIEW_SURFACE,
226
+ resolvedContext: context,
227
+ error: {
228
+ code: error.code,
229
+ message: error.message,
230
+ selector: error.selector,
231
+ candidates: error.candidates,
232
+ },
233
+ };
234
+ if (viewOptions.json) {
235
+ printJson(payload);
236
+ }
237
+ else {
238
+ process.stdout.write(`# ${error.code === 'ambiguous' ? 'Ambiguous Selector' : 'Selector Not Found'}\n\n${error.message}\n\n${selectorTable(error.candidates)}\n`);
239
+ }
240
+ return;
241
+ }
242
+ if (viewOptions.json) {
243
+ printJson({
244
+ ok: false,
245
+ command,
246
+ surface: VIEW_SURFACE,
247
+ resolvedContext: context,
248
+ error: {
249
+ message: error instanceof Error ? error.message : String(error),
250
+ },
251
+ });
252
+ }
253
+ else {
254
+ process.stderr.write(`${error instanceof Error ? error.message : String(error)}\n`);
255
+ }
256
+ }
257
+ }
258
+ async function emitHostedView(command, options, builder) {
259
+ const viewOptions = normalizeViewOptions(options);
260
+ let context;
261
+ try {
262
+ context = await resolveViewContext(options);
263
+ const apiState = await requireApiState(options);
264
+ if (!apiState)
265
+ return;
266
+ const built = await builder(apiState, context, viewOptions, options);
267
+ const finalized = finalizeMarkdown(built.markdown, built.footer, viewOptions.maxBytes);
268
+ const envelope = {
269
+ ok: true,
270
+ command,
271
+ surface: built.surface ?? HOSTED_VIEW_SURFACE,
272
+ resolvedContext: context,
273
+ result: built.result,
274
+ diagnostics: {
275
+ style: viewOptions.style,
276
+ linkMode: viewOptions.linkMode,
277
+ maxItems: viewOptions.maxItems,
278
+ maxBytes: viewOptions.maxBytes,
279
+ truncated: finalized.truncated,
280
+ },
281
+ };
282
+ if (viewOptions.json) {
283
+ printJson(envelope);
284
+ }
285
+ else {
286
+ process.stdout.write(finalized.markdown);
287
+ }
288
+ }
289
+ catch (error) {
290
+ process.exitCode = 1;
291
+ if (viewOptions.json) {
292
+ printJson({
293
+ ok: false,
294
+ command,
295
+ surface: HOSTED_VIEW_SURFACE,
296
+ resolvedContext: context,
297
+ error: {
298
+ message: error instanceof Error ? error.message : String(error),
299
+ },
300
+ });
301
+ }
302
+ else {
303
+ process.stderr.write(`${error instanceof Error ? error.message : String(error)}\n`);
304
+ }
305
+ }
306
+ }
307
+ async function resolveViewContext(options) {
308
+ const resolved = await resolveProjectBindingContext(options, { requireProject: false });
309
+ if (resolved.configFound)
310
+ return resolved;
311
+ const repoRoot = await findAncestorRepoRoot(process.cwd());
312
+ if (!repoRoot || repoRoot === resolved.repoRoot)
313
+ return resolved;
314
+ const config = await readAopsRepoConfig(repoRoot);
315
+ const selected = config?.projects.find((project) => stringValue(options.projectId) && project.projectId === stringValue(options.projectId))
316
+ ?? config?.projects.find((project) => stringValue(options.projectSlug) && project.slug === stringValue(options.projectSlug))
317
+ ?? config?.projects.find((project) => stringValue(options.projectName) && project.name === stringValue(options.projectName))
318
+ ?? config?.projects.find((project) => config.activeProjectName && project.name === config.activeProjectName)
319
+ ?? config?.projects[0];
320
+ return {
321
+ repoRoot,
322
+ configPath: path.join(repoRoot, '.aops', 'aops.config.json'),
323
+ scopeId: selected?.scopeId ?? selected?.projectId ?? stringValue(options.projectId),
324
+ projectId: selected?.projectId ?? stringValue(options.projectId),
325
+ projectName: selected?.name ?? stringValue(options.projectName),
326
+ projectSlug: selected?.slug ?? stringValue(options.projectSlug),
327
+ localRoot: selected?.localRoot,
328
+ ownerRepo: selected?.ownerRepo,
329
+ parentProjectSlug: selected?.parentProjectSlug,
330
+ configFound: Boolean(config),
331
+ };
332
+ }
333
+ async function findAncestorRepoRoot(startDir) {
334
+ let current = path.resolve(startDir);
335
+ while (true) {
336
+ try {
337
+ await fs.access(path.join(current, '.aops', 'aops.config.json'));
338
+ return current;
339
+ }
340
+ catch {
341
+ const parent = path.dirname(current);
342
+ if (parent === current)
343
+ return undefined;
344
+ current = parent;
345
+ }
346
+ }
347
+ }
348
+ function filterEntity(records, entityType) {
349
+ return records.filter((record) => stringValue(record.frontmatter.entityType) === entityType);
350
+ }
351
+ async function loadWorkspace(context) {
352
+ const pmPaths = resolveRepoFirstProjectmanPaths(context);
353
+ const agentPaths = resolveRepoFirstAgentspacePaths(context);
354
+ const hostedPaths = resolveHostedWorkspacePaths(context.repoRoot);
355
+ const docRoot = path.join(context.repoRoot, '.aops', 'docman');
356
+ const [boardsRaw, tasksRaw, sprintsRaw, issuesRaw, feedbackRaw, reviewRequestsRaw, utasksRaw, memory, discussions, experience, skills, prompts, docsRaw, sessionStateNudges,] = await Promise.all([
357
+ readMarkdownRecords(pmPaths.boards, { repoRoot: context.repoRoot }),
358
+ readMarkdownRecords(pmPaths.tasks, { repoRoot: context.repoRoot }),
359
+ readMarkdownRecords(pmPaths.sprints, { repoRoot: context.repoRoot }),
360
+ readMarkdownRecords(pmPaths.issues, { repoRoot: context.repoRoot }),
361
+ readMarkdownRecords(pmPaths.feedback, { repoRoot: context.repoRoot }),
362
+ readMarkdownRecords(pmPaths.reviewRequests, { repoRoot: context.repoRoot }),
363
+ readMarkdownRecords(pmPaths.utasks, { repoRoot: context.repoRoot, recursive: true }),
364
+ readLocalMemoryEntryFiles(agentPaths.memoryItems),
365
+ readDiscussionTopicRecords(agentPaths.discussionTopics),
366
+ readExperienceItemFiles(agentPaths.experienceItems),
367
+ readMarkdownRecords(hostedPaths.skillsRoot, { repoRoot: context.repoRoot }),
368
+ readMarkdownRecords(hostedPaths.promptsRoot, { repoRoot: context.repoRoot }),
369
+ readMarkdownRecords(docRoot, { repoRoot: context.repoRoot, recursive: true, includeIndex: true }),
370
+ readSessionStateNudges({ repoRoot: context.repoRoot, localRoot: context.localRoot }),
371
+ ]);
372
+ const docs = filterEntity(docsRaw, 'docman.document-mirror');
373
+ return {
374
+ boards: filterEntity(boardsRaw, 'projectman.board'),
375
+ tasks: filterEntity(tasksRaw, 'projectman.kanban-task'),
376
+ sprints: filterEntity(sprintsRaw, 'projectman.sprint'),
377
+ issues: filterEntity(issuesRaw, 'projectman.issue'),
378
+ feedback: filterEntity(feedbackRaw, 'projectman.feedback'),
379
+ reviewRequests: filterEntity(reviewRequestsRaw, 'projectman.review-request'),
380
+ utasks: filterEntity(utasksRaw, 'projectman.utask'),
381
+ memory,
382
+ discussions,
383
+ experience,
384
+ skills,
385
+ prompts,
386
+ docs,
387
+ docPages: buildDocPageRecords(context.repoRoot, docs),
388
+ sessionStateNudges,
389
+ };
390
+ }
391
+ function materializeRecord(record) {
392
+ const archivedAt = stringValue(record.frontmatter.archivedAt);
393
+ return {
394
+ ...record.frontmatter,
395
+ id: recordId(record),
396
+ label: recordLabel(record),
397
+ slug: recordSlug(record),
398
+ archivedAt,
399
+ archived: Boolean(archivedAt),
400
+ localState: effectiveLocalState(record),
401
+ relativePath: record.relativePath,
402
+ body: record.body,
403
+ };
404
+ }
405
+ function isArchiveMarked(record) {
406
+ return Boolean(stringValue(record.frontmatter.archivedAt));
407
+ }
408
+ function visibleArchiveRecords(records, options) {
409
+ return options.includeArchived ? records : records.filter((record) => !isArchiveMarked(record));
410
+ }
411
+ function archiveResultMeta(allRecords, visibleRecords, options) {
412
+ const archivedCount = allRecords.filter(isArchiveMarked).length;
413
+ return {
414
+ shown: visibleRecords.length,
415
+ activeCount: allRecords.length - archivedCount,
416
+ archivedCount,
417
+ includeArchived: options.includeArchived,
418
+ };
419
+ }
420
+ function recordUpdatedAt(record) {
421
+ return record.frontmatter.updatedAt ?? record.frontmatter.pulledAt ?? record.frontmatter.createdAt;
422
+ }
423
+ function latestRecord(records) {
424
+ return [...records].sort((left, right) => String(recordUpdatedAt(right) ?? '').localeCompare(String(recordUpdatedAt(left) ?? '')))[0];
425
+ }
426
+ function latestMemory(memory) {
427
+ return [...memory].sort((left, right) => String(right.entry.updatedAt ?? right.entry.createdAt ?? '').localeCompare(String(left.entry.updatedAt ?? left.entry.createdAt ?? '')))[0];
428
+ }
429
+ function commonFooter(source, records = []) {
430
+ const latest = latestRecord(records);
431
+ return {
432
+ source,
433
+ localState: records.length > 0 ? mixedState(records) : '-',
434
+ updatedAt: latest ? recordUpdatedAt(latest) : undefined,
435
+ lastPushedAt: latest?.frontmatter.lastPushedAt,
436
+ lastPulledAt: latest?.frontmatter.lastPulledAt ?? latest?.frontmatter.pulledAt,
437
+ };
438
+ }
439
+ function repoFirstSource(context, ...parts) {
440
+ return [resolveRepoFirstWorkspaceRelativeRoot(context), ...parts].join('/');
441
+ }
442
+ function mixedState(records) {
443
+ const states = Array.from(new Set(records.map(effectiveLocalState))).sort();
444
+ return states.length === 0 ? '-' : states.join(',');
445
+ }
446
+ function limitRows(items, options) {
447
+ return items.slice(0, options.maxItems);
448
+ }
449
+ function linkFor(context, record, options) {
450
+ if (options.linkMode === 'absolute')
451
+ return record.filePath;
452
+ if (options.linkMode === 'relative')
453
+ return record.relativePath;
454
+ return '-';
455
+ }
456
+ function microtaskUid(microtask, phase, phaseIndex, microIndex) {
457
+ const explicitId = stringValue(microtask.id) ?? stringValue(microtask.localId);
458
+ if (explicitId)
459
+ return shortId(explicitId);
460
+ const phasePos = typeof phase.position === 'number' ? phase.position : phaseIndex;
461
+ const microPos = typeof microtask.position === 'number' ? microtask.position : microIndex;
462
+ return `P${phasePos}M${microPos}`;
463
+ }
464
+ function boardColumns(board) {
465
+ const columns = recordArray(board.frontmatter.columns);
466
+ if (columns.length > 0)
467
+ return columns;
468
+ return ['Backlog', 'Todo', 'Doing', 'Done'].map((name, index) => ({
469
+ id: `${recordSlug(board) ?? 'board'}-${slugify(name)}`,
470
+ name,
471
+ slug: slugify(name),
472
+ position: index,
473
+ }));
474
+ }
475
+ function tasksForBoard(workspace, board) {
476
+ const aliases = idAliases(board);
477
+ return workspace.tasks.filter((task) => sameIdOrPrefix(task.frontmatter.boardLocalId, aliases) || sameIdOrPrefix(task.frontmatter.boardId, aliases));
478
+ }
479
+ function sprintsForTask(workspace, task) {
480
+ const aliases = idAliases(task);
481
+ return workspace.sprints.filter((sprint) => sameIdOrPrefix(sprint.frontmatter.kanbanTaskLocalId, aliases) || sameIdOrPrefix(sprint.frontmatter.kanbanTaskId, aliases));
482
+ }
483
+ function issuesForSubject(workspace, subject) {
484
+ const aliases = idAliases(subject);
485
+ return workspace.issues.filter((issue) => sameIdOrPrefix(issue.frontmatter.kanbanTaskLocalId, aliases)
486
+ || sameIdOrPrefix(issue.frontmatter.kanbanTaskId, aliases)
487
+ || sameIdOrPrefix(issue.frontmatter.taskId, aliases)
488
+ || sameIdOrPrefix(issue.frontmatter.sprintLocalId, aliases)
489
+ || sameIdOrPrefix(issue.frontmatter.sprintId, aliases));
490
+ }
491
+ function feedbackForSubject(workspace, subject) {
492
+ const aliases = idAliases(subject);
493
+ return workspace.feedback.filter((feedback) => sameIdOrPrefix(feedback.frontmatter.kanbanTaskLocalId, aliases)
494
+ || sameIdOrPrefix(feedback.frontmatter.kanbanTaskId, aliases)
495
+ || sameIdOrPrefix(feedback.frontmatter.taskId, aliases)
496
+ || sameIdOrPrefix(feedback.frontmatter.sprintLocalId, aliases)
497
+ || sameIdOrPrefix(feedback.frontmatter.sprintId, aliases));
498
+ }
499
+ function reviewRequestsForSubject(workspace, subject) {
500
+ const aliases = idAliases(subject);
501
+ return workspace.reviewRequests.filter((reviewRequest) => sameIdOrPrefix(reviewRequest.frontmatter.kanbanTaskLocalId, aliases)
502
+ || sameIdOrPrefix(reviewRequest.frontmatter.kanbanTaskId, aliases)
503
+ || sameIdOrPrefix(reviewRequest.frontmatter.taskId, aliases)
504
+ || sameIdOrPrefix(reviewRequest.frontmatter.sprintLocalId, aliases)
505
+ || sameIdOrPrefix(reviewRequest.frontmatter.sprintId, aliases)
506
+ || sameIdOrPrefix(reviewRequest.frontmatter.parentReviewRequestLocalId, aliases)
507
+ || sameIdOrPrefix(reviewRequest.frontmatter.parentReviewRequestId, aliases)
508
+ || sameIdOrPrefix(reviewRequest.frontmatter.rootReviewRequestLocalId, aliases)
509
+ || sameIdOrPrefix(reviewRequest.frontmatter.rootReviewRequestId, aliases));
510
+ }
511
+ function memoryForSubject(workspace, subjectType, subject) {
512
+ const aliases = idAliases(subject);
513
+ return workspace.memory.filter(({ entry }) => {
514
+ const tags = entry.tags ?? [];
515
+ return (entry.subjectType === subjectType && Boolean(entry.subjectId && aliases.includes(entry.subjectId))) || aliases.some((id) => tags.some((tag) => tag.endsWith(`:${id}`) || tag === id));
516
+ });
517
+ }
518
+ function discussionsForSubject(workspace, subjectType, subject) {
519
+ const aliases = idAliases(subject);
520
+ return workspace.discussions.filter((record) => record.topic.subjectType === subjectType && Boolean(record.topic.subjectId && aliases.includes(record.topic.subjectId)));
521
+ }
522
+ function resolveTaskColumnName(task, boards) {
523
+ const board = boards.find((candidate) => sameIdOrPrefix(task.frontmatter.boardLocalId, idAliases(candidate)) || sameIdOrPrefix(task.frontmatter.boardId, idAliases(candidate)));
524
+ if (board) {
525
+ const columnId = stringValue(task.frontmatter.boardColumnId);
526
+ const columnSlug = stringValue(task.frontmatter.columnSlug);
527
+ const match = boardColumns(board).find((column) => stringValue(column.id) === columnId
528
+ || stringValue(column.slug) === columnSlug
529
+ || stringValue(column.slug) === columnId);
530
+ if (match)
531
+ return stringValue(match.name) ?? stringValue(match.slug) ?? '-';
532
+ }
533
+ const fallback = stringValue(task.frontmatter.columnSlug) ?? stringValue(task.frontmatter.boardColumnId);
534
+ if (!fallback)
535
+ return '-';
536
+ const boardSlugFromTask = stringValue(task.frontmatter.boardSlug);
537
+ if (boardSlugFromTask && fallback.startsWith(`${boardSlugFromTask}-`)) {
538
+ return capitalize(fallback.slice(boardSlugFromTask.length + 1));
539
+ }
540
+ const lastSegment = fallback.includes('-') ? fallback.slice(fallback.lastIndexOf('-') + 1) : fallback;
541
+ return capitalize(lastSegment);
542
+ }
543
+ function capitalize(value) {
544
+ if (!value)
545
+ return value;
546
+ return value.charAt(0).toUpperCase() + value.slice(1);
547
+ }
548
+ function projectmanTable(context, records, options, kind, statusResolver) {
549
+ const wide = options.style === 'wide';
550
+ const headers = wide
551
+ ? ['UID', 'Title', 'Slug', 'Status', 'State', 'Updated', 'Path']
552
+ : ['UID', 'Title', 'Status', 'State', 'Updated'];
553
+ const rows = limitRows(records, options).map((record) => {
554
+ const status = statusResolver?.(record)
555
+ ?? stringValue(record.frontmatter.status)
556
+ ?? stringValue(record.frontmatter.columnSlug)
557
+ ?? stringValue(record.frontmatter.boardColumnId)
558
+ ?? '-';
559
+ const common = [
560
+ shortId(recordId(record)),
561
+ recordLabel(record),
562
+ status,
563
+ effectiveLocalState(record),
564
+ formatDate(recordUpdatedAt(record)),
565
+ ];
566
+ return wide
567
+ ? [common[0], common[1], recordSlug(record) ?? '-', common[2], common[3], common[4], linkFor(context, record, options)]
568
+ : common;
569
+ });
570
+ return [`## ${kind}`, '', renderMarkdownTable(headers, rows)].join('\n');
571
+ }
572
+ function memoryTable(memory, options) {
573
+ return renderMarkdownTable(['UID', 'Kind', 'Subject', 'Durability', 'Updated', 'Summary'], limitRows(memory, options).map(({ entry }) => [
574
+ shortId(entry.memoryId ?? entry.id),
575
+ entry.kind ?? '-',
576
+ entry.subjectTitle ?? entry.subjectType ?? '-',
577
+ entry.durability ?? '-',
578
+ formatDate(entry.updatedAt ?? entry.createdAt),
579
+ summarizeText(entry.content, options.style === 'wide' ? 160 : 90),
580
+ ]));
581
+ }
582
+ function discussionTable(records, options) {
583
+ return renderMarkdownTable(['UID', 'Title', 'Status', 'Turns', 'Outputs', 'Subject', 'Updated'], limitRows(records, options).map((record) => [
584
+ shortId(record.topic.localId),
585
+ record.topic.title,
586
+ record.topic.status,
587
+ record.turns.length,
588
+ record.outputs.length,
589
+ record.topic.subjectType ? `${record.topic.subjectType}:${shortId(record.topic.subjectId)}` : '-',
590
+ formatDate(record.topic.updatedAt),
591
+ ]));
592
+ }
593
+ function sessionStateNudgeTable(nudges, options) {
594
+ return renderMarkdownTable(['Kind', 'Message', 'Age', 'Action', 'Source'], limitRows(nudges, options).map((nudge) => [
595
+ nudge.kind,
596
+ nudge.message,
597
+ nudge.ageMinutes === undefined ? '-' : `${nudge.ageMinutes}m`,
598
+ nudge.action,
599
+ nudge.sourcePath,
600
+ ]));
601
+ }
602
+ function unwrapResultData(result) {
603
+ if (isRecord(result) && Object.prototype.hasOwnProperty.call(result, 'data')) {
604
+ return result.data;
605
+ }
606
+ return result;
607
+ }
608
+ function unwrapListItems(result) {
609
+ const data = unwrapResultData(result);
610
+ if (Array.isArray(data)) {
611
+ return data.filter((entry) => isRecord(entry));
612
+ }
613
+ if (isRecord(data) && Array.isArray(data.items)) {
614
+ return data.items.filter((entry) => isRecord(entry));
615
+ }
616
+ if (Array.isArray(result)) {
617
+ return result.filter((entry) => isRecord(entry));
618
+ }
619
+ return [];
620
+ }
621
+ function displayValue(value, fallback = '-') {
622
+ const normalized = stringValue(value);
623
+ if (normalized)
624
+ return normalized;
625
+ if (typeof value === 'number' || typeof value === 'boolean')
626
+ return String(value);
627
+ return fallback;
628
+ }
629
+ function firstField(record, fields) {
630
+ for (const field of fields) {
631
+ const value = displayValue(record[field], '');
632
+ if (value)
633
+ return value;
634
+ }
635
+ return undefined;
636
+ }
637
+ function tagList(value) {
638
+ if (!Array.isArray(value))
639
+ return '-';
640
+ const tags = value.map((entry) => displayValue(entry, '')).filter(Boolean);
641
+ return tags.length > 0 ? tags.join(', ') : '-';
642
+ }
643
+ function normalizeHostedScopeResolution(value) {
644
+ return value === 'explicit' || value === 'cascade' ? value : undefined;
645
+ }
646
+ function hostedProjectId(project) {
647
+ return firstField(project, ['id', 'projectId']);
648
+ }
649
+ function hostedProjectScopeId(project) {
650
+ return firstField(project, ['scopeId', 'id', 'projectId']);
651
+ }
652
+ function hostedProjectName(project) {
653
+ return firstField(project, ['name', 'slug', 'id', 'projectId']) ?? '-';
654
+ }
655
+ function hostedProjectSlug(project) {
656
+ return firstField(project, ['slug']) ?? '-';
657
+ }
658
+ function hostedRecordId(record, fields = ['id']) {
659
+ return firstField(record, fields);
660
+ }
661
+ function hostedRecordLabel(record, fields = ['name', 'title', 'slug', 'id']) {
662
+ return firstField(record, fields) ?? '-';
663
+ }
664
+ function hostedRecordUpdatedAt(record) {
665
+ return record.updatedAt ?? record.pulledAt ?? record.createdAt;
666
+ }
667
+ function latestHostedUpdatedAt(records) {
668
+ let latest = '';
669
+ for (const record of records) {
670
+ const value = stringValue(hostedRecordUpdatedAt(record));
671
+ if (value && value > latest)
672
+ latest = value;
673
+ }
674
+ return latest || undefined;
675
+ }
676
+ function hostedViewFooter(source, records) {
677
+ return {
678
+ source,
679
+ localState: 'hosted-read-only',
680
+ updatedAt: latestHostedUpdatedAt(records),
681
+ };
682
+ }
683
+ function hostedLimitRows(records, options) {
684
+ return records.slice(0, options.maxItems);
685
+ }
686
+ function hostedGatewayOptions(options, context, overrides = {}) {
687
+ const hasScopedOverride = Boolean(overrides.scopeId || overrides.projectId);
688
+ return {
689
+ apiBaseUrl: options.apiBaseUrl,
690
+ accessToken: options.accessToken,
691
+ refreshToken: options.refreshToken,
692
+ timeoutMs: options.timeoutMs,
693
+ tenantId: options.tenantId,
694
+ locale: options.locale,
695
+ fallbackLocale: options.fallbackLocale,
696
+ scopeId: overrides.scopeId ?? options.scopeId,
697
+ scopeResolution: overrides.scopeResolution ?? normalizeHostedScopeResolution(options.scopeResolution),
698
+ projectId: overrides.projectId ?? options.projectId ?? context.projectId,
699
+ projectName: overrides.projectName ?? (hasScopedOverride ? undefined : options.projectName ?? context.projectName),
700
+ };
701
+ }
702
+ async function invokeHostedList(apiState, options, context, toolId, input, overrides = {}) {
703
+ const payload = await invokeHostedToolWithApiState(apiState, {
704
+ ...hostedGatewayOptions(options, context, overrides),
705
+ toolId,
706
+ input,
707
+ disableDurableActivityLog: true,
708
+ });
709
+ return unwrapListItems(unwrapHostedToolResult(payload));
710
+ }
711
+ async function listHostedProjects(apiState, options, context, viewOptions) {
712
+ return invokeHostedList(apiState, options, context, 'agentspace.project.list-projects', {
713
+ filter: {},
714
+ options: compactPayload({ limit: viewOptions.maxItems }),
715
+ });
716
+ }
717
+ function hostedProjectContext(project, options) {
718
+ return {
719
+ scopeId: hostedProjectScopeId(project),
720
+ projectId: hostedProjectId(project),
721
+ scopeResolution: normalizeHostedScopeResolution(options.scopeResolution) ?? 'explicit',
722
+ };
723
+ }
724
+ function hostedScopedInput(project, options, viewOptions, params = {}) {
725
+ const projectContext = hostedProjectContext(project, options);
726
+ const filter = compactPayload({
727
+ scopeId: projectContext.scopeId,
728
+ scopeResolution: projectContext.scopeResolution,
729
+ limit: params.promptLimitInFilter ? viewOptions.maxItems : undefined,
730
+ });
731
+ return compactPayload({
732
+ filter,
733
+ options: params.promptLimitInFilter ? undefined : compactPayload({ limit: viewOptions.maxItems }),
734
+ });
735
+ }
736
+ async function listHostedInventoryForProject(apiState, options, context, viewOptions, project) {
737
+ const projectContext = hostedProjectContext(project, options);
738
+ const [docs, skills, prompts, resources] = await Promise.all([
739
+ invokeHostedList(apiState, options, context, 'docman.document.list', hostedScopedInput(project, options, viewOptions), projectContext),
740
+ invokeHostedList(apiState, options, context, 'agentspace.skill.list-skills', hostedScopedInput(project, options, viewOptions), projectContext),
741
+ invokeHostedList(apiState, options, context, 'agentspace.prompt.list-prompts', hostedScopedInput(project, options, viewOptions, { promptLimitInFilter: true }), projectContext),
742
+ invokeHostedList(apiState, options, context, 'agentspace.resource.list-resources', hostedScopedInput(project, options, viewOptions), projectContext),
743
+ ]);
744
+ return { project, docs, skills, prompts, resources };
745
+ }
746
+ function matchesHostedProject(project, selector) {
747
+ const needle = selector.toLowerCase();
748
+ const aliases = [
749
+ hostedProjectId(project),
750
+ hostedProjectScopeId(project),
751
+ firstField(project, ['projectId']),
752
+ firstField(project, ['slug']),
753
+ firstField(project, ['name']),
754
+ ].filter((entry) => Boolean(entry));
755
+ return aliases.some((alias) => {
756
+ const normalized = alias.toLowerCase();
757
+ return normalized === needle || (needle.length >= 8 && normalized.startsWith(needle));
758
+ });
759
+ }
760
+ function hostedProjectTable(records, options) {
761
+ return renderMarkdownTable(['UID', 'Name', 'Slug', 'Status', 'Visibility', 'Type', 'Scope', 'Updated'], hostedLimitRows(records, options).map((project) => [
762
+ shortId(hostedProjectId(project) ?? hostedProjectScopeId(project)),
763
+ hostedProjectName(project),
764
+ hostedProjectSlug(project),
765
+ displayValue(project.status),
766
+ displayValue(project.visibility),
767
+ displayValue(project.projectType),
768
+ shortId(hostedProjectScopeId(project)),
769
+ formatDate(hostedRecordUpdatedAt(project)),
770
+ ]));
771
+ }
772
+ function hostedInventoryProjectTable(inventories, options) {
773
+ return renderMarkdownTable(['UID', 'Name', 'Slug', 'Status', 'Docs', 'Skills', 'Prompts', 'Resources'], hostedLimitRows(inventories.map((inventory) => inventory.project), options).map((project) => {
774
+ const inventory = inventories.find((entry) => entry.project === project);
775
+ return [
776
+ shortId(hostedProjectId(project) ?? hostedProjectScopeId(project)),
777
+ hostedProjectName(project),
778
+ hostedProjectSlug(project),
779
+ displayValue(project.status),
780
+ inventory.docs.length,
781
+ inventory.skills.length,
782
+ inventory.prompts.length,
783
+ inventory.resources.length,
784
+ ];
785
+ }));
786
+ }
787
+ function hostedDocsTable(records, options) {
788
+ return renderMarkdownTable(['UID', 'Title', 'Slug', 'Group', 'Status', 'Version', 'Updated'], hostedLimitRows(records, options).map((doc) => [
789
+ shortId(hostedRecordId(doc, ['documentVersionId', 'currentVersionId', 'documentId', 'id'])),
790
+ hostedRecordLabel(doc, ['title', 'name', 'slug', 'id']),
791
+ displayValue(doc.slug),
792
+ displayValue(doc.groupUid ?? doc.groupId),
793
+ displayValue(doc.status),
794
+ displayValue(doc.documentVersion ?? doc.version ?? doc.currentVersionId),
795
+ formatDate(hostedRecordUpdatedAt(doc)),
796
+ ]));
797
+ }
798
+ function hostedSkillsTable(records, options) {
799
+ return renderMarkdownTable(['UID', 'Name', 'Status', 'Version', 'Tags', 'Updated'], hostedLimitRows(records, options).map((skill) => [
800
+ shortId(hostedRecordId(skill, ['id', 'skillId'])),
801
+ hostedRecordLabel(skill, ['name', 'title', 'slug', 'id']),
802
+ displayValue(skill.status),
803
+ shortId(skill.currentVersionId ?? skill.skillVersionId),
804
+ tagList(skill.tags),
805
+ formatDate(hostedRecordUpdatedAt(skill)),
806
+ ]));
807
+ }
808
+ function hostedPromptsTable(records, options) {
809
+ return renderMarkdownTable(['UID', 'Name', 'Status', 'Version', 'Tags', 'Updated'], hostedLimitRows(records, options).map((prompt) => [
810
+ shortId(hostedRecordId(prompt, ['id', 'promptId'])),
811
+ hostedRecordLabel(prompt, ['name', 'title', 'slug', 'id']),
812
+ displayValue(prompt.status),
813
+ shortId(prompt.currentVersionId ?? prompt.promptVersionId),
814
+ tagList(prompt.tags),
815
+ formatDate(hostedRecordUpdatedAt(prompt)),
816
+ ]));
817
+ }
818
+ function hostedResourcesTable(records, options) {
819
+ return renderMarkdownTable(['UID', 'Name', 'Type', 'Ref', 'URI', 'Updated'], hostedLimitRows(records, options).map((resource) => [
820
+ shortId(hostedRecordId(resource, ['id', 'resourceId'])),
821
+ hostedRecordLabel(resource, ['name', 'title', 'uri', 'id']),
822
+ displayValue(resource.resourceType ?? resource.type),
823
+ displayValue(resource.refType && resource.refId ? `${displayValue(resource.refType)}:${displayValue(resource.refId)}` : resource.refType ?? resource.refId),
824
+ summarizeText(resource.uri, 80),
825
+ formatDate(hostedRecordUpdatedAt(resource)),
826
+ ]));
827
+ }
828
+ function hostedTable(records, options) {
829
+ return renderMarkdownTable(['UID', 'Name', 'Source', 'Version', 'Path'], limitRows(records, options).map((record) => [
830
+ shortId(record.frontmatter.remoteId ?? recordId(record)),
831
+ recordLabel(record),
832
+ stringValue(record.frontmatter.sourceProjectSlug) ?? stringValue(record.frontmatter.sourceProjectName) ?? '-',
833
+ shortId(record.frontmatter.currentVersionId),
834
+ options.linkMode === 'none' ? '-' : record.relativePath,
835
+ ]));
836
+ }
837
+ function docTable(records, options) {
838
+ return renderMarkdownTable(['UID', 'Title', 'Group', 'Version', 'Pulled', 'Path'], limitRows(records, options).map((record) => [
839
+ shortId(record.frontmatter.documentVersionId ?? record.frontmatter.documentId),
840
+ recordLabel(record),
841
+ stringValue(record.frontmatter.groupUid) ?? '-',
842
+ record.frontmatter.documentVersion ?? '-',
843
+ formatDate(record.frontmatter.pulledAt),
844
+ options.linkMode === 'none' ? '-' : record.relativePath,
845
+ ]));
846
+ }
847
+ function buildDocPageRecords(repoRoot, docs) {
848
+ const pages = [];
849
+ for (const doc of docs) {
850
+ const body = stripDocMirrorBody(doc.body);
851
+ const headings = [...body.matchAll(/^(#{1,4})\s+(.+)$/gm)];
852
+ const slugCounts = new Map();
853
+ headings.forEach((match, index) => {
854
+ const fullMatch = match[0];
855
+ const marker = match[1] ?? '#';
856
+ const rawTitle = (match[2] ?? '').replace(/\s+#*$/, '').trim();
857
+ const start = match.index ?? 0;
858
+ const next = headings.slice(index + 1).find((candidate) => (candidate[1] ?? '#').length <= marker.length);
859
+ const end = next?.index ?? body.length;
860
+ const baseSlug = slugify(rawTitle);
861
+ const occurrence = slugCounts.get(baseSlug) ?? 0;
862
+ slugCounts.set(baseSlug, occurrence + 1);
863
+ const slug = occurrence === 0 ? baseSlug : `${baseSlug}-${occurrence + 1}`;
864
+ const docKey = stringValue(doc.frontmatter.documentVersionId)
865
+ ?? stringValue(doc.frontmatter.documentId)
866
+ ?? doc.relativePath;
867
+ const filePath = `${doc.filePath}#${slug}`;
868
+ pages.push({
869
+ filePath,
870
+ relativePath: `${repoRelative(repoRoot, doc.filePath)}#${slug}`,
871
+ frontmatter: {
872
+ entityType: 'docman.page-slice',
873
+ localId: `${docKey}#${slug}`,
874
+ id: `${docKey}#${slug}`,
875
+ slug,
876
+ title: rawTitle,
877
+ headingLevel: marker.length,
878
+ documentId: doc.frontmatter.documentId,
879
+ documentVersionId: doc.frontmatter.documentVersionId,
880
+ documentTitle: recordLabel(doc),
881
+ groupUid: doc.frontmatter.groupUid,
882
+ pulledAt: doc.frontmatter.pulledAt,
883
+ sourcePath: doc.relativePath,
884
+ },
885
+ body: body.slice(start, end).trim() || fullMatch,
886
+ });
887
+ });
888
+ }
889
+ return pages;
890
+ }
891
+ function stripDocMirrorBody(body) {
892
+ return body.replace(DOCMAN_MIRROR_COMMENT, '').replace(RELEASE_NOTES_LINE, '\n').trim();
893
+ }
894
+ function projectCounts(workspace, options) {
895
+ const boards = options ? visibleArchiveRecords(workspace.boards, options) : workspace.boards;
896
+ const sprints = options ? visibleArchiveRecords(workspace.sprints, options) : workspace.sprints;
897
+ return {
898
+ boards: boards.length,
899
+ tasks: workspace.tasks.length,
900
+ sprints: sprints.length,
901
+ archivedBoards: workspace.boards.filter(isArchiveMarked).length,
902
+ archivedSprints: workspace.sprints.filter(isArchiveMarked).length,
903
+ issues: workspace.issues.length,
904
+ feedback: workspace.feedback.length,
905
+ reviewRequests: workspace.reviewRequests.length,
906
+ memory: workspace.memory.length,
907
+ discussions: workspace.discussions.length,
908
+ experience: workspace.experience.length,
909
+ skills: workspace.skills.length,
910
+ prompts: workspace.prompts.length,
911
+ docs: workspace.docs.length,
912
+ };
913
+ }
914
+ async function buildProjectsView(context, options) {
915
+ const [config, workspace] = await Promise.all([readAopsRepoConfig(context.repoRoot), loadWorkspace(context)]);
916
+ const projects = config?.projects ?? [];
917
+ const rows = projects.map((project) => [
918
+ shortId(project.projectId ?? project.scopeId),
919
+ project.name,
920
+ project.slug ?? '-',
921
+ project.projectId ?? '-',
922
+ project.scopeId ?? project.projectId ?? '-',
923
+ context.projectName === project.name ? 'yes' : 'no',
924
+ ]);
925
+ return {
926
+ result: { projects, counts: projectCounts(workspace, options) },
927
+ markdown: [
928
+ '# AOPS Projects',
929
+ '',
930
+ renderMarkdownTable(['UID', 'Name', 'Slug', 'Project ID', 'Scope ID', 'Active'], rows),
931
+ '',
932
+ '## Current Repo Counts',
933
+ '',
934
+ renderMarkdownTable(['Surface', 'Count'], Object.entries(projectCounts(workspace, options))),
935
+ ].join('\n'),
936
+ footer: commonFooter('.aops/aops.config.json'),
937
+ };
938
+ }
939
+ async function buildHostedProjectsView(apiState, context, options, cliOptions) {
940
+ const projects = await listHostedProjects(apiState, cliOptions, context, options);
941
+ return {
942
+ surface: HOSTED_VIEW_SURFACE,
943
+ result: {
944
+ count: projects.length,
945
+ data: projects,
946
+ },
947
+ markdown: [
948
+ '# Hosted Projects',
949
+ '',
950
+ hostedProjectTable(projects, options),
951
+ ].join('\n'),
952
+ footer: hostedViewFooter('hosted:agentspace.project.list-projects', projects),
953
+ };
954
+ }
955
+ async function buildHostedInventoryView(apiState, context, options, cliOptions) {
956
+ const allProjects = await listHostedProjects(apiState, cliOptions, context, options);
957
+ const selector = stringValue(cliOptions.hostedProject);
958
+ const projects = selector ? allProjects.filter((project) => matchesHostedProject(project, selector)) : allProjects;
959
+ if (selector && projects.length === 0) {
960
+ throw new Error(`Hosted project not found: ${selector}`);
961
+ }
962
+ if (selector && projects.length > 1) {
963
+ const matches = projects.map((project) => `${hostedProjectName(project)} (${hostedProjectId(project) ?? hostedProjectScopeId(project)})`).join(', ');
964
+ throw new Error(`Hosted project selector is ambiguous: ${selector}. Matches: ${matches}`);
965
+ }
966
+ const inventories = await Promise.all(projects.map((project) => listHostedInventoryForProject(apiState, cliOptions, context, options, project)));
967
+ const relatedRecords = inventories.flatMap((inventory) => [
968
+ inventory.project,
969
+ ...inventory.docs,
970
+ ...inventory.skills,
971
+ ...inventory.prompts,
972
+ ...inventory.resources,
973
+ ]);
974
+ const lines = [
975
+ '# Hosted Project Inventory',
976
+ '',
977
+ '## Projects',
978
+ '',
979
+ hostedInventoryProjectTable(inventories, options),
980
+ ];
981
+ for (const inventory of inventories) {
982
+ const project = inventory.project;
983
+ lines.push('', `## ${hostedProjectName(project)} [UID ${shortId(hostedProjectId(project) ?? hostedProjectScopeId(project))}]`, '', renderKeyValues([
984
+ ['projectId', hostedProjectId(project) ?? '-'],
985
+ ['scopeId', hostedProjectScopeId(project) ?? '-'],
986
+ ['slug', hostedProjectSlug(project)],
987
+ ['status', displayValue(project.status)],
988
+ ['scopeResolution', normalizeHostedScopeResolution(cliOptions.scopeResolution) ?? 'explicit'],
989
+ ]), '', '### Documents', '', hostedDocsTable(inventory.docs, options), '', '### Skills', '', hostedSkillsTable(inventory.skills, options), '', '### Prompts', '', hostedPromptsTable(inventory.prompts, options), '', '### Resources', '', hostedResourcesTable(inventory.resources, options));
990
+ }
991
+ return {
992
+ surface: HOSTED_VIEW_SURFACE,
993
+ result: {
994
+ count: inventories.length,
995
+ projects: inventories.map((inventory) => ({
996
+ project: inventory.project,
997
+ counts: {
998
+ docs: inventory.docs.length,
999
+ skills: inventory.skills.length,
1000
+ prompts: inventory.prompts.length,
1001
+ resources: inventory.resources.length,
1002
+ },
1003
+ })),
1004
+ data: inventories,
1005
+ },
1006
+ markdown: lines.join('\n'),
1007
+ footer: hostedViewFooter('hosted:agentspace+docman', relatedRecords),
1008
+ };
1009
+ }
1010
+ async function buildProjectView(context, options, selector) {
1011
+ const [config, workspace] = await Promise.all([readAopsRepoConfig(context.repoRoot), loadWorkspace(context)]);
1012
+ const projects = config?.projects ?? [];
1013
+ const chosen = selector
1014
+ ? projects.find((project) => [project.projectId, project.scopeId, project.name, project.slug].some((value) => stringValue(value)?.toLowerCase() === selector.toLowerCase()))
1015
+ : projects.find((project) => project.projectId === context.projectId || project.name === context.projectName) ?? projects[0];
1016
+ if (!chosen)
1017
+ throw new ViewSelectorError('missing', selector ?? '(current project)', []);
1018
+ return {
1019
+ result: { project: chosen, counts: projectCounts(workspace, options) },
1020
+ markdown: [
1021
+ `# Project: ${chosen.name}`,
1022
+ '',
1023
+ renderKeyValues([
1024
+ ['projectId', chosen.projectId],
1025
+ ['scopeId', chosen.scopeId ?? chosen.projectId],
1026
+ ['slug', chosen.slug ?? '-'],
1027
+ ['repoRoot', context.repoRoot],
1028
+ ]),
1029
+ '',
1030
+ '## Counts',
1031
+ '',
1032
+ renderMarkdownTable(['Surface', 'Count'], Object.entries(projectCounts(workspace, options))),
1033
+ ].join('\n'),
1034
+ footer: commonFooter('.aops/aops.config.json'),
1035
+ };
1036
+ }
1037
+ async function buildDashboardView(context, options) {
1038
+ const workspace = await loadWorkspace(context);
1039
+ const recentMemory = workspace.memory.slice(0, Math.min(5, options.maxItems));
1040
+ const recentDiscussions = workspace.discussions.slice(0, Math.min(5, options.maxItems));
1041
+ return {
1042
+ result: {
1043
+ counts: projectCounts(workspace, options),
1044
+ boards: visibleArchiveRecords(workspace.boards, options).map(materializeRecord),
1045
+ openIssues: workspace.issues.filter((issue) => stringValue(issue.frontmatter.status) !== 'resolved').map(materializeRecord),
1046
+ pendingReviewRequests: workspace.reviewRequests.filter((reviewRequest) => !['accepted', 'closed', 'cancelled'].includes(stringValue(reviewRequest.frontmatter.status) ?? '')).map(materializeRecord),
1047
+ sessionStateNudges: workspace.sessionStateNudges,
1048
+ },
1049
+ markdown: [
1050
+ '# AOPS Repo-First Dashboard',
1051
+ '',
1052
+ renderMarkdownTable(['Surface', 'Count'], Object.entries(projectCounts(workspace, options))),
1053
+ '',
1054
+ projectmanTable(context, visibleArchiveRecords(workspace.boards, options), { ...options, maxItems: Math.min(10, options.maxItems) }, 'Boards'),
1055
+ '',
1056
+ projectmanTable(context, workspace.tasks, { ...options, maxItems: Math.min(10, options.maxItems) }, 'Recent Tasks', (record) => resolveTaskColumnName(record, workspace.boards)),
1057
+ '',
1058
+ '## Open Issues',
1059
+ '',
1060
+ projectmanTable(context, workspace.issues.filter((issue) => stringValue(issue.frontmatter.status) !== 'resolved'), options, 'Issues'),
1061
+ '',
1062
+ '## Pending Review Requests',
1063
+ '',
1064
+ projectmanTable(context, workspace.reviewRequests.filter((reviewRequest) => !['accepted', 'closed', 'cancelled'].includes(stringValue(reviewRequest.frontmatter.status) ?? '')), options, 'Review Requests'),
1065
+ ...(workspace.sessionStateNudges.length > 0
1066
+ ? [
1067
+ '',
1068
+ '## Session State Nudges',
1069
+ '',
1070
+ sessionStateNudgeTable(workspace.sessionStateNudges, options),
1071
+ ]
1072
+ : []),
1073
+ '',
1074
+ '## Recent Memory',
1075
+ '',
1076
+ memoryTable(recentMemory, options),
1077
+ '',
1078
+ '## Recent Discussions',
1079
+ '',
1080
+ discussionTable(recentDiscussions, options),
1081
+ ].join('\n'),
1082
+ footer: commonFooter('.aops', [
1083
+ ...workspace.boards,
1084
+ ...workspace.tasks,
1085
+ ...workspace.sprints,
1086
+ ...workspace.issues,
1087
+ ...workspace.feedback,
1088
+ ...workspace.reviewRequests,
1089
+ ...workspace.docs,
1090
+ ]),
1091
+ };
1092
+ }
1093
+ function listView(context, options, title, source, records, kind, statusResolver, meta = {}) {
1094
+ return {
1095
+ result: {
1096
+ count: records.length,
1097
+ ...meta,
1098
+ data: records.map(materializeRecord),
1099
+ },
1100
+ markdown: [`# ${title}`, '', projectmanTable(context, records, options, kind, statusResolver)].join('\n'),
1101
+ footer: commonFooter(source, records),
1102
+ };
1103
+ }
1104
+ async function buildBoardView(context, options, selector) {
1105
+ const workspace = await loadWorkspace(context);
1106
+ const board = resolveRecordSelector(workspace.boards, selector);
1107
+ const tasks = tasksForBoard(workspace, board);
1108
+ const sprints = visibleArchiveRecords(tasks.flatMap((task) => sprintsForTask(workspace, task)), options);
1109
+ const aliases = idAliases(board);
1110
+ return {
1111
+ result: {
1112
+ board: materializeRecord(board),
1113
+ columns: boardColumns(board),
1114
+ tasks: tasks.map(materializeRecord),
1115
+ sprints: sprints.map(materializeRecord),
1116
+ },
1117
+ markdown: [
1118
+ `# Board: ${recordLabel(board)}`,
1119
+ '',
1120
+ renderKeyValues([
1121
+ ['uid', recordId(board)],
1122
+ ['slug', recordSlug(board) ?? '-'],
1123
+ ['description', summarizeText(board.frontmatter.description, options.style === 'wide' ? 240 : 140)],
1124
+ ['tasks', tasks.length],
1125
+ ['sprints', sprints.length],
1126
+ ]),
1127
+ '',
1128
+ '## Columns',
1129
+ '',
1130
+ renderMarkdownTable(['UID', 'Name', 'Slug', 'Position'], boardColumns(board).map((column) => [
1131
+ column.id ?? '-',
1132
+ column.name ?? '-',
1133
+ column.slug ?? '-',
1134
+ column.position ?? '-',
1135
+ ])),
1136
+ '',
1137
+ projectmanTable(context, tasks, options, 'Tasks', (record) => resolveTaskColumnName(record, workspace.boards)),
1138
+ '',
1139
+ projectmanTable(context, sprints, options, 'Sprints'),
1140
+ '',
1141
+ `Aliases: ${aliases.join(', ') || '-'}`,
1142
+ ].join('\n'),
1143
+ footer: commonFooter(board.relativePath, [board, ...tasks, ...sprints]),
1144
+ };
1145
+ }
1146
+ async function buildTaskView(context, options, selector) {
1147
+ const workspace = await loadWorkspace(context);
1148
+ const task = resolveRecordSelector(workspace.tasks, selector);
1149
+ const board = workspace.boards.find((candidate) => sameIdOrPrefix(task.frontmatter.boardLocalId, idAliases(candidate)) || sameIdOrPrefix(task.frontmatter.boardId, idAliases(candidate)));
1150
+ const sprints = visibleArchiveRecords(sprintsForTask(workspace, task), options);
1151
+ const issues = issuesForSubject(workspace, task);
1152
+ const feedback = feedbackForSubject(workspace, task);
1153
+ const reviewRequests = reviewRequestsForSubject(workspace, task);
1154
+ const memory = memoryForSubject(workspace, 'projectman.kanban-task', task);
1155
+ const discussions = discussionsForSubject(workspace, 'projectman.kanban-task', task);
1156
+ return {
1157
+ result: {
1158
+ task: materializeRecord(task),
1159
+ board: board ? materializeRecord(board) : null,
1160
+ sprints: sprints.map(materializeRecord),
1161
+ issues: issues.map(materializeRecord),
1162
+ feedback: feedback.map(materializeRecord),
1163
+ reviewRequests: reviewRequests.map(materializeRecord),
1164
+ memory: memory.map(({ entry }) => entry),
1165
+ discussions: discussions.map(normalizeDiscussionTopicRecord),
1166
+ },
1167
+ markdown: [
1168
+ `# Task: ${recordLabel(task)}`,
1169
+ '',
1170
+ renderKeyValues([
1171
+ ['uid', recordId(task)],
1172
+ ['board', board ? `${recordLabel(board)} [UID ${shortId(recordId(board))}]` : '-'],
1173
+ ['column', stringValue(task.frontmatter.columnSlug) ?? stringValue(task.frontmatter.boardColumnId) ?? '-'],
1174
+ ['progress', task.frontmatter.progress ?? '-'],
1175
+ ['description', summarizeText(task.frontmatter.description, options.style === 'wide' ? 300 : 160)],
1176
+ ]),
1177
+ '',
1178
+ projectmanTable(context, sprints, options, 'Sprints'),
1179
+ '',
1180
+ projectmanTable(context, issues, options, 'Issues'),
1181
+ '',
1182
+ projectmanTable(context, feedback, options, 'Feedback'),
1183
+ '',
1184
+ projectmanTable(context, reviewRequests, options, 'Review Requests'),
1185
+ '',
1186
+ '## Related Memory',
1187
+ '',
1188
+ memoryTable(memory, options),
1189
+ '',
1190
+ '## Related Discussions',
1191
+ '',
1192
+ discussionTable(discussions, options),
1193
+ ].join('\n'),
1194
+ footer: commonFooter(task.relativePath, [task, ...sprints, ...issues, ...feedback, ...reviewRequests]),
1195
+ };
1196
+ }
1197
+ async function buildSprintView(context, options, selector) {
1198
+ const workspace = await loadWorkspace(context);
1199
+ const sprint = resolveRecordSelector(workspace.sprints, selector);
1200
+ const task = workspace.tasks.find((candidate) => sameIdOrPrefix(sprint.frontmatter.kanbanTaskLocalId, idAliases(candidate)) || sameIdOrPrefix(sprint.frontmatter.kanbanTaskId, idAliases(candidate)));
1201
+ const issues = issuesForSubject(workspace, sprint);
1202
+ const feedback = feedbackForSubject(workspace, sprint);
1203
+ const reviewRequests = reviewRequestsForSubject(workspace, sprint);
1204
+ const memory = memoryForSubject(workspace, 'projectman.sprint', sprint);
1205
+ const discussions = discussionsForSubject(workspace, 'projectman.sprint', sprint);
1206
+ const phases = recordArray(sprint.frontmatter.phases);
1207
+ const microtaskRows = phases.flatMap((phase, phaseIndex) => recordArray(phase.microtasks).map((microtask, microIndex) => [
1208
+ microtaskUid(microtask, phase, phaseIndex, microIndex),
1209
+ phase.name ?? '-',
1210
+ microtask.title ?? '-',
1211
+ microtask.status ?? '-',
1212
+ summarizeText(microtask.notes, 90),
1213
+ ]));
1214
+ return {
1215
+ result: {
1216
+ sprint: materializeRecord(sprint),
1217
+ task: task ? materializeRecord(task) : null,
1218
+ phases,
1219
+ issues: issues.map(materializeRecord),
1220
+ feedback: feedback.map(materializeRecord),
1221
+ reviewRequests: reviewRequests.map(materializeRecord),
1222
+ memory: memory.map(({ entry }) => entry),
1223
+ discussions: discussions.map(normalizeDiscussionTopicRecord),
1224
+ },
1225
+ markdown: [
1226
+ `# Sprint: ${recordLabel(sprint)}`,
1227
+ '',
1228
+ renderKeyValues([
1229
+ ['uid', recordId(sprint)],
1230
+ ['status', stringValue(sprint.frontmatter.status) ?? '-'],
1231
+ ['task', task ? `${recordLabel(task)} [UID ${shortId(recordId(task))}]` : '-'],
1232
+ ['goal', summarizeText(sprint.frontmatter.goal, options.style === 'wide' ? 300 : 180)],
1233
+ ]),
1234
+ '',
1235
+ '## Microtasks',
1236
+ '',
1237
+ renderMarkdownTable(['UID', 'Phase', 'Title', 'Status', 'Notes'], limitRows(microtaskRows, options)),
1238
+ '',
1239
+ projectmanTable(context, issues, options, 'Issues'),
1240
+ '',
1241
+ projectmanTable(context, feedback, options, 'Feedback'),
1242
+ '',
1243
+ projectmanTable(context, reviewRequests, options, 'Review Requests'),
1244
+ '',
1245
+ '## Related Memory',
1246
+ '',
1247
+ memoryTable(memory, options),
1248
+ '',
1249
+ '## Related Discussions',
1250
+ '',
1251
+ discussionTable(discussions, options),
1252
+ ].join('\n'),
1253
+ footer: commonFooter(sprint.relativePath, [sprint, ...issues, ...feedback, ...reviewRequests]),
1254
+ };
1255
+ }
1256
+ async function buildMemoryView(context, options, params = {}) {
1257
+ const workspace = await loadWorkspace(context);
1258
+ let entries = params.resumeOnly
1259
+ ? workspace.memory.filter(({ entry }) => ['resume', 'kickoff', 'closeout'].includes(entry.kind ?? ''))
1260
+ : workspace.memory;
1261
+ const filter = params.filter ?? {};
1262
+ if (filter.durability) {
1263
+ entries = entries.filter(({ entry }) => matchesEqualCi(entry.durability, filter.durability));
1264
+ }
1265
+ if (filter.kind) {
1266
+ entries = entries.filter(({ entry }) => matchesEqualCi(entry.kind, filter.kind));
1267
+ }
1268
+ if (filter.subject) {
1269
+ const aliases = resolveMemorySubjectAliases(filter.subject);
1270
+ entries = entries.filter(({ entry }) => entry.subjectType
1271
+ ? aliases.some((alias) => entry.subjectType?.toLowerCase() === alias.toLowerCase())
1272
+ : false);
1273
+ }
1274
+ if (filter.id) {
1275
+ const needle = filter.id.toLowerCase();
1276
+ entries = entries.filter(({ entry }) => {
1277
+ const sid = entry.subjectId?.toLowerCase();
1278
+ if (!sid)
1279
+ return false;
1280
+ if (sid === needle)
1281
+ return true;
1282
+ return needle.length >= 8 && sid.startsWith(needle);
1283
+ });
1284
+ }
1285
+ const latest = latestMemory(entries);
1286
+ const titleSuffix = [
1287
+ filter.durability ? `durability=${filter.durability}` : null,
1288
+ filter.kind ? `kind=${filter.kind}` : null,
1289
+ filter.subject ? `subject=${filter.subject}` : null,
1290
+ filter.id ? `id=${filter.id}` : null,
1291
+ ].filter(Boolean).join(', ');
1292
+ const heading = params.resumeOnly ? 'AOPS Resume Memory' : 'AOPS Memory';
1293
+ return {
1294
+ result: {
1295
+ count: entries.length,
1296
+ filter,
1297
+ data: entries.map(({ entry, filePath }) => ({ ...entry, relativePath: repoRelative(context.repoRoot, filePath) })),
1298
+ },
1299
+ markdown: [
1300
+ `# ${heading}${titleSuffix ? ` (${titleSuffix})` : ''}`,
1301
+ '',
1302
+ memoryTable(entries, options),
1303
+ ].join('\n'),
1304
+ footer: {
1305
+ source: repoFirstSource(context, 'agentspace', 'memory', 'items'),
1306
+ localState: '-',
1307
+ updatedAt: latest?.entry.updatedAt ?? latest?.entry.createdAt,
1308
+ },
1309
+ };
1310
+ }
1311
+ async function buildDiscussionsView(context, options, filter = {}) {
1312
+ const workspace = await loadWorkspace(context);
1313
+ let records = workspace.discussions;
1314
+ if (filter.status) {
1315
+ records = records.filter((record) => matchesEqualCi(record.topic.status, filter.status));
1316
+ }
1317
+ if (filter.agent) {
1318
+ const needle = filter.agent.toLowerCase();
1319
+ records = records.filter((record) => record.topic.participants.some((participant) => participant.toLowerCase() === needle));
1320
+ }
1321
+ const visibleRecords = limitRows(records, options);
1322
+ return {
1323
+ result: {
1324
+ count: records.length,
1325
+ shown: visibleRecords.length,
1326
+ hasMore: visibleRecords.length < records.length,
1327
+ filter,
1328
+ data: visibleRecords.map(normalizeDiscussionTopicRecord),
1329
+ },
1330
+ markdown: [`# ${filterTitle('AOPS Discussions', filter)}`, '', discussionTable(records, options)].join('\n'),
1331
+ footer: {
1332
+ source: repoFirstSource(context, 'agentspace', 'discussions', 'topics'),
1333
+ localState: '-',
1334
+ updatedAt: records[0]?.topic.updatedAt,
1335
+ },
1336
+ };
1337
+ }
1338
+ async function buildDiscussionView(context, options, selector) {
1339
+ const workspace = await loadWorkspace(context);
1340
+ const fakeRecords = workspace.discussions.map((record) => discussionRecordAsViewRecord(context.repoRoot, record));
1341
+ const selected = resolveRecordSelector(fakeRecords, selector);
1342
+ const record = workspace.discussions.find((candidate) => candidate.dirName === selected.relativePath || candidate.topic.localId === selected.frontmatter.localId);
1343
+ if (!record)
1344
+ throw new ViewSelectorError('missing', selector, fakeRecords.map(candidateFromRecord));
1345
+ return {
1346
+ result: {
1347
+ topic: normalizeDiscussionTopicRecord(record),
1348
+ turns: record.turns.map(({ turn }) => normalizeDiscussionTurnRecord(turn)),
1349
+ outputs: record.outputs.map(({ output }) => normalizeDiscussionOutputRecord(output)),
1350
+ },
1351
+ markdown: [
1352
+ `# Discussion: ${record.topic.title}`,
1353
+ '',
1354
+ renderKeyValues([
1355
+ ['uid', record.topic.localId],
1356
+ ['status', record.topic.status],
1357
+ ['participants', record.topic.participants.join(', ')],
1358
+ ['subject', record.topic.subjectType ? `${record.topic.subjectType}:${record.topic.subjectId ?? '-'}` : '-'],
1359
+ ['question', summarizeText(record.topic.question, 220)],
1360
+ ]),
1361
+ '',
1362
+ '## Turns',
1363
+ '',
1364
+ renderMarkdownTable(options.style === 'wide'
1365
+ ? ['Seq', 'Agent', 'Kind', 'To', 'Created', 'Summary']
1366
+ : ['Seq', 'Agent', 'Kind', 'Created', 'Summary'], limitRows(record.turns, options).map(({ turn }) => {
1367
+ const base = [turn.seq, turn.agentId, turn.kind, formatDate(turn.createdAt), summarizeText(turn.content, options.style === 'wide' ? 180 : 100)];
1368
+ return options.style === 'wide'
1369
+ ? [base[0], base[1], base[2], turn.addressedTo ?? '-', base[3], base[4]]
1370
+ : base;
1371
+ })),
1372
+ '',
1373
+ '## Outputs',
1374
+ '',
1375
+ renderMarkdownTable(['Kind', 'Agent', 'Updated', 'Summary'], limitRows(record.outputs, options).map(({ output }) => [
1376
+ output.outputKind,
1377
+ output.agentId ?? '-',
1378
+ formatDate(output.updatedAt),
1379
+ summarizeText(output.content, options.style === 'wide' ? 180 : 100),
1380
+ ])),
1381
+ ].join('\n'),
1382
+ footer: {
1383
+ source: repoFirstSource(context, 'agentspace', 'discussions', 'topics', record.dirName),
1384
+ localState: '-',
1385
+ updatedAt: record.topic.updatedAt,
1386
+ },
1387
+ };
1388
+ }
1389
+ function discussionRecordAsViewRecord(repoRoot, record) {
1390
+ return {
1391
+ filePath: record.topicFilePath,
1392
+ relativePath: record.dirName,
1393
+ frontmatter: {
1394
+ entityType: 'agentspace.discussion-topic',
1395
+ localId: record.topic.localId,
1396
+ id: record.topic.localId,
1397
+ slug: record.dirName,
1398
+ title: record.topic.title,
1399
+ updatedAt: record.topic.updatedAt,
1400
+ },
1401
+ body: record.topic.question ?? '',
1402
+ };
1403
+ }
1404
+ async function buildExperienceView(context, options, filter = {}) {
1405
+ const workspace = await loadWorkspace(context);
1406
+ let entries = workspace.experience;
1407
+ if (filter.type) {
1408
+ entries = entries.filter(({ item }) => matchesEqualCi(item.type, filter.type));
1409
+ }
1410
+ if (filter.area) {
1411
+ const needle = filter.area.toLowerCase();
1412
+ entries = entries.filter(({ item }) => item.areas.some((area) => area.toLowerCase() === needle));
1413
+ }
1414
+ return {
1415
+ result: {
1416
+ count: entries.length,
1417
+ filter,
1418
+ data: entries.map(({ item, filePath }) => ({ ...item, relativePath: repoRelative(context.repoRoot, filePath) })),
1419
+ },
1420
+ markdown: [
1421
+ `# ${filterTitle('AOPS Experience', filter)}`,
1422
+ '',
1423
+ renderMarkdownTable(['UID', 'Type', 'Title', 'Areas', 'Updated', 'Summary'], limitRows(entries, options).map(({ item }) => [
1424
+ shortId(item.localId),
1425
+ item.type,
1426
+ item.title,
1427
+ item.areas.join(', ') || '-',
1428
+ formatDate(item.updatedAt),
1429
+ summarizeText(item.content, 100),
1430
+ ])),
1431
+ ].join('\n'),
1432
+ footer: {
1433
+ source: repoFirstSource(context, 'agentspace', 'experience', 'items'),
1434
+ localState: '-',
1435
+ updatedAt: entries[0]?.item.updatedAt,
1436
+ },
1437
+ };
1438
+ }
1439
+ function hostedView(title, source, records, options) {
1440
+ return {
1441
+ result: {
1442
+ count: records.length,
1443
+ data: records.map(materializeRecord),
1444
+ },
1445
+ markdown: [`# ${title}`, '', hostedTable(records, options)].join('\n'),
1446
+ footer: commonFooter(source, records),
1447
+ };
1448
+ }
1449
+ async function buildDocView(context, options, selector) {
1450
+ const workspace = await loadWorkspace(context);
1451
+ const doc = resolveRecordSelector(workspace.docs, selector);
1452
+ const pages = workspace.docPages.filter((page) => page.frontmatter.sourcePath === doc.relativePath);
1453
+ return {
1454
+ result: {
1455
+ document: materializeRecord(doc),
1456
+ pages: pages.map(materializeRecord),
1457
+ },
1458
+ markdown: [
1459
+ `# Doc: ${recordLabel(doc)}`,
1460
+ '',
1461
+ renderKeyValues([
1462
+ ['documentId', doc.frontmatter.documentId],
1463
+ ['documentVersionId', doc.frontmatter.documentVersionId],
1464
+ ['groupUid', doc.frontmatter.groupUid],
1465
+ ['version', doc.frontmatter.documentVersion],
1466
+ ['path', doc.relativePath],
1467
+ ]),
1468
+ '',
1469
+ '## Page Slices',
1470
+ '',
1471
+ renderMarkdownTable(['UID', 'Title', 'Slug'], limitRows(pages, options).map((page) => [
1472
+ shortId(page.frontmatter.localId),
1473
+ page.frontmatter.title ?? '-',
1474
+ page.frontmatter.slug ?? '-',
1475
+ ])),
1476
+ '',
1477
+ '## Preview',
1478
+ '',
1479
+ stripDocMirrorBody(doc.body).split('\n').slice(0, options.style === 'wide' ? 80 : 32).join('\n'),
1480
+ ].join('\n'),
1481
+ footer: commonFooter(doc.relativePath, [doc]),
1482
+ };
1483
+ }
1484
+ function resolveDocPage(workspace, selector) {
1485
+ const split = selector.split('#');
1486
+ if (split.length >= 2) {
1487
+ const doc = resolveRecordSelector(workspace.docs, split[0] ?? '');
1488
+ const pageSelector = split.slice(1).join('#');
1489
+ const pages = workspace.docPages.filter((page) => page.frontmatter.sourcePath === doc.relativePath);
1490
+ return resolveDocPageSelector(pages, pageSelector);
1491
+ }
1492
+ return resolveDocPageSelector(workspace.docPages, selector);
1493
+ }
1494
+ function resolveDocPageSelector(pages, selector) {
1495
+ try {
1496
+ return resolveRecordSelector(pages, selector);
1497
+ }
1498
+ catch (error) {
1499
+ if (!(error instanceof ViewSelectorError) || error.code !== 'missing')
1500
+ throw error;
1501
+ const normalized = slugify(selector);
1502
+ const suffixMatches = pages.filter((page) => {
1503
+ const slug = stringValue(page.frontmatter.slug);
1504
+ const title = slugify(stringValue(page.frontmatter.title) ?? '');
1505
+ return slug === normalized
1506
+ || title === normalized
1507
+ || Boolean(slug?.endsWith(`-${normalized}`))
1508
+ || title.endsWith(`-${normalized}`);
1509
+ });
1510
+ if (suffixMatches.length === 1)
1511
+ return suffixMatches[0];
1512
+ if (suffixMatches.length > 1) {
1513
+ throw new ViewSelectorError('ambiguous', selector, suffixMatches.map(candidateFromRecord));
1514
+ }
1515
+ throw error;
1516
+ }
1517
+ }
1518
+ async function buildDocPageView(context, options, selector) {
1519
+ const workspace = await loadWorkspace(context);
1520
+ const page = resolveDocPage(workspace, selector);
1521
+ return {
1522
+ result: {
1523
+ page: materializeRecord(page),
1524
+ },
1525
+ markdown: [
1526
+ `# Doc Page: ${recordLabel(page)}`,
1527
+ '',
1528
+ renderKeyValues([
1529
+ ['uid', page.frontmatter.localId],
1530
+ ['document', page.frontmatter.documentTitle],
1531
+ ['documentVersionId', page.frontmatter.documentVersionId],
1532
+ ['sourcePath', page.frontmatter.sourcePath],
1533
+ ]),
1534
+ '',
1535
+ page.body,
1536
+ ].join('\n'),
1537
+ footer: commonFooter(String(page.frontmatter.sourcePath ?? page.relativePath), [page]),
1538
+ };
1539
+ }
1540
+ async function buildDigestView(context, options, digestOptions) {
1541
+ const depth = digestOptions.depth === 'deep' ? 'deep' : 'shallow';
1542
+ const maxItems = depth === 'deep' ? options.maxItems : Math.min(options.maxItems, 8);
1543
+ const digestViewOptions = { ...options, maxItems };
1544
+ if (digestOptions.project)
1545
+ return buildDashboardView(context, digestViewOptions);
1546
+ if (digestOptions.board)
1547
+ return buildBoardView(context, digestViewOptions, digestOptions.board);
1548
+ if (digestOptions.task)
1549
+ return buildTaskView(context, digestViewOptions, digestOptions.task);
1550
+ if (digestOptions.sprint)
1551
+ return buildSprintView(context, digestViewOptions, digestOptions.sprint);
1552
+ return buildDashboardView(context, digestViewOptions);
1553
+ }
1554
+ export async function runViewProjects(options = {}) {
1555
+ await emitView('view.projects', options, buildProjectsView);
1556
+ }
1557
+ export async function runViewHostedProjects(options = {}) {
1558
+ await emitHostedView('view.hosted-projects', options, buildHostedProjectsView);
1559
+ }
1560
+ export async function runViewHostedInventory(options = {}) {
1561
+ await emitHostedView('view.hosted-inventory', options, buildHostedInventoryView);
1562
+ }
1563
+ export async function runViewProject(selector, options = {}) {
1564
+ await emitView('view.project', options, (context, viewOptions) => buildProjectView(context, viewOptions, selector));
1565
+ }
1566
+ export async function runViewDashboard(options = {}) {
1567
+ await emitView('view.dashboard', options, buildDashboardView);
1568
+ }
1569
+ export async function runViewBoards(options = {}) {
1570
+ await emitView('view.boards', options, async (context, viewOptions) => {
1571
+ const workspace = await loadWorkspace(context);
1572
+ const records = visibleArchiveRecords(workspace.boards, viewOptions);
1573
+ return listView(context, viewOptions, 'Projectman Boards', repoFirstSource(context, 'projectman', 'boards'), records, 'Boards', undefined, archiveResultMeta(workspace.boards, records, viewOptions));
1574
+ });
1575
+ }
1576
+ export async function runViewBoard(selector, options = {}) {
1577
+ await emitView('view.board', options, (context, viewOptions) => buildBoardView(context, viewOptions, selector));
1578
+ }
1579
+ export async function runViewTasks(options = {}) {
1580
+ await emitView('view.tasks', options, async (context, viewOptions) => {
1581
+ const workspace = await loadWorkspace(context);
1582
+ const filter = pmFilterFrom(options);
1583
+ const taskColumnResolver = (record) => resolveTaskColumnName(record, workspace.boards);
1584
+ const filtered = applyPmFilters(workspace.tasks, workspace, filter, { taskColumnResolver });
1585
+ return listView(context, viewOptions, filterTitle('Projectman Tasks', filter), repoFirstSource(context, 'projectman', 'kanban-tasks'), filtered, 'Tasks', taskColumnResolver);
1586
+ });
1587
+ }
1588
+ export async function runViewTask(selector, options = {}) {
1589
+ await emitView('view.task', options, (context, viewOptions) => buildTaskView(context, viewOptions, selector));
1590
+ }
1591
+ export async function runViewSprints(options = {}) {
1592
+ await emitView('view.sprints', options, async (context, viewOptions) => {
1593
+ const workspace = await loadWorkspace(context);
1594
+ const filter = pmFilterFrom(options);
1595
+ const allFiltered = applyPmFilters(workspace.sprints, workspace, filter);
1596
+ const filtered = visibleArchiveRecords(allFiltered, viewOptions);
1597
+ return listView(context, viewOptions, filterTitle('Projectman Sprints', filter), repoFirstSource(context, 'projectman', 'sprints'), filtered, 'Sprints', undefined, archiveResultMeta(allFiltered, filtered, viewOptions));
1598
+ });
1599
+ }
1600
+ export async function runViewSprint(selector, options = {}) {
1601
+ await emitView('view.sprint', options, (context, viewOptions) => buildSprintView(context, viewOptions, selector));
1602
+ }
1603
+ export async function runViewIssues(options = {}) {
1604
+ await emitView('view.issues', options, async (context, viewOptions) => {
1605
+ const workspace = await loadWorkspace(context);
1606
+ const filter = pmFilterFrom(options);
1607
+ const filtered = applyPmFilters(workspace.issues, workspace, filter);
1608
+ return listView(context, viewOptions, filterTitle('Projectman Issues', filter), repoFirstSource(context, 'projectman', 'issues'), filtered, 'Issues');
1609
+ });
1610
+ }
1611
+ export async function runViewFeedback(options = {}) {
1612
+ await emitView('view.feedback', options, async (context, viewOptions) => {
1613
+ const workspace = await loadWorkspace(context);
1614
+ const filter = pmFilterFrom(options);
1615
+ const filtered = applyPmFilters(workspace.feedback, workspace, filter);
1616
+ return listView(context, viewOptions, filterTitle('Projectman Feedback', filter), repoFirstSource(context, 'projectman', 'feedback'), filtered, 'Feedback');
1617
+ });
1618
+ }
1619
+ export async function runViewReviewRequests(options = {}) {
1620
+ await emitView('view.review-requests', options, async (context, viewOptions) => {
1621
+ const workspace = await loadWorkspace(context);
1622
+ const filter = pmFilterFrom(options);
1623
+ const filtered = applyPmFilters(workspace.reviewRequests, workspace, filter);
1624
+ return listView(context, viewOptions, filterTitle('Projectman Review Requests', filter), repoFirstSource(context, 'projectman', 'review-requests'), filtered, 'Review Requests');
1625
+ });
1626
+ }
1627
+ function memoryFilterFrom(options) {
1628
+ return {
1629
+ durability: stringValue(options.durability),
1630
+ kind: stringValue(options.kind),
1631
+ subject: stringValue(options.subject),
1632
+ id: stringValue(options.id),
1633
+ };
1634
+ }
1635
+ export async function runViewMemory(options = {}) {
1636
+ await emitView('view.memory', options, (context, viewOptions) => buildMemoryView(context, viewOptions, { resumeOnly: false, filter: memoryFilterFrom(options) }));
1637
+ }
1638
+ export async function runViewResume(options = {}) {
1639
+ await emitView('view.resume', options, (context, viewOptions) => buildMemoryView(context, viewOptions, { resumeOnly: true, filter: memoryFilterFrom(options) }));
1640
+ }
1641
+ export async function runViewDiscussions(options = {}) {
1642
+ await emitView('view.discussions', options, (context, viewOptions) => buildDiscussionsView(context, viewOptions, {
1643
+ status: stringValue(options.status),
1644
+ agent: stringValue(options.agent),
1645
+ }));
1646
+ }
1647
+ export async function runViewDiscussion(selector, options = {}) {
1648
+ await emitView('view.discussion', options, (context, viewOptions) => buildDiscussionView(context, viewOptions, selector));
1649
+ }
1650
+ export async function runViewExperience(options = {}) {
1651
+ await emitView('view.experience', options, (context, viewOptions) => buildExperienceView(context, viewOptions, {
1652
+ type: stringValue(options.type),
1653
+ area: stringValue(options.area),
1654
+ }));
1655
+ }
1656
+ export async function runViewSkills(options = {}) {
1657
+ await emitView('view.skills', options, async (context, viewOptions) => {
1658
+ const workspace = await loadWorkspace(context);
1659
+ return hostedView('Hosted Skills Mirror', '.aops/hosted/skills', workspace.skills, viewOptions);
1660
+ });
1661
+ }
1662
+ export async function runViewPrompts(options = {}) {
1663
+ await emitView('view.prompts', options, async (context, viewOptions) => {
1664
+ const workspace = await loadWorkspace(context);
1665
+ return hostedView('Hosted Prompts Mirror', '.aops/hosted/prompts', workspace.prompts, viewOptions);
1666
+ });
1667
+ }
1668
+ export async function runViewDocs(options = {}) {
1669
+ await emitView('view.docs', options, async (context, viewOptions) => {
1670
+ const workspace = await loadWorkspace(context);
1671
+ return {
1672
+ result: {
1673
+ count: workspace.docs.length,
1674
+ data: workspace.docs.map(materializeRecord),
1675
+ },
1676
+ markdown: ['# Docman Mirror Documents', '', docTable(workspace.docs, viewOptions)].join('\n'),
1677
+ footer: commonFooter('.aops/docman', workspace.docs),
1678
+ };
1679
+ });
1680
+ }
1681
+ export async function runViewDoc(selector, options = {}) {
1682
+ await emitView('view.doc', options, (context, viewOptions) => buildDocView(context, viewOptions, selector));
1683
+ }
1684
+ export async function runViewDocPage(selector, options = {}) {
1685
+ await emitView('view.doc-page', options, (context, viewOptions) => buildDocPageView(context, viewOptions, selector));
1686
+ }
1687
+ export async function runViewDigest(options = {}) {
1688
+ await emitView('view.digest', options, (context, viewOptions) => buildDigestView(context, viewOptions, options));
1689
+ }
1690
+ function addNoArg(parent, name, description, runner) {
1691
+ const command = addViewOptions(parent.command(name).description(description));
1692
+ command.action(async () => {
1693
+ await runner(commandOptions(command));
1694
+ });
1695
+ return command;
1696
+ }
1697
+ function addOneArg(parent, name, description, runner) {
1698
+ const command = addViewOptions(parent.command(`${name} <selector>`).description(description));
1699
+ command.action(async (selector) => {
1700
+ await runner(selector, commandOptions(command));
1701
+ });
1702
+ return command;
1703
+ }
1704
+ export function makeViewCommand() {
1705
+ const cmd = addViewOptions(new Command('view').description('Read-only AOPS markdown/JSON presentation views'));
1706
+ addNoArg(cmd, 'projects', 'List repo-bound projects and local counts', runViewProjects);
1707
+ addNoArg(cmd, 'hosted-projects', 'List hosted Agentspace projects', runViewHostedProjects);
1708
+ const hostedInventory = addViewOptions(cmd.command('hosted-inventory').description('Show hosted project inventory grouped by docs, skills, prompts, and resources'))
1709
+ .option('--hosted-project <selector>', 'Filter by hosted project id, slug, name, or 8+ char prefix');
1710
+ hostedInventory.action(async () => {
1711
+ await runViewHostedInventory(commandOptions(hostedInventory));
1712
+ });
1713
+ const project = addViewOptions(cmd.command('project [selector]').description('Show current or selected repo-bound project'));
1714
+ project.action(async (selector) => {
1715
+ await runViewProject(selector, commandOptions(project));
1716
+ });
1717
+ addNoArg(cmd, 'dashboard', 'Show repo-first dashboard across local AOPS workspaces', runViewDashboard).addHelpText('after', `
1718
+ Examples:
1719
+ aops-cli view dashboard --style agent --max-items 15
1720
+ aops-cli view digest --project --depth shallow
1721
+
1722
+ Notes:
1723
+ Use dashboard for a first scan across boards, tasks, memory, issues, and recent discussions.
1724
+ Use view digest for handoff/resume context; digest is scoped and usually cheaper than a full dashboard.
1725
+ Dashboard is read-only and reads local .aops/** files only.
1726
+ `);
1727
+ addNoArg(cmd, 'boards', 'List Projectman boards from .aops/projectman', runViewBoards);
1728
+ addOneArg(cmd, 'board', 'Show a Projectman board with tasks and sprints', runViewBoard);
1729
+ const tasksCmd = addViewOptions(cmd.command('tasks').description('List Projectman kanban tasks'))
1730
+ .option('--board <selector>', 'Filter by board slug, name, id, or 8+ char prefix')
1731
+ .option('--status <value>', 'Filter by column name/slug (e.g. Done, todo, ops-doing)');
1732
+ tasksCmd.action(async () => {
1733
+ await runViewTasks(commandOptions(tasksCmd));
1734
+ });
1735
+ addOneArg(cmd, 'task', 'Show a Projectman kanban task with relations', runViewTask).addHelpText('after', `
1736
+ Examples:
1737
+ aops-cli view task <selector>
1738
+ aops-cli view digest --task <selector> --depth shallow
1739
+
1740
+ Notes:
1741
+ Selector accepts full UUID, 8+ char prefix, slug, or exact title; ambiguous selectors list candidates.
1742
+ This command inspects task relations only. Use aops-cli pm ktask ... for planning mutations.
1743
+ `);
1744
+ const sprintsCmd = addViewOptions(cmd.command('sprints').description('List Projectman sprints'))
1745
+ .option('--board <selector>', 'Filter by board')
1746
+ .option('--status <value>', 'Filter by sprint status (todo, doing, completed, ...)');
1747
+ sprintsCmd.action(async () => {
1748
+ await runViewSprints(commandOptions(sprintsCmd));
1749
+ });
1750
+ addOneArg(cmd, 'sprint', 'Show a Projectman sprint with plan and relations', runViewSprint).addHelpText('after', `
1751
+ Examples:
1752
+ aops-cli view sprint <selector> --max-items 20
1753
+ aops-cli view digest --sprint <selector> --depth shallow
1754
+
1755
+ Notes:
1756
+ Selector accepts full UUID, 8+ char prefix, slug, or exact title; ambiguous selectors list candidates.
1757
+ This command inspects sprint plan, microtasks, issues, feedback, review requests, and memory without mutating PM state.
1758
+ Use aops-cli pm utask update ... for granular progress; pm sprint set-status bulk-rewrites nested microtask status.
1759
+ `);
1760
+ const issuesCmd = addViewOptions(cmd.command('issues').description('List Projectman issues'))
1761
+ .option('--status <value>', 'Filter by issue status (open, resolved, ...)')
1762
+ .option('--severity <value>', 'Filter by severity (low, medium, high, critical)')
1763
+ .option('--board <selector>', 'Filter by board')
1764
+ .option('--sprint <selector>', 'Filter by sprint')
1765
+ .option('--task <selector>', 'Filter by kanban task');
1766
+ issuesCmd.action(async () => {
1767
+ await runViewIssues(commandOptions(issuesCmd));
1768
+ });
1769
+ const feedbackCmd = addViewOptions(cmd.command('feedback').description('List Projectman feedback'))
1770
+ .option('--status <value>', 'Filter by feedback status')
1771
+ .option('--board <selector>', 'Filter by board')
1772
+ .option('--sprint <selector>', 'Filter by sprint')
1773
+ .option('--task <selector>', 'Filter by kanban task');
1774
+ feedbackCmd.action(async () => {
1775
+ await runViewFeedback(commandOptions(feedbackCmd));
1776
+ });
1777
+ const reviewRequestsCmd = addViewOptions(cmd.command('review-requests').alias('review-request').description('List Projectman review requests'))
1778
+ .option('--status <value>', 'Filter by review request status')
1779
+ .option('--board <selector>', 'Filter by board')
1780
+ .option('--sprint <selector>', 'Filter by sprint')
1781
+ .option('--task <selector>', 'Filter by kanban task');
1782
+ reviewRequestsCmd.action(async () => {
1783
+ await runViewReviewRequests(commandOptions(reviewRequestsCmd));
1784
+ });
1785
+ const memory = addViewOptions(cmd.command('memory').description('List local Agentspace memory items'))
1786
+ .option('--durability <value>', 'Filter by durability: short, durable, sticky')
1787
+ .option('--kind <value>', 'Filter by kind: kickoff, resume, closeout, decision, blocker, note, rule, summary')
1788
+ .option('--subject <value>', 'Filter by subject: project, board, sprint, task, ktask, utask, issue, feedback, review-request')
1789
+ .option('--id <value>', 'Filter by subject id (full UUID or 8+ char prefix)');
1790
+ memory.action(async () => {
1791
+ await runViewMemory(commandOptions(memory));
1792
+ });
1793
+ const resume = addViewOptions(cmd.command('resume').description('Show local resume/kickoff/closeout memory items'))
1794
+ .option('--durability <value>', 'Filter by durability')
1795
+ .option('--kind <value>', 'Filter by kind')
1796
+ .option('--subject <value>', 'Filter by subject category')
1797
+ .option('--id <value>', 'Filter by subject id');
1798
+ resume.action(async () => {
1799
+ await runViewResume(commandOptions(resume));
1800
+ });
1801
+ const discussionsCmd = addViewOptions(cmd.command('discussions').description('List local discussion topics'))
1802
+ .option('--status <value>', 'Filter by topic status (active, concluding, concluded, abandoned)')
1803
+ .option('--agent <value>', 'Filter by participant agent id');
1804
+ discussionsCmd.action(async () => {
1805
+ await runViewDiscussions(commandOptions(discussionsCmd));
1806
+ });
1807
+ addOneArg(cmd, 'discussion', 'Show a discussion topic with turns and outputs', runViewDiscussion);
1808
+ const experienceCmd = addViewOptions(cmd.command('experience').description('List local Agentspace experience items'))
1809
+ .option('--type <value>', 'Filter by experience type (technique, problem-solution, tool, script, idea, ...)')
1810
+ .option('--area <value>', 'Filter by area tag');
1811
+ experienceCmd.action(async () => {
1812
+ await runViewExperience(commandOptions(experienceCmd));
1813
+ });
1814
+ addNoArg(cmd, 'skills', 'List read-only hosted skill mirrors', runViewSkills);
1815
+ addNoArg(cmd, 'prompts', 'List read-only hosted prompt mirrors', runViewPrompts);
1816
+ addNoArg(cmd, 'docs', 'List read-only Docman document mirrors', runViewDocs);
1817
+ addOneArg(cmd, 'doc', 'Show a Docman mirror document', runViewDoc);
1818
+ addOneArg(cmd, 'doc-page', 'Show a heading slice from Docman mirror markdown', runViewDocPage);
1819
+ const digest = addViewOptions(cmd.command('digest').description('Compose a bounded repo-first context pack'))
1820
+ .option('--project', 'Build project dashboard digest')
1821
+ .option('--board <selector>', 'Build board digest')
1822
+ .option('--task <selector>', 'Build task digest')
1823
+ .option('--sprint <selector>', 'Build sprint digest')
1824
+ .option('--depth <depth>', 'Digest depth: shallow or deep', 'shallow');
1825
+ digest.action(async () => {
1826
+ await runViewDigest(commandOptions(digest));
1827
+ });
1828
+ digest.addHelpText('after', `
1829
+ Examples:
1830
+ aops-cli view digest --sprint <selector> --depth shallow
1831
+ aops-cli view digest --task <selector> --depth deep --max-bytes 32768
1832
+ aops-cli view digest --board <board-slug> --depth shallow
1833
+
1834
+ Notes:
1835
+ Prefer one scope flag: --sprint, --task, --board, or --project.
1836
+ Shallow is the default handoff/resume pack; deep is for fuller local context and still respects --max-bytes.
1837
+ Digest is read-only and does not sync, mutate, refresh mirrors, or write cache files.
1838
+ `);
1839
+ cmd.addHelpText('after', `
1840
+ Examples:
1841
+ aops-cli view dashboard --style agent
1842
+ aops-cli view hosted-projects --style compact
1843
+ aops-cli view hosted-inventory --hosted-project aops --style compact
1844
+ aops-cli view sprint <selector> --max-items 20
1845
+ aops-cli view digest --task <selector> --depth deep --max-bytes 32768
1846
+
1847
+ Notes:
1848
+ local-cache view commands use local .aops/** files only.
1849
+ hosted view commands call read-only hosted list APIs and do not sync, mutate, or write cache files.
1850
+ `);
1851
+ return cmd;
1852
+ }