@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.
- package/LICENSE +19 -0
- package/NOTICE +7 -0
- package/README.md +203 -0
- package/assets/agent-assets/core/SKILL.md +49 -0
- package/assets/agent-assets/core/references/agentspace/SKILL.md +78 -0
- package/assets/agent-assets/core/references/aops-cli-core/SKILL.md +108 -0
- package/assets/agent-assets/core/references/chatv3/SKILL.md +48 -0
- package/assets/agent-assets/core/references/collaborative-work/SKILL.md +85 -0
- package/assets/agent-assets/core/references/discuss/SKILL.md +66 -0
- package/assets/agent-assets/core/references/docman/SKILL.md +69 -0
- package/assets/agent-assets/core/references/hosted-chat/SKILL.md +64 -0
- package/assets/agent-assets/core/references/mission/SKILL.md +54 -0
- package/assets/agent-assets/core/references/projectman/SKILL.md +86 -0
- package/assets/agent-assets/core/references/sys/SKILL.md +36 -0
- package/assets/agent-assets/core/references/view/SKILL.md +58 -0
- package/assets/agent-assets/core/references/working-disciplines/SKILL.md +69 -0
- package/assets/agent-assets/core/user-guides/agent-assets.md +81 -0
- package/assets/agent-assets/core/user-guides/agentspace.md +1098 -0
- package/assets/agent-assets/core/user-guides/aops-cli.md +1107 -0
- package/assets/agent-assets/core/user-guides/aops-system.md +63 -0
- package/assets/agent-assets/core/user-guides/chatv3.md +113 -0
- package/assets/agent-assets/core/user-guides/docman.md +162 -0
- package/assets/agent-assets/core/user-guides/projectman.md +212 -0
- package/assets/agent-assets/core/user-guides/sys.md +94 -0
- package/assets/agent-assets/core/user-guides/working-disciplines.md +588 -0
- package/assets/agent-assets/gateway/aops/SKILL.md +14 -0
- package/assets/r6-remote-session-baseline.json +19 -0
- package/assets/skills/aops-install/SKILL.md +231 -0
- package/assets/skills/aops-install/agents/openai.yaml +4 -0
- package/dist/commands/activity.js +352 -0
- package/dist/commands/agent-profile.js +653 -0
- package/dist/commands/agent.js +710 -0
- package/dist/commands/agents-md.js +125 -0
- package/dist/commands/api.js +84 -0
- package/dist/commands/archive.js +873 -0
- package/dist/commands/artifact.js +535 -0
- package/dist/commands/assets.js +1414 -0
- package/dist/commands/auth/device-login.js +1 -0
- package/dist/commands/auth/legacy-export.js +177 -0
- package/dist/commands/auth/login.js +426 -0
- package/dist/commands/chat.js +1363 -0
- package/dist/commands/chatv3.js +1684 -0
- package/dist/commands/checkpoint.js +549 -0
- package/dist/commands/commercial-artifact.js +475 -0
- package/dist/commands/commercial-license.js +594 -0
- package/dist/commands/community-auth.js +62 -0
- package/dist/commands/community-cockpit.js +185 -0
- package/dist/commands/community-console.js +109 -0
- package/dist/commands/community-doctor.js +335 -0
- package/dist/commands/community-server.js +2062 -0
- package/dist/commands/community-setup.js +284 -0
- package/dist/commands/discuss.js +1448 -0
- package/dist/commands/doc.js +5219 -0
- package/dist/commands/experience.js +922 -0
- package/dist/commands/host.js +1127 -0
- package/dist/commands/init.js +118 -0
- package/dist/commands/memory.js +3129 -0
- package/dist/commands/mission.js +562 -0
- package/dist/commands/official-catalog.js +134 -0
- package/dist/commands/plan.js +84 -0
- package/dist/commands/playbook.js +415 -0
- package/dist/commands/pm/context.js +6 -0
- package/dist/commands/pm/index.js +701 -0
- package/dist/commands/pm/projectman.js +6266 -0
- package/dist/commands/project.js +1080 -0
- package/dist/commands/prompt.js +1024 -0
- package/dist/commands/repo-sync.js +1907 -0
- package/dist/commands/resource.js +527 -0
- package/dist/commands/skill.js +1211 -0
- package/dist/commands/start-disciplines.js +510 -0
- package/dist/commands/start.js +1343 -0
- package/dist/commands/target.js +309 -0
- package/dist/commands/version.js +42 -0
- package/dist/commands/view.js +1852 -0
- package/dist/launchers/cockpit.js +28 -0
- package/dist/launchers/options.js +74 -0
- package/dist/launchers/server.js +41 -0
- package/dist/lib/agent-assets/envelope.js +83 -0
- package/dist/lib/agent-assets/gateway.js +15 -0
- package/dist/lib/agent-assets/guards.js +23 -0
- package/dist/lib/agent-assets/hosted-discovery.js +148 -0
- package/dist/lib/agent-assets/hosted-package-input.js +154 -0
- package/dist/lib/agent-assets/legacy-pointer-migration.js +677 -0
- package/dist/lib/agent-assets/native-fs.js +589 -0
- package/dist/lib/agent-assets/package-manifest.js +324 -0
- package/dist/lib/agent-assets/portable-path.js +67 -0
- package/dist/lib/agent-assets/release-input.js +177 -0
- package/dist/lib/agent-assets/roots.js +45 -0
- package/dist/lib/agent-assets/runtime-binding-reader.js +545 -0
- package/dist/lib/agent-assets/runtime-targets.js +50 -0
- package/dist/lib/agent-assets/store-reader.js +1212 -0
- package/dist/lib/agent-assets/store-types.js +1 -0
- package/dist/lib/agent-assets/store-writer.js +1484 -0
- package/dist/lib/agent-assets/types.js +1 -0
- package/dist/lib/agent-assets/unicode-case-folding-v15-1.generated.js +1548 -0
- package/dist/lib/commercial-final-readiness.js +504 -0
- package/dist/lib/commercial-lifecycle-smoke-isolation.js +236 -0
- package/dist/lib/commercial-managed-postgres-docker-adapter.js +274 -0
- package/dist/lib/commercial-setup-readiness-adapter.js +179 -0
- package/dist/lib/community-client-contract.js +164 -0
- package/dist/lib/community-cockpit-lifecycle.js +601 -0
- package/dist/lib/community-command-abort.js +59 -0
- package/dist/lib/community-diagnostic.js +237 -0
- package/dist/lib/community-docker-adapter.js +498 -0
- package/dist/lib/community-home.js +31 -0
- package/dist/lib/community-instance-contract.js +148 -0
- package/dist/lib/community-instance-layout.js +97 -0
- package/dist/lib/community-lifecycle.js +1631 -0
- package/dist/lib/community-migration-snapshot.js +770 -0
- package/dist/lib/community-native-application-recovery.js +425 -0
- package/dist/lib/community-native-child.js +240 -0
- package/dist/lib/community-native-database-recovery.js +456 -0
- package/dist/lib/community-native-lifecycle.js +2458 -0
- package/dist/lib/community-native-migration.js +339 -0
- package/dist/lib/community-native-postgres.js +596 -0
- package/dist/lib/community-operation-journal.js +773 -0
- package/dist/lib/community-operation-lock.js +596 -0
- package/dist/lib/community-release-resolver.js +291 -0
- package/dist/lib/community-release-verifier.js +475 -0
- package/dist/lib/community-repo-discovery.js +252 -0
- package/dist/lib/community-setup-server-env.js +203 -0
- package/dist/lib/external-url.js +110 -0
- package/dist/lib/official-catalog-gateway.js +108 -0
- package/dist/lib/official-catalog.js +364 -0
- package/dist/lib/server-bootstrap.js +240 -0
- package/dist/lib/setup-agent-assets-bridge.js +176 -0
- package/dist/lib/setup-agent-assets-release.js +52 -0
- package/dist/lib/setup-external-postgres.js +108 -0
- package/dist/lib/setup-init-orchestrator.js +990 -0
- package/dist/lib/setup-install-guide.js +52 -0
- package/dist/lib/setup-installer-release.js +47 -0
- package/dist/lib/setup-local-postgres.js +329 -0
- package/dist/lib/setup-official-catalog-bridge.js +148 -0
- package/dist/lib/setup-readiness.js +888 -0
- package/dist/lib/tui-launcher.js +59 -0
- package/dist/main.js +150 -0
- package/dist/seeds/agents-md.js +54 -0
- package/dist/utils/agent-gateway.js +275 -0
- package/dist/utils/agents-md.js +281 -0
- package/dist/utils/api.js +255 -0
- package/dist/utils/chatv3-session-store.js +185 -0
- package/dist/utils/command.js +67 -0
- package/dist/utils/community-host-runtime.js +376 -0
- package/dist/utils/config.js +1015 -0
- package/dist/utils/discussion-workspace.js +552 -0
- package/dist/utils/experience-workspace.js +326 -0
- package/dist/utils/guide-paths.js +67 -0
- package/dist/utils/hosted-sugar.js +56 -0
- package/dist/utils/hosted-workspace.js +229 -0
- package/dist/utils/memory-workspace.js +537 -0
- package/dist/utils/operator-runtime.js +199 -0
- package/dist/utils/password-crypto.js +39 -0
- package/dist/utils/playbook-workspace.js +479 -0
- package/dist/utils/project-context.js +255 -0
- package/dist/utils/prompts.js +70 -0
- package/dist/utils/refresh-race.js +28 -0
- package/dist/utils/repo-config.js +109 -0
- package/dist/utils/repo-first-projectman.js +194 -0
- package/dist/utils/repo-first-storage.js +13 -0
- package/dist/utils/repo-first-view.js +305 -0
- package/dist/utils/session-guidance.js +278 -0
- package/dist/utils/session-state.js +137 -0
- package/dist/utils/table.js +39 -0
- package/dist/utils/transient-fs.js +49 -0
- package/dist/utils/workspace-root.js +1 -0
- package/launchers/aops-cockpit.ps1 +15 -0
- package/launchers/aops-cockpit.sh +16 -0
- package/launchers/aops-server.ps1 +15 -0
- package/launchers/aops-server.sh +16 -0
- package/native/tui/win32-x64/aops-tui.exe +0 -0
- package/package.json +81 -0
|
@@ -0,0 +1,305 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto';
|
|
2
|
+
import fs from 'node:fs/promises';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import { normalizeNonEmpty } from './command.js';
|
|
5
|
+
import { parseFrontmatterDocument, renderFrontmatterDocument } from './memory-workspace.js';
|
|
6
|
+
export class ViewSelectorError extends Error {
|
|
7
|
+
code;
|
|
8
|
+
selector;
|
|
9
|
+
candidates;
|
|
10
|
+
constructor(code, selector, candidates) {
|
|
11
|
+
super(code === 'ambiguous'
|
|
12
|
+
? `Selector "${selector}" is ambiguous.`
|
|
13
|
+
: `Selector "${selector}" was not found.`);
|
|
14
|
+
this.name = 'ViewSelectorError';
|
|
15
|
+
this.code = code;
|
|
16
|
+
this.selector = selector;
|
|
17
|
+
this.candidates = candidates;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
const SYNC_STATES = new Set(['local', 'synced', 'dirty', 'deleted', 'conflict']);
|
|
21
|
+
const DEFAULT_MAX_ITEMS = 25;
|
|
22
|
+
const DEFAULT_MAX_BYTES = 32768;
|
|
23
|
+
const MAX_BYTES_HARD_CAP = 32768;
|
|
24
|
+
export function isRecord(value) {
|
|
25
|
+
return value !== null && typeof value === 'object' && !Array.isArray(value);
|
|
26
|
+
}
|
|
27
|
+
export function stringValue(value) {
|
|
28
|
+
return normalizeNonEmpty(value);
|
|
29
|
+
}
|
|
30
|
+
export function numberValue(value) {
|
|
31
|
+
return typeof value === 'number' && Number.isFinite(value) ? value : undefined;
|
|
32
|
+
}
|
|
33
|
+
export function stringArray(value) {
|
|
34
|
+
return Array.isArray(value)
|
|
35
|
+
? value.map((entry) => normalizeNonEmpty(entry)).filter((entry) => Boolean(entry))
|
|
36
|
+
: [];
|
|
37
|
+
}
|
|
38
|
+
export function recordArray(value) {
|
|
39
|
+
return Array.isArray(value) ? value.filter(isRecord) : [];
|
|
40
|
+
}
|
|
41
|
+
export function normalizeViewStyle(value) {
|
|
42
|
+
const normalized = normalizeNonEmpty(value);
|
|
43
|
+
if (normalized === 'compact' || normalized === 'wide')
|
|
44
|
+
return normalized;
|
|
45
|
+
return 'agent';
|
|
46
|
+
}
|
|
47
|
+
export function normalizeViewLinkMode(value) {
|
|
48
|
+
const normalized = normalizeNonEmpty(value);
|
|
49
|
+
if (normalized === 'relative' || normalized === 'absolute')
|
|
50
|
+
return normalized;
|
|
51
|
+
return 'none';
|
|
52
|
+
}
|
|
53
|
+
export function normalizeMaxItems(value) {
|
|
54
|
+
const parsed = typeof value === 'number' ? value : Number.parseInt(String(value ?? ''), 10);
|
|
55
|
+
if (!Number.isFinite(parsed) || parsed <= 0)
|
|
56
|
+
return DEFAULT_MAX_ITEMS;
|
|
57
|
+
return Math.max(1, Math.min(500, Math.floor(parsed)));
|
|
58
|
+
}
|
|
59
|
+
export function normalizeMaxBytes(value) {
|
|
60
|
+
const parsed = typeof value === 'number' ? value : Number.parseInt(String(value ?? ''), 10);
|
|
61
|
+
if (!Number.isFinite(parsed) || parsed <= 0)
|
|
62
|
+
return DEFAULT_MAX_BYTES;
|
|
63
|
+
return Math.max(512, Math.min(MAX_BYTES_HARD_CAP, Math.floor(parsed)));
|
|
64
|
+
}
|
|
65
|
+
export function normalizeViewOptions(options = {}) {
|
|
66
|
+
return {
|
|
67
|
+
json: options.json === true,
|
|
68
|
+
style: normalizeViewStyle(options.style),
|
|
69
|
+
linkMode: normalizeViewLinkMode(options.linkMode),
|
|
70
|
+
maxItems: normalizeMaxItems(options.maxItems),
|
|
71
|
+
maxBytes: normalizeMaxBytes(options.maxBytes),
|
|
72
|
+
includeArchived: options.includeArchived === true,
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
export function slugify(value) {
|
|
76
|
+
return value
|
|
77
|
+
.toLowerCase()
|
|
78
|
+
.replace(/[^a-z0-9ğüşöçıİĞÜŞÖÇ]+/gi, '-')
|
|
79
|
+
.replace(/^-+|-+$/g, '')
|
|
80
|
+
.slice(0, 96) || 'item';
|
|
81
|
+
}
|
|
82
|
+
export function repoRelative(repoRoot, filePath) {
|
|
83
|
+
return path.relative(repoRoot, filePath).split(path.sep).join('/');
|
|
84
|
+
}
|
|
85
|
+
export async function readMarkdownRecords(rootDir, params = {}) {
|
|
86
|
+
try {
|
|
87
|
+
const entries = await fs.readdir(rootDir, { withFileTypes: true });
|
|
88
|
+
const records = [];
|
|
89
|
+
for (const entry of entries.sort((left, right) => left.name.localeCompare(right.name))) {
|
|
90
|
+
const filePath = path.join(rootDir, entry.name);
|
|
91
|
+
if (entry.isDirectory() && params.recursive === true) {
|
|
92
|
+
records.push(...await readMarkdownRecords(filePath, params));
|
|
93
|
+
continue;
|
|
94
|
+
}
|
|
95
|
+
if (!entry.isFile() || !entry.name.endsWith('.md'))
|
|
96
|
+
continue;
|
|
97
|
+
if (entry.name === 'index.md' && params.includeIndex !== true)
|
|
98
|
+
continue;
|
|
99
|
+
const parsed = parseFrontmatterDocument(await fs.readFile(filePath, 'utf8'));
|
|
100
|
+
records.push({
|
|
101
|
+
filePath,
|
|
102
|
+
relativePath: params.repoRoot ? repoRelative(params.repoRoot, filePath) : filePath,
|
|
103
|
+
frontmatter: parsed.frontmatter,
|
|
104
|
+
body: parsed.body.trim(),
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
return records;
|
|
108
|
+
}
|
|
109
|
+
catch {
|
|
110
|
+
return [];
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
function hashContent(value) {
|
|
114
|
+
return createHash('sha256').update(value).digest('hex');
|
|
115
|
+
}
|
|
116
|
+
function comparableRecordHash(record) {
|
|
117
|
+
const frontmatter = { ...record.frontmatter };
|
|
118
|
+
delete frontmatter.baseHash;
|
|
119
|
+
return hashContent(renderFrontmatterDocument(frontmatter, record.body));
|
|
120
|
+
}
|
|
121
|
+
export function effectiveLocalState(record) {
|
|
122
|
+
const syncState = normalizeNonEmpty(record.frontmatter.syncState);
|
|
123
|
+
const normalized = syncState && SYNC_STATES.has(syncState) ? syncState : 'local';
|
|
124
|
+
const baseHash = normalizeNonEmpty(record.frontmatter.baseHash);
|
|
125
|
+
if (normalized === 'synced' && baseHash && comparableRecordHash(record) !== baseHash)
|
|
126
|
+
return 'dirty';
|
|
127
|
+
return normalized;
|
|
128
|
+
}
|
|
129
|
+
export function recordId(record) {
|
|
130
|
+
return stringValue(record.frontmatter.localId)
|
|
131
|
+
?? stringValue(record.frontmatter.id)
|
|
132
|
+
?? stringValue(record.frontmatter.remoteId)
|
|
133
|
+
?? stringValue(record.frontmatter.memoryId)
|
|
134
|
+
?? stringValue(record.frontmatter.documentVersionId)
|
|
135
|
+
?? stringValue(record.frontmatter.documentId);
|
|
136
|
+
}
|
|
137
|
+
export function recordLabel(record) {
|
|
138
|
+
return stringValue(record.frontmatter.name)
|
|
139
|
+
?? stringValue(record.frontmatter.title)
|
|
140
|
+
?? stringValue(record.frontmatter.slug)
|
|
141
|
+
?? recordId(record)
|
|
142
|
+
?? record.relativePath;
|
|
143
|
+
}
|
|
144
|
+
export function recordSlug(record) {
|
|
145
|
+
return stringValue(record.frontmatter.slug)
|
|
146
|
+
?? stringValue(record.frontmatter.documentSlug)
|
|
147
|
+
?? stringValue(record.frontmatter.groupUid);
|
|
148
|
+
}
|
|
149
|
+
export function idAliases(value) {
|
|
150
|
+
if (!value)
|
|
151
|
+
return [];
|
|
152
|
+
const maybeRecord = value;
|
|
153
|
+
const frontmatter = isRecord(maybeRecord.frontmatter)
|
|
154
|
+
? maybeRecord.frontmatter
|
|
155
|
+
: value;
|
|
156
|
+
return [
|
|
157
|
+
frontmatter.localId,
|
|
158
|
+
frontmatter.id,
|
|
159
|
+
frontmatter.remoteId,
|
|
160
|
+
frontmatter.memoryId,
|
|
161
|
+
frontmatter.documentId,
|
|
162
|
+
frontmatter.documentVersionId,
|
|
163
|
+
frontmatter.topicLocalId,
|
|
164
|
+
frontmatter.sessionLocalId,
|
|
165
|
+
].map((entry) => normalizeNonEmpty(entry)).filter((entry) => Boolean(entry));
|
|
166
|
+
}
|
|
167
|
+
export function sameId(left, aliases) {
|
|
168
|
+
const normalized = normalizeNonEmpty(left);
|
|
169
|
+
return Boolean(normalized && aliases.includes(normalized));
|
|
170
|
+
}
|
|
171
|
+
export function matchesEqualCi(value, needle) {
|
|
172
|
+
const normalized = normalizeNonEmpty(value);
|
|
173
|
+
return Boolean(normalized && normalized.toLowerCase() === needle.toLowerCase());
|
|
174
|
+
}
|
|
175
|
+
export function matchesAnyEqualCi(values, needle) {
|
|
176
|
+
return values.some((value) => matchesEqualCi(value, needle));
|
|
177
|
+
}
|
|
178
|
+
export function sameIdOrPrefix(left, aliases) {
|
|
179
|
+
const normalized = normalizeNonEmpty(left);
|
|
180
|
+
if (!normalized)
|
|
181
|
+
return false;
|
|
182
|
+
if (aliases.includes(normalized))
|
|
183
|
+
return true;
|
|
184
|
+
if (normalized.length < 8)
|
|
185
|
+
return false;
|
|
186
|
+
return aliases.some((alias) => alias.startsWith(normalized));
|
|
187
|
+
}
|
|
188
|
+
export function candidateFromRecord(record) {
|
|
189
|
+
return {
|
|
190
|
+
id: recordId(record),
|
|
191
|
+
label: recordLabel(record),
|
|
192
|
+
slug: recordSlug(record),
|
|
193
|
+
type: stringValue(record.frontmatter.entityType),
|
|
194
|
+
path: record.relativePath,
|
|
195
|
+
};
|
|
196
|
+
}
|
|
197
|
+
function selectorFields(record) {
|
|
198
|
+
const values = [];
|
|
199
|
+
for (const value of idAliases(record))
|
|
200
|
+
values.push({ value, kind: 'id' });
|
|
201
|
+
for (const value of [recordSlug(record), stringValue(record.frontmatter.name), stringValue(record.frontmatter.title)]) {
|
|
202
|
+
if (value)
|
|
203
|
+
values.push({ value, kind: value === recordSlug(record) ? 'slug' : 'label' });
|
|
204
|
+
}
|
|
205
|
+
values.push({ value: record.relativePath, kind: 'path' });
|
|
206
|
+
values.push({ value: path.basename(record.relativePath, '.md'), kind: 'slug' });
|
|
207
|
+
return values;
|
|
208
|
+
}
|
|
209
|
+
export function resolveRecordSelector(records, selector) {
|
|
210
|
+
const normalized = selector.trim().toLowerCase();
|
|
211
|
+
if (!normalized) {
|
|
212
|
+
throw new ViewSelectorError('missing', selector, records.slice(0, 10).map(candidateFromRecord));
|
|
213
|
+
}
|
|
214
|
+
const matches = records.filter((record) => selectorFields(record).some(({ value, kind }) => {
|
|
215
|
+
const candidate = value.toLowerCase();
|
|
216
|
+
if (candidate === normalized)
|
|
217
|
+
return true;
|
|
218
|
+
return kind === 'id' && normalized.length >= 8 && candidate.startsWith(normalized);
|
|
219
|
+
}));
|
|
220
|
+
if (matches.length === 1)
|
|
221
|
+
return matches[0];
|
|
222
|
+
if (matches.length > 1) {
|
|
223
|
+
throw new ViewSelectorError('ambiguous', selector, matches.slice(0, 25).map(candidateFromRecord));
|
|
224
|
+
}
|
|
225
|
+
throw new ViewSelectorError('missing', selector, records.slice(0, 25).map(candidateFromRecord));
|
|
226
|
+
}
|
|
227
|
+
function escapeCell(value) {
|
|
228
|
+
return String(value ?? '')
|
|
229
|
+
.replace(/\r?\n/g, ' ')
|
|
230
|
+
.replace(/\|/g, '\\|')
|
|
231
|
+
.trim();
|
|
232
|
+
}
|
|
233
|
+
export function renderMarkdownTable(headers, rows, options = {}) {
|
|
234
|
+
const safeHeaders = headers.map(escapeCell);
|
|
235
|
+
const safeRows = rows.map((row) => row.map(escapeCell));
|
|
236
|
+
const lines = [
|
|
237
|
+
`| ${safeHeaders.join(' | ')} |`,
|
|
238
|
+
`| ${safeHeaders.map(() => '---').join(' | ')} |`,
|
|
239
|
+
];
|
|
240
|
+
for (const row of safeRows) {
|
|
241
|
+
const padded = safeHeaders.map((_, index) => row[index] ?? '');
|
|
242
|
+
lines.push(`| ${padded.join(' | ')} |`);
|
|
243
|
+
}
|
|
244
|
+
if (rows.length === 0) {
|
|
245
|
+
const emptyText = escapeCell(options.emptyText ?? 'No matching records.');
|
|
246
|
+
const cells = safeHeaders.map((_, index) => (index === 0 ? `_${emptyText}_` : ''));
|
|
247
|
+
lines.push(`| ${cells.join(' | ')} |`);
|
|
248
|
+
}
|
|
249
|
+
return lines.join('\n');
|
|
250
|
+
}
|
|
251
|
+
export function shortId(value) {
|
|
252
|
+
const normalized = normalizeNonEmpty(value);
|
|
253
|
+
return normalized ? normalized.slice(0, 8) : '-';
|
|
254
|
+
}
|
|
255
|
+
export function formatDate(value) {
|
|
256
|
+
const normalized = normalizeNonEmpty(value);
|
|
257
|
+
if (!normalized)
|
|
258
|
+
return '-';
|
|
259
|
+
const date = new Date(normalized);
|
|
260
|
+
if (Number.isNaN(date.getTime()))
|
|
261
|
+
return normalized;
|
|
262
|
+
return date.toISOString().replace(/\.\d{3}Z$/, 'Z');
|
|
263
|
+
}
|
|
264
|
+
export function summarizeText(value, maxLength = 120) {
|
|
265
|
+
const compact = String(value ?? '').replace(/\s+/g, ' ').trim();
|
|
266
|
+
if (!compact)
|
|
267
|
+
return '-';
|
|
268
|
+
if (compact.length <= maxLength)
|
|
269
|
+
return compact;
|
|
270
|
+
return `${compact.slice(0, Math.max(0, maxLength - 3)).trimEnd()}...`;
|
|
271
|
+
}
|
|
272
|
+
export function renderKeyValues(values) {
|
|
273
|
+
return values
|
|
274
|
+
.filter(([, value]) => value !== undefined && value !== null && String(value).trim() !== '')
|
|
275
|
+
.map(([label, value]) => `- ${label}: ${String(value)}`)
|
|
276
|
+
.join('\n');
|
|
277
|
+
}
|
|
278
|
+
export function applyMaxBytes(markdown, maxBytes) {
|
|
279
|
+
const limit = normalizeMaxBytes(maxBytes);
|
|
280
|
+
const bytes = Buffer.byteLength(markdown, 'utf8');
|
|
281
|
+
if (bytes <= limit)
|
|
282
|
+
return { markdown, truncated: false };
|
|
283
|
+
const footer = `\n\n---\n\nTruncated: output exceeded ${limit} bytes. Use --max-items or a narrower selector.\n`;
|
|
284
|
+
const target = Math.max(0, limit - Buffer.byteLength(footer, 'utf8'));
|
|
285
|
+
let sliced = markdown;
|
|
286
|
+
while (Buffer.byteLength(sliced, 'utf8') > target && sliced.length > 0) {
|
|
287
|
+
sliced = sliced.slice(0, Math.floor(sliced.length * 0.9));
|
|
288
|
+
}
|
|
289
|
+
return { markdown: `${sliced.trimEnd()}${footer}`, truncated: true };
|
|
290
|
+
}
|
|
291
|
+
export function renderFooter(params) {
|
|
292
|
+
return [
|
|
293
|
+
'',
|
|
294
|
+
'---',
|
|
295
|
+
'',
|
|
296
|
+
renderKeyValues([
|
|
297
|
+
['source', params.source],
|
|
298
|
+
['local-state', params.localState ?? '-'],
|
|
299
|
+
['updatedAt', formatDate(params.updatedAt)],
|
|
300
|
+
['lastPushedAt', formatDate(params.lastPushedAt)],
|
|
301
|
+
['lastPulledAt', formatDate(params.lastPulledAt)],
|
|
302
|
+
['truncated', params.truncated === true ? 'true' : 'false'],
|
|
303
|
+
]),
|
|
304
|
+
].join('\n');
|
|
305
|
+
}
|
|
@@ -0,0 +1,278 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
import { compactPayload, normalizeNonEmpty } from './command.js';
|
|
3
|
+
import { readExperienceItems, resolveExperienceWorkspacePaths } from './experience-workspace.js';
|
|
4
|
+
const DEFAULT_EXPERIENCE_LIMIT = 3;
|
|
5
|
+
const MAX_EXPERIENCE_LIMIT = 5;
|
|
6
|
+
function parseLimit(value) {
|
|
7
|
+
if (typeof value === 'number' && Number.isFinite(value) && value > 0) {
|
|
8
|
+
return Math.min(Math.trunc(value), MAX_EXPERIENCE_LIMIT);
|
|
9
|
+
}
|
|
10
|
+
const normalized = normalizeNonEmpty(value);
|
|
11
|
+
if (!normalized)
|
|
12
|
+
return DEFAULT_EXPERIENCE_LIMIT;
|
|
13
|
+
const parsed = Number.parseInt(normalized, 10);
|
|
14
|
+
if (!Number.isFinite(parsed) || parsed <= 0)
|
|
15
|
+
return DEFAULT_EXPERIENCE_LIMIT;
|
|
16
|
+
return Math.min(Math.trunc(parsed), MAX_EXPERIENCE_LIMIT);
|
|
17
|
+
}
|
|
18
|
+
function summarizeContent(content, maxLength = 180) {
|
|
19
|
+
const compact = normalizeNonEmpty(content)?.replace(/\s+/g, ' ');
|
|
20
|
+
if (!compact)
|
|
21
|
+
return undefined;
|
|
22
|
+
if (compact.length <= maxLength)
|
|
23
|
+
return compact;
|
|
24
|
+
return `${compact.slice(0, Math.max(0, maxLength - 3)).trimEnd()}...`;
|
|
25
|
+
}
|
|
26
|
+
function tokenize(value) {
|
|
27
|
+
const normalized = normalizeNonEmpty(value)?.toLowerCase();
|
|
28
|
+
if (!normalized)
|
|
29
|
+
return [];
|
|
30
|
+
return [...new Set(normalized.split(/[^a-z0-9]+/).filter((token) => token.length >= 3))];
|
|
31
|
+
}
|
|
32
|
+
function sourceRefsContain(item, expected) {
|
|
33
|
+
if (!expected)
|
|
34
|
+
return false;
|
|
35
|
+
return item.sourceRefs.some((ref) => {
|
|
36
|
+
if (!ref || typeof ref !== 'object' || Array.isArray(ref))
|
|
37
|
+
return false;
|
|
38
|
+
const record = ref;
|
|
39
|
+
const refId = normalizeNonEmpty(record.refId) ?? normalizeNonEmpty(record.id);
|
|
40
|
+
return refId === expected;
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
function scoreExperience(item, options) {
|
|
44
|
+
const reasons = [];
|
|
45
|
+
let score = 0;
|
|
46
|
+
const area = normalizeNonEmpty(options.area)?.toLowerCase();
|
|
47
|
+
const taskTokens = tokenize(options.task);
|
|
48
|
+
const haystackParts = [
|
|
49
|
+
item.title,
|
|
50
|
+
item.problem,
|
|
51
|
+
item.solution,
|
|
52
|
+
item.content,
|
|
53
|
+
...item.areas,
|
|
54
|
+
...item.stack,
|
|
55
|
+
...item.tags,
|
|
56
|
+
...item.commands,
|
|
57
|
+
...item.files,
|
|
58
|
+
].map((entry) => normalizeNonEmpty(entry)?.toLowerCase()).filter((entry) => Boolean(entry));
|
|
59
|
+
const haystack = haystackParts.join(' ');
|
|
60
|
+
if (area && item.areas.some((entry) => entry.toLowerCase() === area)) {
|
|
61
|
+
score += 30;
|
|
62
|
+
reasons.push(`area:${area}`);
|
|
63
|
+
}
|
|
64
|
+
if (sourceRefsContain(item, options.missionId)) {
|
|
65
|
+
score += 25;
|
|
66
|
+
reasons.push('mission-ref');
|
|
67
|
+
}
|
|
68
|
+
if (sourceRefsContain(item, options.planId)) {
|
|
69
|
+
score += 20;
|
|
70
|
+
reasons.push('plan-ref');
|
|
71
|
+
}
|
|
72
|
+
for (const token of taskTokens) {
|
|
73
|
+
if (haystack.includes(token)) {
|
|
74
|
+
score += 4;
|
|
75
|
+
reasons.push(`task:${token}`);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
if (item.confidence?.toLowerCase() === 'high') {
|
|
79
|
+
score += 3;
|
|
80
|
+
reasons.push('confidence:high');
|
|
81
|
+
}
|
|
82
|
+
if (item.reusability?.toLowerCase() === 'high') {
|
|
83
|
+
score += 2;
|
|
84
|
+
reasons.push('reusability:high');
|
|
85
|
+
}
|
|
86
|
+
return { score, reasons };
|
|
87
|
+
}
|
|
88
|
+
function toExperienceBrief(item, score, reasons) {
|
|
89
|
+
return {
|
|
90
|
+
id: item.localId,
|
|
91
|
+
title: item.title,
|
|
92
|
+
type: item.type,
|
|
93
|
+
areas: item.areas,
|
|
94
|
+
stack: item.stack,
|
|
95
|
+
tags: item.tags,
|
|
96
|
+
confidence: item.confidence,
|
|
97
|
+
score,
|
|
98
|
+
scoreReasons: reasons,
|
|
99
|
+
updatedAt: item.updatedAt,
|
|
100
|
+
contentPreview: summarizeContent(item.content),
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
async function buildExperienceBriefs(context, options) {
|
|
104
|
+
const paths = resolveExperienceWorkspacePaths({ repoRoot: context.repoRoot, localRoot: context.localRoot });
|
|
105
|
+
const limit = parseLimit(options.limit);
|
|
106
|
+
const items = await readExperienceItems(paths.itemsDir);
|
|
107
|
+
return items
|
|
108
|
+
.map((item) => {
|
|
109
|
+
const scored = scoreExperience(item, options);
|
|
110
|
+
return { item, ...scored };
|
|
111
|
+
})
|
|
112
|
+
.filter((entry) => entry.score > 0 || normalizeNonEmpty(options.area) === undefined)
|
|
113
|
+
.sort((left, right) => {
|
|
114
|
+
if (right.score !== left.score)
|
|
115
|
+
return right.score - left.score;
|
|
116
|
+
const updated = right.item.updatedAt.localeCompare(left.item.updatedAt);
|
|
117
|
+
if (updated !== 0)
|
|
118
|
+
return updated;
|
|
119
|
+
return left.item.localId.localeCompare(right.item.localId);
|
|
120
|
+
})
|
|
121
|
+
.slice(0, limit)
|
|
122
|
+
.map((entry) => toExperienceBrief(entry.item, entry.score, entry.reasons));
|
|
123
|
+
}
|
|
124
|
+
function flattenPlaybookBriefs(playbookNudges) {
|
|
125
|
+
if (!playbookNudges)
|
|
126
|
+
return [];
|
|
127
|
+
const data = [...playbookNudges.project.data, ...playbookNudges.session.data];
|
|
128
|
+
return data.slice(0, MAX_EXPERIENCE_LIMIT);
|
|
129
|
+
}
|
|
130
|
+
function readOnlyPlaybookActions(playbookNudges) {
|
|
131
|
+
return (playbookNudges?.suggestedActions ?? []).filter((action) => action.applyRequired === false);
|
|
132
|
+
}
|
|
133
|
+
function runtimeLayer(context, options) {
|
|
134
|
+
return {
|
|
135
|
+
id: 'L1-runtime-session-rules',
|
|
136
|
+
items: [
|
|
137
|
+
{
|
|
138
|
+
id: 'agents-md',
|
|
139
|
+
title: 'Project AGENTS.md and local operator instructions',
|
|
140
|
+
source: path.relative(context.repoRoot, path.join(context.repoRoot, 'AGENTS.md')).split(path.sep).join('/'),
|
|
141
|
+
required: true,
|
|
142
|
+
note: 'Read project runtime rules before acting; they define owner boundaries, PM discipline, and repo-local constraints.',
|
|
143
|
+
},
|
|
144
|
+
{
|
|
145
|
+
id: 'chatv3-room-rules',
|
|
146
|
+
title: 'ChatV3 channel and room guidance',
|
|
147
|
+
source: 'chatv3.guidanceMarkdown',
|
|
148
|
+
required: options.surface === 'start-reminder',
|
|
149
|
+
note: 'When a hosted room is active, read room rules and latest messages; chat is coordination, PM/discuss/memory remain truth ledgers.',
|
|
150
|
+
},
|
|
151
|
+
{
|
|
152
|
+
id: 'operator-current-instruction',
|
|
153
|
+
title: 'Current operator instruction',
|
|
154
|
+
source: 'current prompt or latest chat directive',
|
|
155
|
+
required: true,
|
|
156
|
+
note: 'Newest operator instruction wins when it conflicts with older session context.',
|
|
157
|
+
},
|
|
158
|
+
],
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
function disciplineLayer(input) {
|
|
162
|
+
if (!input)
|
|
163
|
+
return undefined;
|
|
164
|
+
return {
|
|
165
|
+
id: 'L2-working-discipline-guardrails',
|
|
166
|
+
selected: normalizeNonEmpty(input.selected),
|
|
167
|
+
recommended: normalizeNonEmpty(input.recommended),
|
|
168
|
+
explicit: input.explicit,
|
|
169
|
+
profile: input.profile
|
|
170
|
+
? compactPayload({
|
|
171
|
+
id: normalizeNonEmpty(input.profile.id),
|
|
172
|
+
version: normalizeNonEmpty(input.profile.version),
|
|
173
|
+
title: normalizeNonEmpty(input.profile.title),
|
|
174
|
+
})
|
|
175
|
+
: undefined,
|
|
176
|
+
guardrails: (input.guardrails ?? []).map((guardrail) => compactPayload({
|
|
177
|
+
id: normalizeNonEmpty(guardrail.id),
|
|
178
|
+
title: normalizeNonEmpty(guardrail.title),
|
|
179
|
+
phase: normalizeNonEmpty(guardrail.phase),
|
|
180
|
+
enforcementLevel: normalizeNonEmpty(guardrail.enforcementLevel),
|
|
181
|
+
})),
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
function buildSuggestedActions(params) {
|
|
185
|
+
const projectSlug = normalizeNonEmpty(params.context.projectSlug);
|
|
186
|
+
const area = normalizeNonEmpty(params.options.area);
|
|
187
|
+
const task = normalizeNonEmpty(params.options.task);
|
|
188
|
+
return [
|
|
189
|
+
{
|
|
190
|
+
kind: 'read-runtime-rules',
|
|
191
|
+
mode: 'read-only',
|
|
192
|
+
applyRequired: false,
|
|
193
|
+
command: 'Read AGENTS.md in the active repo before implementation.',
|
|
194
|
+
},
|
|
195
|
+
...readOnlyPlaybookActions(params.playbookNudges).map((action) => ({
|
|
196
|
+
...action,
|
|
197
|
+
mode: 'read-only',
|
|
198
|
+
applyRequired: false,
|
|
199
|
+
})),
|
|
200
|
+
{
|
|
201
|
+
kind: 'inspect-experience',
|
|
202
|
+
mode: 'read-only',
|
|
203
|
+
applyRequired: false,
|
|
204
|
+
command: [
|
|
205
|
+
'aops-cli view experience',
|
|
206
|
+
projectSlug ? `--project-slug ${projectSlug}` : undefined,
|
|
207
|
+
area ? `--area ${area}` : undefined,
|
|
208
|
+
'--json',
|
|
209
|
+
].filter(Boolean).join(' '),
|
|
210
|
+
note: task
|
|
211
|
+
? 'Use ranked experience briefs first, then inspect the full view only if the task still needs more context.'
|
|
212
|
+
: 'Inspect repo-first experience only when the reminder pack is not enough.',
|
|
213
|
+
},
|
|
214
|
+
];
|
|
215
|
+
}
|
|
216
|
+
export async function buildSessionGuidancePack(context, options) {
|
|
217
|
+
const missionId = normalizeNonEmpty(options.missionId);
|
|
218
|
+
const planId = normalizeNonEmpty(options.planId);
|
|
219
|
+
const area = normalizeNonEmpty(options.area);
|
|
220
|
+
const task = normalizeNonEmpty(options.task);
|
|
221
|
+
const experience = await buildExperienceBriefs(context, options);
|
|
222
|
+
const playbookData = flattenPlaybookBriefs(options.playbookNudges);
|
|
223
|
+
const suggestedActions = buildSuggestedActions({ context, options, playbookNudges: options.playbookNudges });
|
|
224
|
+
return {
|
|
225
|
+
schemaVersion: 1,
|
|
226
|
+
mode: 'session-guidance-pack',
|
|
227
|
+
surface: options.surface,
|
|
228
|
+
readOnly: true,
|
|
229
|
+
defaultWrite: false,
|
|
230
|
+
hints: compactPayload({
|
|
231
|
+
task,
|
|
232
|
+
missionId,
|
|
233
|
+
planId,
|
|
234
|
+
area,
|
|
235
|
+
experienceLimit: parseLimit(options.limit),
|
|
236
|
+
}),
|
|
237
|
+
layers: compactPayload({
|
|
238
|
+
runtime: runtimeLayer(context, options),
|
|
239
|
+
discipline: disciplineLayer(options.discipline),
|
|
240
|
+
playbooks: {
|
|
241
|
+
id: 'L3-accepted-playbook-briefs',
|
|
242
|
+
readOnly: true,
|
|
243
|
+
defaultWrite: false,
|
|
244
|
+
count: playbookData.length,
|
|
245
|
+
projectCount: options.playbookNudges?.project.count ?? 0,
|
|
246
|
+
sessionCount: options.playbookNudges?.session.count ?? 0,
|
|
247
|
+
data: playbookData,
|
|
248
|
+
suggestedActions: readOnlyPlaybookActions(options.playbookNudges),
|
|
249
|
+
note: 'Accepted playbooks are guidance only; start/reminder/resume never promote, capture, or mutate playbooks.',
|
|
250
|
+
},
|
|
251
|
+
experience: {
|
|
252
|
+
id: 'L3-ranked-experience-briefs',
|
|
253
|
+
mode: 'repo-first-experience-briefs',
|
|
254
|
+
readOnly: true,
|
|
255
|
+
defaultWrite: false,
|
|
256
|
+
filters: compactPayload({
|
|
257
|
+
area,
|
|
258
|
+
task,
|
|
259
|
+
missionId,
|
|
260
|
+
planId,
|
|
261
|
+
limit: parseLimit(options.limit),
|
|
262
|
+
maxLimit: MAX_EXPERIENCE_LIMIT,
|
|
263
|
+
}),
|
|
264
|
+
count: experience.length,
|
|
265
|
+
data: experience,
|
|
266
|
+
note: 'Experience briefs are deterministic repo-first reads ranked by area, source refs, task token matches, confidence, recency, then id.',
|
|
267
|
+
},
|
|
268
|
+
}),
|
|
269
|
+
suggestedActions,
|
|
270
|
+
constraints: [
|
|
271
|
+
'read-only',
|
|
272
|
+
'defaultWrite=false',
|
|
273
|
+
'no checkpoint, PM, chat, experience, memory, or playbook mutation',
|
|
274
|
+
'bounded experience briefs: default 3, max 5',
|
|
275
|
+
'start --reminder does not require board, room, roles, or full kickoff prompt rendering',
|
|
276
|
+
],
|
|
277
|
+
};
|
|
278
|
+
}
|