@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,537 @@
1
+ import { randomUUID } from 'node:crypto';
2
+ import fs from 'node:fs/promises';
3
+ import path from 'node:path';
4
+ import { setTimeout as delay } from 'node:timers/promises';
5
+ import { compactPayload, normalizeNonEmpty } from './command.js';
6
+ import { resolveRepoFirstWorkspaceRoot } from './repo-first-storage.js';
7
+ import { writeFileWithRetry } from './transient-fs.js';
8
+ const MEMORY_WORKSPACE_LOCK_RETRY_MS = 75;
9
+ const MEMORY_WORKSPACE_LOCK_TIMEOUT_MS = 15_000;
10
+ const MEMORY_WORKSPACE_LOCK_STALE_MS = 30_000;
11
+ function isRecord(value) {
12
+ return value !== null && typeof value === 'object' && !Array.isArray(value);
13
+ }
14
+ function toStringArray(value) {
15
+ return Array.isArray(value)
16
+ ? value.map((entry) => normalizeNonEmpty(entry)).filter((entry) => Boolean(entry))
17
+ : [];
18
+ }
19
+ function uniqueStrings(values) {
20
+ const seen = new Set();
21
+ const result = [];
22
+ for (const value of values) {
23
+ const normalized = normalizeNonEmpty(value);
24
+ if (!normalized)
25
+ continue;
26
+ const key = normalized.toLowerCase();
27
+ if (seen.has(key))
28
+ continue;
29
+ seen.add(key);
30
+ result.push(normalized);
31
+ }
32
+ return result;
33
+ }
34
+ function slugify(value) {
35
+ return value
36
+ .toLowerCase()
37
+ .replace(/[^a-z0-9]+/g, '-')
38
+ .replace(/^-+|-+$/g, '')
39
+ .slice(0, 80) || 'item';
40
+ }
41
+ function computeTimestampForFile(isoValue) {
42
+ const date = isoValue ? new Date(isoValue) : new Date();
43
+ if (Number.isNaN(date.getTime()))
44
+ return 'unknown-time';
45
+ const year = String(date.getUTCFullYear());
46
+ const month = String(date.getUTCMonth() + 1).padStart(2, '0');
47
+ const day = String(date.getUTCDate()).padStart(2, '0');
48
+ const hour = String(date.getUTCHours()).padStart(2, '0');
49
+ const minute = String(date.getUTCMinutes()).padStart(2, '0');
50
+ const second = String(date.getUTCSeconds()).padStart(2, '0');
51
+ return `${year}${month}${day}-${hour}${minute}${second}`;
52
+ }
53
+ function encodeFrontmatterValue(value) {
54
+ return JSON.stringify(value);
55
+ }
56
+ export function renderFrontmatterDocument(frontmatter, body) {
57
+ const lines = ['---'];
58
+ Object.entries(frontmatter).forEach(([key, value]) => {
59
+ if (value === undefined)
60
+ return;
61
+ lines.push(`${key}: ${encodeFrontmatterValue(value)}`);
62
+ });
63
+ lines.push('---', '');
64
+ return `${lines.join('\n')}${body.trim() ? `${body.trim()}\n` : ''}`;
65
+ }
66
+ export function parseFrontmatterDocument(content) {
67
+ const normalized = content.replace(/\r\n/g, '\n');
68
+ if (!normalized.startsWith('---\n')) {
69
+ return { frontmatter: {}, body: normalized.trim() };
70
+ }
71
+ const endIndex = normalized.indexOf('\n---\n', 4);
72
+ if (endIndex < 0) {
73
+ return { frontmatter: {}, body: normalized.trim() };
74
+ }
75
+ const rawFrontmatter = normalized.slice(4, endIndex);
76
+ const body = normalized.slice(endIndex + 5);
77
+ const frontmatter = {};
78
+ for (const line of rawFrontmatter.split('\n')) {
79
+ const separator = line.indexOf(':');
80
+ if (separator < 0)
81
+ continue;
82
+ const key = line.slice(0, separator).trim();
83
+ const rawValue = line.slice(separator + 1).trim();
84
+ if (!key)
85
+ continue;
86
+ if (!rawValue) {
87
+ frontmatter[key] = '';
88
+ continue;
89
+ }
90
+ try {
91
+ frontmatter[key] = JSON.parse(rawValue);
92
+ }
93
+ catch {
94
+ frontmatter[key] = rawValue;
95
+ }
96
+ }
97
+ return { frontmatter, body: body.trim() };
98
+ }
99
+ export function normalizeHostedMemoryProjection(record, projectId) {
100
+ const meta = isRecord(record.meta) ? record.meta : {};
101
+ const subjectType = normalizeNonEmpty(meta.subjectType) ?? normalizeNonEmpty(record.sourceType);
102
+ const subjectId = normalizeNonEmpty(meta.subjectId) ?? normalizeNonEmpty(record.sourceId);
103
+ const subjectTitle = normalizeNonEmpty(meta.subjectTitle);
104
+ const areas = uniqueStrings([...toStringArray(meta.areas), normalizeNonEmpty(meta.area)]);
105
+ const statuses = uniqueStrings([...toStringArray(meta.status), normalizeNonEmpty(meta.status)]);
106
+ return {
107
+ id: normalizeNonEmpty(record.id),
108
+ memoryId: normalizeNonEmpty(record.id),
109
+ kind: normalizeNonEmpty(record.kind),
110
+ durability: normalizeNonEmpty(record.durability),
111
+ subjectType,
112
+ subjectId,
113
+ subjectTitle,
114
+ projectId: normalizeNonEmpty(meta.projectId) ?? projectId,
115
+ importance: typeof record.importance === 'number' ? record.importance : undefined,
116
+ createdAt: normalizeNonEmpty(record.createdAt),
117
+ updatedAt: normalizeNonEmpty(record.updatedAt),
118
+ nextAction: normalizeNonEmpty(meta.nextAction),
119
+ validationState: normalizeNonEmpty(meta.validationState),
120
+ sourceRefs: Array.isArray(meta.sourceRefs) ? meta.sourceRefs : undefined,
121
+ nextReadRefs: Array.isArray(meta.nextReadRefs) ? meta.nextReadRefs : undefined,
122
+ purpose: toStringArray(meta.purpose),
123
+ areas,
124
+ status: statuses,
125
+ tags: toStringArray(record.tags),
126
+ storage: 'hosted',
127
+ content: normalizeNonEmpty(record.content) ?? '',
128
+ raw: record,
129
+ };
130
+ }
131
+ export function normalizeLocalMemoryEntry(frontmatter, body) {
132
+ return {
133
+ id: normalizeNonEmpty(frontmatter.memoryId) ?? normalizeNonEmpty(frontmatter.localId),
134
+ memoryId: normalizeNonEmpty(frontmatter.memoryId) ?? normalizeNonEmpty(frontmatter.localId),
135
+ kind: normalizeNonEmpty(frontmatter.kind),
136
+ durability: normalizeNonEmpty(frontmatter.durability),
137
+ subjectType: normalizeNonEmpty(frontmatter.subjectType),
138
+ subjectId: normalizeNonEmpty(frontmatter.subjectId),
139
+ subjectTitle: normalizeNonEmpty(frontmatter.subjectTitle),
140
+ projectId: normalizeNonEmpty(frontmatter.projectId),
141
+ importance: typeof frontmatter.importance === 'number' ? frontmatter.importance : undefined,
142
+ createdAt: normalizeNonEmpty(frontmatter.createdAt),
143
+ updatedAt: normalizeNonEmpty(frontmatter.updatedAt),
144
+ nextAction: normalizeNonEmpty(frontmatter.nextAction),
145
+ validationState: normalizeNonEmpty(frontmatter.validationState),
146
+ sourceRefs: Array.isArray(frontmatter.sourceRefs) ? frontmatter.sourceRefs : undefined,
147
+ nextReadRefs: Array.isArray(frontmatter.nextReadRefs) ? frontmatter.nextReadRefs : undefined,
148
+ purpose: toStringArray(frontmatter.purpose),
149
+ areas: toStringArray(frontmatter.areas),
150
+ status: toStringArray(frontmatter.status),
151
+ tags: toStringArray(frontmatter.tags),
152
+ storage: normalizeNonEmpty(frontmatter.storage) ?? 'local-cache',
153
+ content: body.trim(),
154
+ raw: frontmatter,
155
+ };
156
+ }
157
+ export function buildMemoryFrontmatter(entry) {
158
+ const raw = entry.raw ?? {};
159
+ return compactPayload({
160
+ ...raw,
161
+ schemaVersion: 2,
162
+ entityType: 'agentspace.memory-item',
163
+ localId: entry.memoryId ?? entry.id,
164
+ memoryId: entry.memoryId ?? entry.id,
165
+ remoteId: normalizeNonEmpty(raw.remoteId),
166
+ kind: entry.kind,
167
+ durability: entry.durability,
168
+ subjectType: entry.subjectType,
169
+ subjectId: entry.subjectId,
170
+ subjectTitle: entry.subjectTitle,
171
+ projectId: entry.projectId,
172
+ scopeId: normalizeNonEmpty(raw.scopeId),
173
+ importance: entry.importance,
174
+ createdAt: entry.createdAt,
175
+ updatedAt: entry.updatedAt,
176
+ nextAction: entry.nextAction,
177
+ validationState: entry.validationState,
178
+ sourceRefs: entry.sourceRefs,
179
+ nextReadRefs: entry.nextReadRefs,
180
+ purpose: entry.purpose.length > 0 ? entry.purpose : undefined,
181
+ areas: entry.areas.length > 0 ? entry.areas : undefined,
182
+ status: entry.status.length > 0 ? entry.status : undefined,
183
+ tags: entry.tags.length > 0 ? entry.tags : undefined,
184
+ syncState: normalizeNonEmpty(entry.raw?.syncState) ?? 'local',
185
+ remoteUpdatedAt: normalizeNonEmpty(raw.remoteUpdatedAt),
186
+ lastPulledAt: normalizeNonEmpty(raw.lastPulledAt),
187
+ lastPushedAt: normalizeNonEmpty(raw.lastPushedAt),
188
+ baseHash: normalizeNonEmpty(raw.baseHash),
189
+ storage: entry.storage ?? 'local-cache',
190
+ });
191
+ }
192
+ export function buildMemoryItemDocument(entry) {
193
+ return renderFrontmatterDocument(buildMemoryFrontmatter(entry), entry.content);
194
+ }
195
+ export function buildMemoryFilename(entry) {
196
+ const timestamp = computeTimestampForFile(entry.createdAt ?? entry.updatedAt);
197
+ const kind = slugify(entry.kind ?? 'memory');
198
+ const subject = slugify((entry.subjectType ?? 'subject').replace(/\./g, '-'));
199
+ const shortId = (entry.memoryId ?? entry.id ?? randomUUID()).slice(0, 8);
200
+ return `${timestamp}-${kind}-${subject}-${shortId}.md`;
201
+ }
202
+ function summarizeContent(content, maxLength = 120) {
203
+ const compact = content.replace(/\s+/g, ' ').trim();
204
+ if (compact.length <= maxLength)
205
+ return compact || 'No content.';
206
+ return `${compact.slice(0, Math.max(0, maxLength - 3)).trimEnd()}...`;
207
+ }
208
+ function buildItemLinkLine(rootRelativePath, entry) {
209
+ const subjectLabel = entry.subjectTitle
210
+ ? `${entry.subjectType ?? 'subject'} (${entry.subjectTitle})`
211
+ : (entry.subjectType ?? 'subject');
212
+ const parts = [
213
+ entry.kind ?? 'memory',
214
+ entry.durability ?? 'durable',
215
+ subjectLabel,
216
+ entry.updatedAt ?? entry.createdAt ?? 'unknown',
217
+ ];
218
+ if (entry.importance !== undefined)
219
+ parts.push(`importance=${entry.importance}`);
220
+ return `- [${entry.kind ?? 'memory'} / ${subjectLabel}](${rootRelativePath}) — ${parts.join(' — ')}\n ${summarizeContent(entry.content)}`;
221
+ }
222
+ function buildCollectionMarkdown(title, intro, items) {
223
+ const lines = [`# ${title}`, '', `> ${intro}`, ''];
224
+ if (items.length === 0) {
225
+ lines.push('- No projected memory items in this view.');
226
+ return `${lines.join('\n')}\n`;
227
+ }
228
+ items.forEach(({ linkPath, entry }) => {
229
+ lines.push(buildItemLinkLine(linkPath, entry));
230
+ });
231
+ return `${lines.join('\n')}\n`;
232
+ }
233
+ function buildSubjectViewFileName(subjectType, subjectId) {
234
+ return `${slugify((subjectType ?? 'subject').replace(/\./g, '-'))}-${slugify(subjectId ?? 'unknown')}.md`;
235
+ }
236
+ function classifyOwner(subjectType) {
237
+ switch ((subjectType ?? '').toLowerCase()) {
238
+ case 'projectman.plan':
239
+ return 'project';
240
+ case 'projectman.kanban-task':
241
+ return 'ktask';
242
+ case 'projectman.sprint':
243
+ return 'sprint';
244
+ case 'projectman.phase':
245
+ return 'phase';
246
+ case 'projectman.microtask':
247
+ return 'utask';
248
+ case 'projectman.issue':
249
+ return 'issue';
250
+ case 'projectman.feedback':
251
+ return 'feedback';
252
+ default:
253
+ return undefined;
254
+ }
255
+ }
256
+ function classifyFlow(entry) {
257
+ const kind = (entry.kind ?? '').toLowerCase();
258
+ if (kind === 'checkpoint')
259
+ return 'checkpoint';
260
+ if (kind === 'constraint')
261
+ return 'blocker';
262
+ if (kind === 'resume')
263
+ return 'resume';
264
+ if (['kickoff', 'closeout', 'decision', 'rule', 'note'].includes(kind))
265
+ return kind;
266
+ return undefined;
267
+ }
268
+ function sortEntries(items) {
269
+ return [...items].sort((left, right) => (right.updatedAt ?? right.createdAt ?? '').localeCompare(left.updatedAt ?? left.createdAt ?? ''));
270
+ }
271
+ function buildWorkspaceIndexMarkdown(params) {
272
+ const lines = [`# ${params.title}`, '', `> ${params.intro}`, ''];
273
+ if (params.resumePack) {
274
+ lines.push('## Resume Summary', params.resumePack.resumeSummary?.trim() || 'No project resume summary available.', '');
275
+ lines.push('## Sticky Guidance');
276
+ if (params.resumePack.bootstrapGuidance.length === 0) {
277
+ lines.push('- No sticky guidance available.');
278
+ }
279
+ else {
280
+ params.resumePack.bootstrapGuidance.forEach((entry) => lines.push(`- ${entry}`));
281
+ }
282
+ lines.push('');
283
+ }
284
+ lines.push('## Quick Views');
285
+ lines.push(`- [Sticky Memory](sticky.md) — ${params.items.filter((entry) => entry.durability === 'sticky').length} item(s)`);
286
+ lines.push(`- [Recent Memory](recent.md) — ${Math.min(10, params.items.length)} item(s)`);
287
+ lines.push('- [By Durability](by-durability/short.md) / [durable](by-durability/durable.md) / [sticky](by-durability/sticky.md)');
288
+ lines.push('- [By Flow](by-flow/handoff.md) / [kickoff](by-flow/kickoff.md) / [closeout](by-flow/closeout.md)');
289
+ lines.push('- [By Owner](by-owner/project.md) / [sprint](by-owner/sprint.md) / [ktask](by-owner/ktask.md)');
290
+ lines.push('', '## Projected Memory Items');
291
+ if (params.items.length === 0) {
292
+ lines.push('- No memory items available.');
293
+ }
294
+ else {
295
+ sortEntries(params.items).forEach((entry) => {
296
+ lines.push(buildItemLinkLine(path.join('items', buildMemoryFilename(entry)).split(path.sep).join('/'), entry));
297
+ });
298
+ }
299
+ return `${lines.join('\n')}\n`;
300
+ }
301
+ export function resolveMemoryWorkspacePaths(context) {
302
+ const rootDir = path.join(resolveRepoFirstWorkspaceRoot(context), 'agentspace', 'memory');
303
+ return {
304
+ rootDir,
305
+ localDir: rootDir,
306
+ localItemsDir: path.join(rootDir, 'items'),
307
+ };
308
+ }
309
+ export function buildMemoryWorkspaceFiles(params) {
310
+ const sourceItems = sortEntries(params.items);
311
+ const files = [];
312
+ const itemRelativePaths = new Map();
313
+ for (const entry of sourceItems) {
314
+ const itemFileName = buildMemoryFilename(entry);
315
+ itemRelativePaths.set(entry.memoryId ?? entry.id ?? itemFileName, path.join('items', itemFileName).split(path.sep).join('/'));
316
+ files.push({
317
+ relativePath: path.join('items', itemFileName).split(path.sep).join('/'),
318
+ content: buildMemoryItemDocument(entry),
319
+ itemId: entry.memoryId ?? entry.id,
320
+ });
321
+ }
322
+ const toLinks = (items) => sortEntries(items).map((entry) => ({
323
+ linkPath: itemRelativePaths.get(entry.memoryId ?? entry.id ?? '') ?? path.join('items', buildMemoryFilename(entry)).split(path.sep).join('/'),
324
+ entry,
325
+ }));
326
+ files.push({
327
+ relativePath: 'index.md',
328
+ content: buildWorkspaceIndexMarkdown({
329
+ title: 'Local Memory Cache',
330
+ intro: 'Read-only local memory cache (the hosted server is the source of truth). `items/*.md` is a cache mirror refreshed by `sync pull`; edits here are not pushed back.',
331
+ items: sourceItems,
332
+ resumePack: params.resumePack,
333
+ }),
334
+ });
335
+ files.push({
336
+ relativePath: 'sticky.md',
337
+ content: buildCollectionMarkdown('Sticky Memory', 'Local cache of sticky memory for project bootstrap (hosted server is the source of truth).', toLinks(sourceItems.filter((entry) => entry.durability === 'sticky'))),
338
+ });
339
+ files.push({
340
+ relativePath: 'recent.md',
341
+ content: buildCollectionMarkdown('Recent Memory', 'Most recently updated cached memory items (hosted server is the source of truth).', toLinks(sourceItems.slice(0, 10))),
342
+ });
343
+ const durabilityBuckets = ['short', 'durable', 'sticky'];
344
+ for (const bucket of durabilityBuckets) {
345
+ files.push({
346
+ relativePath: path.join('by-durability', `${bucket}.md`).split(path.sep).join('/'),
347
+ content: buildCollectionMarkdown(`Durability: ${bucket}`, `Grouped by durability = ${bucket}.`, toLinks(sourceItems.filter((entry) => (entry.durability ?? '').toLowerCase() === bucket))),
348
+ });
349
+ }
350
+ const flowBuckets = [
351
+ { key: 'kickoff', title: 'Flow: kickoff', predicate: (entry) => classifyFlow(entry) === 'kickoff' },
352
+ { key: 'handoff', title: 'Flow: handoff', predicate: (entry) => classifyFlow(entry) === 'resume' },
353
+ { key: 'closeout', title: 'Flow: closeout', predicate: (entry) => classifyFlow(entry) === 'closeout' },
354
+ { key: 'decision', title: 'Flow: decision', predicate: (entry) => classifyFlow(entry) === 'decision' },
355
+ { key: 'blocker', title: 'Flow: blocker', predicate: (entry) => classifyFlow(entry) === 'blocker' },
356
+ { key: 'checkpoint', title: 'Flow: checkpoint', predicate: (entry) => classifyFlow(entry) === 'checkpoint' },
357
+ { key: 'rule', title: 'Flow: rule', predicate: (entry) => classifyFlow(entry) === 'rule' },
358
+ { key: 'note', title: 'Flow: note', predicate: (entry) => classifyFlow(entry) === 'note' },
359
+ { key: 'resume', title: 'Flow: resume', predicate: (entry) => classifyFlow(entry) === 'resume' },
360
+ ];
361
+ for (const bucket of flowBuckets) {
362
+ files.push({
363
+ relativePath: path.join('by-flow', `${bucket.key}.md`).split(path.sep).join('/'),
364
+ content: buildCollectionMarkdown(bucket.title, `Grouped by flow bucket = ${bucket.key}.`, toLinks(sourceItems.filter(bucket.predicate))),
365
+ });
366
+ }
367
+ const owners = ['project', 'ktask', 'sprint', 'phase', 'utask', 'issue', 'feedback'];
368
+ for (const owner of owners) {
369
+ files.push({
370
+ relativePath: path.join('by-owner', `${owner}.md`).split(path.sep).join('/'),
371
+ content: buildCollectionMarkdown(`Owner: ${owner}`, `Grouped by owner bucket = ${owner}.`, toLinks(sourceItems.filter((entry) => classifyOwner(entry.subjectType) === owner))),
372
+ });
373
+ }
374
+ const subjectGroups = new Map();
375
+ for (const entry of sourceItems) {
376
+ const key = `${entry.subjectType ?? 'subject'}::${entry.subjectId ?? 'unknown'}`;
377
+ const bucket = subjectGroups.get(key) ?? [];
378
+ bucket.push(entry);
379
+ subjectGroups.set(key, bucket);
380
+ }
381
+ for (const [key, items] of subjectGroups.entries()) {
382
+ const [subjectType, subjectId] = key.split('::');
383
+ const subjectTitle = items.find((entry) => entry.subjectTitle)?.subjectTitle;
384
+ files.push({
385
+ relativePath: path.join('subjects', buildSubjectViewFileName(subjectType, subjectId)).split(path.sep).join('/'),
386
+ content: buildCollectionMarkdown(`Subject Memory: ${subjectType}${subjectTitle ? ` / ${subjectTitle}` : ` / ${subjectId}`}`, 'Grouped memory for a single subject.', toLinks(items)),
387
+ });
388
+ }
389
+ return files;
390
+ }
391
+ export async function writeMemoryWorkspaceFiles(baseDir, files) {
392
+ await withMemoryWorkspaceViewLock(baseDir, async () => {
393
+ await fs.rm(path.join(baseDir, 'by-durability'), { recursive: true, force: true });
394
+ await fs.rm(path.join(baseDir, 'by-flow'), { recursive: true, force: true });
395
+ await fs.rm(path.join(baseDir, 'by-owner'), { recursive: true, force: true });
396
+ await fs.rm(path.join(baseDir, 'subjects'), { recursive: true, force: true });
397
+ const expectedPaths = new Set();
398
+ for (const file of files) {
399
+ const filePath = path.join(baseDir, file.relativePath);
400
+ expectedPaths.add(filePath);
401
+ await fs.mkdir(path.dirname(filePath), { recursive: true });
402
+ await writeFileWithRetry(filePath, file.content, 'utf8');
403
+ }
404
+ for (const child of ['by-durability', 'by-flow', 'by-owner', 'subjects']) {
405
+ await fs.mkdir(path.join(baseDir, child), { recursive: true });
406
+ }
407
+ const staleRoots = ['index.md', 'sticky.md', 'recent.md'];
408
+ for (const fileName of staleRoots) {
409
+ if (expectedPaths.has(path.join(baseDir, fileName)))
410
+ continue;
411
+ await fs.rm(path.join(baseDir, fileName), { force: true });
412
+ }
413
+ });
414
+ }
415
+ async function acquireMemoryWorkspaceViewLock(baseDir) {
416
+ await fs.mkdir(baseDir, { recursive: true });
417
+ const lockDir = path.join(baseDir, '.views.lock');
418
+ const startedAt = Date.now();
419
+ for (;;) {
420
+ try {
421
+ await fs.mkdir(lockDir);
422
+ await writeFileWithRetry(path.join(lockDir, 'owner.json'), `${JSON.stringify({ pid: process.pid, createdAt: new Date().toISOString() })}\n`, 'utf8');
423
+ return async () => {
424
+ await fs.rm(lockDir, { recursive: true, force: true });
425
+ };
426
+ }
427
+ catch (error) {
428
+ const code = typeof error === 'object' && error !== null && 'code' in error ? normalizeNonEmpty(error.code) : undefined;
429
+ if (code !== 'EEXIST')
430
+ throw error;
431
+ const stats = await fs.stat(lockDir).catch(() => null);
432
+ if (stats && Date.now() - stats.mtimeMs > MEMORY_WORKSPACE_LOCK_STALE_MS) {
433
+ await fs.rm(lockDir, { recursive: true, force: true }).catch(() => undefined);
434
+ continue;
435
+ }
436
+ if (Date.now() - startedAt > MEMORY_WORKSPACE_LOCK_TIMEOUT_MS) {
437
+ throw new Error('Timed out waiting for memory workspace view lock.');
438
+ }
439
+ await delay(MEMORY_WORKSPACE_LOCK_RETRY_MS);
440
+ }
441
+ }
442
+ }
443
+ async function withMemoryWorkspaceViewLock(baseDir, fn) {
444
+ const release = await acquireMemoryWorkspaceViewLock(baseDir);
445
+ try {
446
+ return await fn();
447
+ }
448
+ finally {
449
+ await release();
450
+ }
451
+ }
452
+ export async function readLocalMemoryEntries(itemsDir) {
453
+ const files = await readLocalMemoryEntryFiles(itemsDir);
454
+ return sortEntries(files.map((entry) => entry.entry));
455
+ }
456
+ export async function readLocalMemoryEntryFiles(itemsDir) {
457
+ try {
458
+ const files = await fs.readdir(itemsDir);
459
+ const entries = [];
460
+ for (const fileName of files.filter((entry) => entry.endsWith('.md')).sort()) {
461
+ const filePath = path.join(itemsDir, fileName);
462
+ const raw = await fs.readFile(filePath, 'utf8');
463
+ const parsed = parseFrontmatterDocument(raw);
464
+ const entry = normalizeLocalMemoryEntry(parsed.frontmatter, parsed.body);
465
+ entries.push({ fileName, filePath, entry });
466
+ }
467
+ return entries.sort((left, right) => (right.entry.updatedAt ?? right.entry.createdAt ?? '').localeCompare(left.entry.updatedAt ?? left.entry.createdAt ?? ''));
468
+ }
469
+ catch {
470
+ return [];
471
+ }
472
+ }
473
+ export async function writeLocalMemoryEntryFile(itemsDir, entry) {
474
+ await fs.mkdir(itemsDir, { recursive: true });
475
+ const fileName = buildMemoryFilename(entry);
476
+ const filePath = path.join(itemsDir, fileName);
477
+ await writeFileWithRetry(filePath, buildMemoryItemDocument(entry), 'utf8');
478
+ return { fileName, filePath };
479
+ }
480
+ export async function overwriteLocalMemoryEntryFile(filePath, entry) {
481
+ await fs.mkdir(path.dirname(filePath), { recursive: true });
482
+ await writeFileWithRetry(filePath, buildMemoryItemDocument(entry), 'utf8');
483
+ }
484
+ export async function removeLocalMemoryEntryFile(itemsDir, memoryId) {
485
+ try {
486
+ const files = await fs.readdir(itemsDir);
487
+ for (const fileName of files.filter((entry) => entry.endsWith('.md'))) {
488
+ const raw = await fs.readFile(path.join(itemsDir, fileName), 'utf8');
489
+ const parsed = parseFrontmatterDocument(raw);
490
+ if (normalizeNonEmpty(parsed.frontmatter.memoryId) === memoryId) {
491
+ await fs.rm(path.join(itemsDir, fileName), { force: true });
492
+ return;
493
+ }
494
+ }
495
+ }
496
+ catch {
497
+ return;
498
+ }
499
+ }
500
+ export async function rebuildLocalMemoryWorkspace(params) {
501
+ const paths = resolveMemoryWorkspacePaths(params);
502
+ await fs.mkdir(paths.localItemsDir, { recursive: true });
503
+ const viewFiles = buildMemoryWorkspaceFiles({
504
+ items: params.items,
505
+ resumePack: params.resumePack,
506
+ }).filter((file) => !file.relativePath.startsWith('items/'));
507
+ await writeMemoryWorkspaceFiles(paths.localDir, viewFiles);
508
+ await fs.mkdir(paths.rootDir, { recursive: true });
509
+ }
510
+ export function createNewLocalMemoryEntry(params) {
511
+ const now = new Date().toISOString();
512
+ const id = randomUUID();
513
+ return {
514
+ id,
515
+ memoryId: id,
516
+ kind: params.kind,
517
+ durability: params.durability,
518
+ subjectType: params.subjectType,
519
+ subjectId: params.subjectId,
520
+ subjectTitle: params.subjectTitle,
521
+ projectId: params.projectId,
522
+ importance: params.importance,
523
+ createdAt: now,
524
+ updatedAt: now,
525
+ nextAction: params.nextAction,
526
+ validationState: params.validationState,
527
+ sourceRefs: params.sourceRefs,
528
+ nextReadRefs: params.nextReadRefs,
529
+ purpose: uniqueStrings(params.purpose ?? []),
530
+ areas: uniqueStrings(params.areas ?? []),
531
+ status: uniqueStrings(params.status ?? []),
532
+ tags: uniqueStrings(params.tags ?? []),
533
+ storage: 'local-cache',
534
+ content: params.content,
535
+ raw: params.raw,
536
+ };
537
+ }