@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,479 @@
1
+ import { compactPayload, normalizeNonEmpty } from './command.js';
2
+ import { readLocalMemoryEntries, resolveMemoryWorkspacePaths, } from './memory-workspace.js';
3
+ function toStringArray(value) {
4
+ if (Array.isArray(value)) {
5
+ return value.map((entry) => normalizeNonEmpty(entry)).filter((entry) => Boolean(entry));
6
+ }
7
+ const normalized = normalizeNonEmpty(value);
8
+ return normalized ? [normalized] : [];
9
+ }
10
+ function isRecord(value) {
11
+ return value !== null && typeof value === 'object' && !Array.isArray(value);
12
+ }
13
+ function toRecordArray(value) {
14
+ return Array.isArray(value) ? value.filter(isRecord) : [];
15
+ }
16
+ function uniqueStrings(values) {
17
+ const seen = new Set();
18
+ const result = [];
19
+ for (const value of values) {
20
+ const normalized = normalizeNonEmpty(value);
21
+ if (!normalized)
22
+ continue;
23
+ const key = normalized.toLowerCase();
24
+ if (seen.has(key))
25
+ continue;
26
+ seen.add(key);
27
+ result.push(normalized);
28
+ }
29
+ return result;
30
+ }
31
+ function tagValue(tags, prefix) {
32
+ const normalizedPrefix = prefix.toLowerCase();
33
+ const match = tags.find((tag) => tag.toLowerCase().startsWith(normalizedPrefix));
34
+ return match ? normalizeNonEmpty(match.slice(prefix.length)) : undefined;
35
+ }
36
+ function hasPlaybookTag(tags) {
37
+ return tags.some((tag) => {
38
+ const normalized = tag.toLowerCase();
39
+ return normalized === 'playbook' || normalized.startsWith('playbook-') || normalized.startsWith('playbook:');
40
+ });
41
+ }
42
+ function contentTitle(content) {
43
+ const first = normalizeNonEmpty(content)?.split(/\r?\n/).map((line) => line.trim()).find(Boolean);
44
+ return first?.replace(/^#+\s*/, '').slice(0, 120);
45
+ }
46
+ function summarizeContent(content, maxLength = 240) {
47
+ const compact = normalizeNonEmpty(content)?.replace(/\s+/g, ' ');
48
+ if (!compact)
49
+ return undefined;
50
+ if (compact.length <= maxLength)
51
+ return compact;
52
+ return `${compact.slice(0, Math.max(0, maxLength - 3)).trimEnd()}...`;
53
+ }
54
+ export function normalizePlaybookScope(value) {
55
+ const normalized = normalizeNonEmpty(value)?.toLowerCase();
56
+ return normalized === 'session' ? 'session' : 'project';
57
+ }
58
+ export function normalizePlaybookReviewState(value) {
59
+ const normalized = normalizeNonEmpty(value)?.toLowerCase();
60
+ if (normalized === 'accepted' || normalized === 'superseded' || normalized === 'archived')
61
+ return normalized;
62
+ return 'proposed';
63
+ }
64
+ export function normalizePlaybookEnforcement(value) {
65
+ const normalized = normalizeNonEmpty(value)?.toLowerCase();
66
+ if (normalized === 'soft-preflight' || normalized === 'strict-opt-in')
67
+ return normalized;
68
+ return 'advisory';
69
+ }
70
+ export function buildPlaybookTags(params) {
71
+ const scope = normalizePlaybookScope(params.scope);
72
+ const area = normalizeNonEmpty(params.area);
73
+ const source = normalizeNonEmpty(params.source);
74
+ return uniqueStrings([
75
+ 'playbook',
76
+ `playbook-scope:${scope}`,
77
+ area ? `playbook-area:${area}` : undefined,
78
+ source ? `playbook-source:${source}` : undefined,
79
+ ...(params.extraTags ?? []),
80
+ ]);
81
+ }
82
+ export function buildPlaybookMeta(input) {
83
+ const scope = normalizePlaybookScope(input.scope);
84
+ return compactPayload({
85
+ id: normalizeNonEmpty(input.id),
86
+ title: normalizeNonEmpty(input.title) ?? 'Untitled playbook',
87
+ scope,
88
+ area: normalizeNonEmpty(input.area),
89
+ appliesWhen: normalizeNonEmpty(input.appliesWhen),
90
+ steps: uniqueStrings(input.steps ?? []),
91
+ evidence: input.evidence,
92
+ enforcement: normalizePlaybookEnforcement(input.enforcement),
93
+ confidence: normalizeNonEmpty(input.confidence),
94
+ reviewState: normalizePlaybookReviewState(input.reviewState),
95
+ supersedes: normalizeNonEmpty(input.supersedes),
96
+ promotedFromExperienceId: normalizeNonEmpty(input.promotedFromExperienceId),
97
+ sessionContext: input.sessionContext && Object.keys(compactPayload(input.sessionContext)).length > 0
98
+ ? compactPayload(input.sessionContext)
99
+ : undefined,
100
+ });
101
+ }
102
+ export function isPlaybookMemoryEntry(entry) {
103
+ const kind = normalizeNonEmpty(entry.kind)?.toLowerCase();
104
+ if (kind !== 'rule' && kind !== 'constraint')
105
+ return false;
106
+ const meta = isRecord(entry.raw?.meta) ? entry.raw.meta : {};
107
+ const playbook = isRecord(meta.playbook) ? meta.playbook : {};
108
+ return Object.keys(playbook).length > 0 || hasPlaybookTag(entry.tags);
109
+ }
110
+ export function memoryEntryToPlaybookRecord(entry) {
111
+ if (!isPlaybookMemoryEntry(entry))
112
+ return null;
113
+ const meta = isRecord(entry.raw?.meta) ? entry.raw.meta : {};
114
+ const playbook = isRecord(meta.playbook) ? meta.playbook : {};
115
+ const sourceType = normalizeNonEmpty(entry.raw?.sourceType);
116
+ const sourceId = normalizeNonEmpty(entry.raw?.sourceId);
117
+ const id = normalizeNonEmpty(playbook.id) ?? entry.memoryId ?? entry.id;
118
+ const scope = normalizeNonEmpty(playbook.scope) ?? tagValue(entry.tags, 'playbook-scope:');
119
+ const area = normalizeNonEmpty(playbook.area) ?? tagValue(entry.tags, 'playbook-area:');
120
+ return {
121
+ id,
122
+ playbookId: id,
123
+ title: normalizeNonEmpty(playbook.title) ?? entry.subjectTitle ?? contentTitle(entry.content) ?? id,
124
+ memoryItemId: entry.memoryId ?? entry.id,
125
+ kind: entry.kind,
126
+ durability: entry.durability,
127
+ scope,
128
+ area,
129
+ appliesWhen: normalizeNonEmpty(playbook.appliesWhen),
130
+ steps: toStringArray(playbook.steps),
131
+ enforcement: normalizeNonEmpty(playbook.enforcement),
132
+ confidence: normalizeNonEmpty(playbook.confidence),
133
+ reviewState: normalizeNonEmpty(playbook.reviewState),
134
+ supersedes: normalizeNonEmpty(playbook.supersedes),
135
+ promotedFromExperienceId: normalizeNonEmpty(playbook.promotedFromExperienceId),
136
+ sessionContext: playbook.sessionContext,
137
+ sourceType,
138
+ sourceId,
139
+ sourceRefs: entry.sourceRefs,
140
+ content: entry.content,
141
+ tags: entry.tags,
142
+ updatedAt: entry.updatedAt,
143
+ createdAt: entry.createdAt,
144
+ };
145
+ }
146
+ export function toPlaybookBrief(record) {
147
+ return {
148
+ id: record.id ?? record.playbookId,
149
+ title: record.title,
150
+ scope: record.scope,
151
+ area: record.area,
152
+ kind: record.kind,
153
+ durability: record.durability,
154
+ reviewState: record.reviewState,
155
+ enforcement: record.enforcement,
156
+ appliesWhen: record.appliesWhen,
157
+ steps: record.steps ?? [],
158
+ contentPreview: summarizeContent(record.content),
159
+ tags: record.tags,
160
+ memoryItemId: record.memoryItemId,
161
+ promotedFromExperienceId: record.promotedFromExperienceId,
162
+ sourceType: record.sourceType,
163
+ sourceId: record.sourceId,
164
+ updatedAt: record.updatedAt ?? record.createdAt,
165
+ };
166
+ }
167
+ export function renderPlaybookMarkdown(record) {
168
+ const brief = toPlaybookBrief(record);
169
+ const lines = [`# ${brief.title ?? brief.id ?? 'Playbook'}`, ''];
170
+ const facts = compactPayload({
171
+ id: brief.id,
172
+ memoryItemId: brief.memoryItemId,
173
+ scope: brief.scope,
174
+ area: brief.area,
175
+ kind: brief.kind,
176
+ durability: brief.durability,
177
+ reviewState: brief.reviewState,
178
+ enforcement: brief.enforcement,
179
+ appliesWhen: brief.appliesWhen,
180
+ promotedFromExperienceId: brief.promotedFromExperienceId,
181
+ updatedAt: brief.updatedAt,
182
+ });
183
+ lines.push('## Facts');
184
+ Object.entries(facts).forEach(([key, value]) => lines.push(`- ${key}: ${String(value)}`));
185
+ lines.push('');
186
+ lines.push('## Steps');
187
+ if (brief.steps.length === 0) {
188
+ lines.push('- No structured steps recorded.');
189
+ }
190
+ else {
191
+ brief.steps.forEach((step) => lines.push(`- ${step}`));
192
+ }
193
+ lines.push('');
194
+ lines.push('## Content');
195
+ lines.push(normalizeNonEmpty(record.content) ?? brief.contentPreview ?? 'No content recorded.');
196
+ if (brief.tags.length > 0) {
197
+ lines.push('', '## Tags', brief.tags.map((tag) => `- ${tag}`).join('\n'));
198
+ }
199
+ return `${lines.join('\n')}\n`;
200
+ }
201
+ export function filterPlaybookRecords(records, filter = {}) {
202
+ const id = normalizeNonEmpty(filter.id)?.toLowerCase();
203
+ const scope = normalizeNonEmpty(filter.scope)?.toLowerCase();
204
+ const areas = toStringArray(filter.area).map((entry) => entry.toLowerCase());
205
+ const durability = toStringArray(filter.durability).map((entry) => entry.toLowerCase());
206
+ const reviewState = normalizeNonEmpty(filter.reviewState)?.toLowerCase();
207
+ const tags = toStringArray(filter.tag).map((entry) => entry.toLowerCase());
208
+ const filtered = records.filter((record) => {
209
+ if (id) {
210
+ const candidates = [record.id, record.playbookId, record.memoryItemId, record.title]
211
+ .map((entry) => normalizeNonEmpty(entry)?.toLowerCase())
212
+ .filter(Boolean);
213
+ if (!candidates.some((candidate) => candidate === id || candidate?.startsWith(id)))
214
+ return false;
215
+ }
216
+ if (scope && normalizeNonEmpty(record.scope)?.toLowerCase() !== scope)
217
+ return false;
218
+ if (areas.length > 0 && !areas.includes(normalizeNonEmpty(record.area)?.toLowerCase() ?? ''))
219
+ return false;
220
+ if (durability.length > 0 && !durability.includes(normalizeNonEmpty(record.durability)?.toLowerCase() ?? ''))
221
+ return false;
222
+ if (reviewState && normalizeNonEmpty(record.reviewState)?.toLowerCase() !== reviewState)
223
+ return false;
224
+ if (tags.length > 0) {
225
+ const itemTags = record.tags.map((tag) => tag.toLowerCase());
226
+ if (!tags.every((tag) => itemTags.includes(tag)))
227
+ return false;
228
+ }
229
+ return true;
230
+ });
231
+ return Number.isFinite(filter.limit) && filter.limit && filter.limit > 0
232
+ ? filtered.slice(0, Math.trunc(filter.limit))
233
+ : filtered;
234
+ }
235
+ function getSessionContextMissionId(record) {
236
+ return isRecord(record.sessionContext) ? normalizeNonEmpty(record.sessionContext.missionId) : undefined;
237
+ }
238
+ function sourceRefsIncludeMission(record, missionId) {
239
+ return toRecordArray(record.sourceRefs).some((ref) => {
240
+ const refType = normalizeNonEmpty(ref.refType)?.toLowerCase();
241
+ const refId = normalizeNonEmpty(ref.refId) ?? normalizeNonEmpty(ref.id);
242
+ return refId === missionId && (refType === undefined || refType === 'agentspace.mission' || refType === 'mission');
243
+ });
244
+ }
245
+ function isActiveSessionPlaybook(record, missionId) {
246
+ if (normalizeNonEmpty(record.scope)?.toLowerCase() !== 'session')
247
+ return false;
248
+ if (!missionId)
249
+ return true;
250
+ return getSessionContextMissionId(record) === missionId || sourceRefsIncludeMission(record, missionId);
251
+ }
252
+ function buildSessionPlaybookSet(records, options = {}) {
253
+ const missionId = normalizeNonEmpty(options.missionId);
254
+ const reviewState = toStringArray(options.reviewState).length > 0
255
+ ? toStringArray(options.reviewState).map((entry) => entry.toLowerCase())
256
+ : ['accepted'];
257
+ const durability = toStringArray(options.durability).length > 0
258
+ ? toStringArray(options.durability).map((entry) => entry.toLowerCase())
259
+ : ['short', 'durable', 'sticky'];
260
+ const limit = Number.isFinite(options.limit) && options.limit && options.limit > 0
261
+ ? Math.trunc(options.limit)
262
+ : 8;
263
+ const filtered = records.filter((record) => {
264
+ if (!isActiveSessionPlaybook(record, missionId))
265
+ return false;
266
+ if (!durability.includes(normalizeNonEmpty(record.durability)?.toLowerCase() ?? ''))
267
+ return false;
268
+ if (!reviewState.includes(normalizeNonEmpty(record.reviewState)?.toLowerCase() ?? ''))
269
+ return false;
270
+ return true;
271
+ }).slice(0, limit);
272
+ const command = [
273
+ 'aops-cli playbook list',
274
+ options.projectSlug ? `--project-slug ${options.projectSlug}` : undefined,
275
+ '--scope session',
276
+ ...reviewState.map((state) => `--review-state ${state}`),
277
+ '--json',
278
+ ].filter(Boolean).join(' ');
279
+ return {
280
+ mode: 'repo-first-playbook-set',
281
+ readOnly: true,
282
+ scope: 'session',
283
+ filters: {
284
+ scope: 'session',
285
+ durability,
286
+ reviewState,
287
+ ...(missionId ? { missionId } : {}),
288
+ limit,
289
+ },
290
+ count: filtered.length,
291
+ data: filtered.map(toPlaybookBrief),
292
+ suggestedActions: [
293
+ {
294
+ kind: 'inspect-session-playbooks',
295
+ mode: 'read-only',
296
+ applyRequired: false,
297
+ command,
298
+ policyPath: 'mission.policy.playbook.sessionSet',
299
+ note: missionId
300
+ ? 'Review accepted session-scope playbooks attached to this mission before resuming work.'
301
+ : 'Review accepted session-scope playbooks before resuming active work.',
302
+ },
303
+ ],
304
+ note: 'Session playbooks are read-only active-mission guidance; start/resume surfaces never promote them silently.',
305
+ };
306
+ }
307
+ export function buildProjectPlaybookSet(records, options = {}) {
308
+ const reviewState = toStringArray(options.reviewState).length > 0
309
+ ? toStringArray(options.reviewState).map((entry) => entry.toLowerCase())
310
+ : ['accepted'];
311
+ const durability = toStringArray(options.durability).length > 0
312
+ ? toStringArray(options.durability).map((entry) => entry.toLowerCase())
313
+ : ['sticky', 'durable'];
314
+ const limit = Number.isFinite(options.limit) && options.limit && options.limit > 0
315
+ ? Math.trunc(options.limit)
316
+ : 8;
317
+ const filtered = records.filter((record) => {
318
+ if (normalizeNonEmpty(record.scope)?.toLowerCase() !== 'project')
319
+ return false;
320
+ if (!durability.includes(normalizeNonEmpty(record.durability)?.toLowerCase() ?? ''))
321
+ return false;
322
+ if (!reviewState.includes(normalizeNonEmpty(record.reviewState)?.toLowerCase() ?? ''))
323
+ return false;
324
+ return true;
325
+ }).slice(0, limit);
326
+ const command = [
327
+ 'aops-cli playbook project-set',
328
+ options.projectSlug ? `--project-slug ${options.projectSlug}` : undefined,
329
+ '--scope project',
330
+ ...reviewState.map((state) => `--review-state ${state}`),
331
+ '--json',
332
+ ].filter(Boolean).join(' ');
333
+ return {
334
+ mode: 'repo-first-playbook-set',
335
+ readOnly: true,
336
+ scope: 'project',
337
+ filters: {
338
+ scope: 'project',
339
+ durability,
340
+ reviewState,
341
+ limit,
342
+ },
343
+ count: filtered.length,
344
+ data: filtered.map(toPlaybookBrief),
345
+ suggestedActions: [
346
+ {
347
+ kind: 'inspect-project-playbooks',
348
+ mode: 'read-only',
349
+ applyRequired: false,
350
+ command,
351
+ policyPath: 'mission.policy.playbook.projectSet',
352
+ note: 'Review accepted project-scope playbooks before starting or resuming work.',
353
+ },
354
+ ],
355
+ note: 'Project playbooks are read-only start/resume guidance projected from sticky or durable memory rules/constraints.',
356
+ };
357
+ }
358
+ export function buildActivePlaybookNudgePack(records, options = {}) {
359
+ const missionId = normalizeNonEmpty(options.missionId);
360
+ const project = buildProjectPlaybookSet(records, {
361
+ projectSlug: options.projectSlug,
362
+ reviewState: options.reviewState,
363
+ durability: options.projectDurability,
364
+ limit: options.limit,
365
+ });
366
+ const session = buildSessionPlaybookSet(records, {
367
+ projectSlug: options.projectSlug,
368
+ missionId,
369
+ reviewState: options.reviewState,
370
+ durability: options.sessionDurability,
371
+ limit: options.limit,
372
+ });
373
+ const closeoutCaptureActions = [
374
+ {
375
+ kind: 'capture-closeout-experience',
376
+ mode: 'nudge-first',
377
+ applyRequired: true,
378
+ command: [
379
+ 'aops-cli experience capture',
380
+ '--type pattern',
381
+ '--title "Hexagen backend playbook candidate"',
382
+ '--area hexagen',
383
+ '--tag playbook-candidate',
384
+ missionId ? `--mission-id ${missionId}` : undefined,
385
+ '--content "<reviewed closeout pattern, evidence, and applicability>"',
386
+ '--apply --json',
387
+ ].filter(Boolean).join(' '),
388
+ policyPath: 'mission.policy.playbook.closeoutCapture.capture',
389
+ note: 'At closeout, capture only reviewed operator/reviewer signal as experience; this is an explicit write.',
390
+ },
391
+ {
392
+ kind: 'promote-closeout-playbook',
393
+ mode: 'nudge-first',
394
+ applyRequired: true,
395
+ command: [
396
+ 'aops-cli playbook promote',
397
+ '--id <experience-id>',
398
+ options.projectSlug ? `--project-slug ${options.projectSlug}` : undefined,
399
+ '--scope project',
400
+ '--area hexagen',
401
+ '--review-state accepted',
402
+ '--applies-when "A future hexagen backend slice needs the same generated-domain pattern"',
403
+ '--apply --json',
404
+ ].filter(Boolean).join(' '),
405
+ policyPath: 'mission.policy.playbook.closeoutCapture.promote',
406
+ note: 'Promote a reviewed experience into an accepted playbook only after operator/reviewer signal.',
407
+ },
408
+ {
409
+ kind: 'write-closeout-summary',
410
+ mode: 'nudge-first',
411
+ applyRequired: true,
412
+ command: 'aops-cli mem summary --closeout --durability durable --confirm --content "<outcome, validation, review evidence, next action>" --apply --json',
413
+ policyPath: 'mission.policy.closeout.handoff',
414
+ note: 'Durable closeout memory remains explicit and confirmed; it is evidence for later playbook promotion, not an automatic promotion.',
415
+ },
416
+ ];
417
+ const hexagenCommands = [
418
+ 'aops-cli experience capture --type pattern --title "Hexagen backend slice pattern" --area hexagen --command "<validated generation/build/test commands>" --source-ref "<PM/RR refs>" --apply --json',
419
+ 'aops-cli playbook promote --id <experience-id> --scope project --area hexagen --review-state accepted --step "<repeatable hexagen step>" --apply --json',
420
+ options.projectSlug
421
+ ? `aops-cli playbook project-set --project-slug ${options.projectSlug} --scope project --review-state accepted --json`
422
+ : 'aops-cli playbook project-set --scope project --review-state accepted --json',
423
+ ];
424
+ return {
425
+ mode: 'active-playbook-nudge-pack',
426
+ readOnly: true,
427
+ defaultWrite: false,
428
+ ...(missionId ? { missionId } : {}),
429
+ project,
430
+ session,
431
+ count: project.count + session.count,
432
+ suggestedActions: [
433
+ ...project.suggestedActions,
434
+ ...session.suggestedActions,
435
+ ...closeoutCaptureActions,
436
+ ],
437
+ closeoutCapture: {
438
+ mode: 'nudge-first',
439
+ defaultWrite: false,
440
+ triggerSignals: ['operator-closeout', 'reviewer-approved-pattern', 'validated-repeatable-hexagen-flow'],
441
+ suggestedActions: closeoutCaptureActions,
442
+ note: 'Closeout capture/promote is surfaced as opt-in commands only; no start/resume path writes experience, memory, or playbooks.',
443
+ },
444
+ examples: [
445
+ {
446
+ kind: 'hexagen-as-playbook',
447
+ title: 'Hexagen backend slice as accepted project playbook',
448
+ flow: [
449
+ 'Complete a hexagen backend slice and gather validation/review evidence.',
450
+ 'At closeout, capture the repeatable pattern as an experience item with PM/RR/source refs.',
451
+ 'Promote the reviewed experience into an accepted project-scope playbook.',
452
+ 'On the next start/resume, read the accepted project playbook through playbook project-set before implementing.',
453
+ ],
454
+ commands: hexagenCommands,
455
+ note: 'Example only; every write command is explicit and guarded with --apply.',
456
+ },
457
+ ],
458
+ note: 'Active playbooks are guidance and nudges. Start/resume reads them and suggests explicit commands; it never writes or promotes playbooks silently.',
459
+ };
460
+ }
461
+ export async function readLocalPlaybookRecordsFromContext(context) {
462
+ const paths = resolveMemoryWorkspacePaths(context);
463
+ const entries = await readLocalMemoryEntries(paths.localItemsDir);
464
+ return entries
465
+ .map(memoryEntryToPlaybookRecord)
466
+ .filter((entry) => Boolean(entry));
467
+ }
468
+ export async function buildReadOnlyProjectPlaybookSetFromContext(context) {
469
+ const records = await readLocalPlaybookRecordsFromContext(context);
470
+ return buildProjectPlaybookSet(records, { projectSlug: context.projectSlug });
471
+ }
472
+ export async function buildReadOnlyActivePlaybookNudgePackFromContext(context, options = {}) {
473
+ const records = await readLocalPlaybookRecordsFromContext(context);
474
+ return buildActivePlaybookNudgePack(records, {
475
+ projectSlug: context.projectSlug,
476
+ missionId: options.missionId,
477
+ limit: options.limit,
478
+ });
479
+ }