gsd-pi 2.75.0-dev.b6ad8c5f7 → 2.75.0-dev.e41b70b10
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/dist/resources/extensions/gsd/auto/phases.js +2 -0
- package/dist/resources/extensions/gsd/auto-dashboard.js +22 -1
- package/dist/resources/extensions/gsd/auto-direct-dispatch.js +8 -2
- package/dist/resources/extensions/gsd/auto-dispatch.js +11 -11
- package/dist/resources/extensions/gsd/auto-model-selection.js +3 -1
- package/dist/resources/extensions/gsd/auto-prompts.js +19 -9
- package/dist/resources/extensions/gsd/auto-worktree.js +16 -1
- package/dist/resources/extensions/gsd/bootstrap/memory-tools.js +128 -0
- package/dist/resources/extensions/gsd/bootstrap/register-extension.js +2 -0
- package/dist/resources/extensions/gsd/bootstrap/system-context.js +17 -4
- package/dist/resources/extensions/gsd/commands/handlers/onboarding.js +52 -92
- package/dist/resources/extensions/gsd/commands/handlers/ops.js +5 -0
- package/dist/resources/extensions/gsd/commands-memory.js +462 -0
- package/dist/resources/extensions/gsd/doctor-git-checks.js +22 -2
- package/dist/resources/extensions/gsd/gsd-db.js +237 -4
- package/dist/resources/extensions/gsd/memory-embeddings.js +219 -0
- package/dist/resources/extensions/gsd/memory-extractor.js +78 -27
- package/dist/resources/extensions/gsd/memory-ingest.js +218 -0
- package/dist/resources/extensions/gsd/memory-relations.js +189 -0
- package/dist/resources/extensions/gsd/memory-source-store.js +113 -0
- package/dist/resources/extensions/gsd/memory-store.js +299 -6
- package/dist/resources/extensions/gsd/pre-execution-checks.js +12 -8
- package/dist/resources/extensions/gsd/prompts/add-tests.md +1 -0
- package/dist/resources/extensions/gsd/prompts/execute-task.md +1 -1
- package/dist/resources/extensions/gsd/prompts/plan-slice.md +1 -0
- package/dist/resources/extensions/gsd/tools/memory-tools.js +306 -0
- package/dist/resources/extensions/gsd/tools/workflow-tool-executors.js +14 -0
- package/dist/resources/extensions/search-the-web/command-search-provider.js +4 -1
- package/dist/resources/extensions/search-the-web/native-search.js +13 -2
- package/dist/tsconfig.extensions.tsbuildinfo +1 -1
- package/dist/web/standalone/.next/BUILD_ID +1 -1
- package/dist/web/standalone/.next/app-path-routes-manifest.json +10 -10
- package/dist/web/standalone/.next/build-manifest.json +2 -2
- package/dist/web/standalone/.next/prerender-manifest.json +3 -3
- package/dist/web/standalone/.next/server/app/_global-error.html +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.html +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.html +1 -1
- package/dist/web/standalone/.next/server/app/index.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/_index.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app-paths-manifest.json +10 -10
- package/dist/web/standalone/.next/server/middleware-build-manifest.js +1 -1
- package/dist/web/standalone/.next/server/middleware-manifest.json +5 -5
- package/dist/web/standalone/.next/server/pages/404.html +1 -1
- package/dist/web/standalone/.next/server/pages/500.html +1 -1
- package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
- package/package.json +1 -1
- package/packages/mcp-server/dist/server.d.ts.map +1 -1
- package/packages/mcp-server/dist/server.js +12 -10
- package/packages/mcp-server/dist/server.js.map +1 -1
- package/packages/mcp-server/dist/session-manager.d.ts.map +1 -1
- package/packages/mcp-server/dist/session-manager.js +8 -1
- package/packages/mcp-server/dist/session-manager.js.map +1 -1
- package/packages/mcp-server/dist/workflow-tools.d.ts +1 -0
- package/packages/mcp-server/dist/workflow-tools.d.ts.map +1 -1
- package/packages/mcp-server/dist/workflow-tools.js +207 -71
- package/packages/mcp-server/dist/workflow-tools.js.map +1 -1
- package/packages/mcp-server/src/mcp-server.test.ts +40 -4
- package/packages/mcp-server/src/server.ts +12 -10
- package/packages/mcp-server/src/session-manager.ts +10 -3
- package/packages/mcp-server/src/workflow-tools.test.ts +346 -1
- package/packages/mcp-server/src/workflow-tools.ts +228 -75
- package/packages/mcp-server/tsconfig.tsbuildinfo +1 -1
- package/packages/pi-agent-core/tsconfig.tsbuildinfo +1 -1
- package/packages/pi-ai/dist/index.d.ts +1 -0
- package/packages/pi-ai/dist/index.d.ts.map +1 -1
- package/packages/pi-ai/dist/index.js +1 -0
- package/packages/pi-ai/dist/index.js.map +1 -1
- package/packages/pi-ai/dist/providers/api-family.d.ts +27 -0
- package/packages/pi-ai/dist/providers/api-family.d.ts.map +1 -0
- package/packages/pi-ai/dist/providers/api-family.js +47 -0
- package/packages/pi-ai/dist/providers/api-family.js.map +1 -0
- package/packages/pi-ai/dist/providers/api-family.test.d.ts +2 -0
- package/packages/pi-ai/dist/providers/api-family.test.d.ts.map +1 -0
- package/packages/pi-ai/dist/providers/api-family.test.js +101 -0
- package/packages/pi-ai/dist/providers/api-family.test.js.map +1 -0
- package/packages/pi-ai/src/index.ts +1 -0
- package/packages/pi-ai/src/providers/api-family.test.ts +129 -0
- package/packages/pi-ai/src/providers/api-family.ts +57 -0
- package/packages/pi-ai/tsconfig.tsbuildinfo +1 -1
- package/packages/pi-coding-agent/dist/cli/args.d.ts +6 -0
- package/packages/pi-coding-agent/dist/cli/args.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/cli/args.js +14 -4
- package/packages/pi-coding-agent/dist/cli/args.js.map +1 -1
- package/packages/pi-coding-agent/dist/cli/args.test.d.ts +2 -0
- package/packages/pi-coding-agent/dist/cli/args.test.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/cli/args.test.js +38 -0
- package/packages/pi-coding-agent/dist/cli/args.test.js.map +1 -0
- package/packages/pi-coding-agent/dist/core/extensions/runner.d.ts +1 -0
- package/packages/pi-coding-agent/dist/core/extensions/runner.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/extensions/runner.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/extensions/types.d.ts +2 -1
- package/packages/pi-coding-agent/dist/core/extensions/types.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/extensions/types.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/retry-handler.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/retry-handler.js +4 -1
- package/packages/pi-coding-agent/dist/core/retry-handler.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/sdk.d.ts +10 -0
- package/packages/pi-coding-agent/dist/core/sdk.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/sdk.js +7 -1
- package/packages/pi-coding-agent/dist/core/sdk.js.map +1 -1
- package/packages/pi-coding-agent/dist/main.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/main.js +3 -0
- package/packages/pi-coding-agent/dist/main.js.map +1 -1
- package/packages/pi-coding-agent/src/cli/args.test.ts +44 -0
- package/packages/pi-coding-agent/src/cli/args.ts +21 -6
- package/packages/pi-coding-agent/src/core/extensions/runner.ts +4 -1
- package/packages/pi-coding-agent/src/core/extensions/types.ts +2 -2
- package/packages/pi-coding-agent/src/core/retry-handler.ts +4 -1
- package/packages/pi-coding-agent/src/core/sdk.ts +17 -1
- package/packages/pi-coding-agent/src/main.ts +4 -0
- package/packages/pi-coding-agent/tsconfig.tsbuildinfo +1 -1
- package/src/resources/extensions/gsd/auto/loop-deps.ts +2 -10
- package/src/resources/extensions/gsd/auto/phases.ts +3 -0
- package/src/resources/extensions/gsd/auto-dashboard.ts +25 -1
- package/src/resources/extensions/gsd/auto-direct-dispatch.ts +15 -2
- package/src/resources/extensions/gsd/auto-dispatch.ts +21 -7
- package/src/resources/extensions/gsd/auto-model-selection.ts +3 -1
- package/src/resources/extensions/gsd/auto-prompts.ts +33 -9
- package/src/resources/extensions/gsd/auto-worktree.ts +16 -1
- package/src/resources/extensions/gsd/bootstrap/memory-tools.ts +158 -0
- package/src/resources/extensions/gsd/bootstrap/register-extension.ts +2 -0
- package/src/resources/extensions/gsd/bootstrap/system-context.ts +20 -4
- package/src/resources/extensions/gsd/commands/handlers/onboarding.ts +65 -131
- package/src/resources/extensions/gsd/commands/handlers/ops.ts +5 -0
- package/src/resources/extensions/gsd/commands-memory.ts +551 -0
- package/src/resources/extensions/gsd/doctor-git-checks.ts +23 -2
- package/src/resources/extensions/gsd/gsd-db.ts +273 -4
- package/src/resources/extensions/gsd/memory-embeddings.ts +235 -0
- package/src/resources/extensions/gsd/memory-extractor.ts +100 -34
- package/src/resources/extensions/gsd/memory-ingest.ts +286 -0
- package/src/resources/extensions/gsd/memory-relations.ts +240 -0
- package/src/resources/extensions/gsd/memory-source-store.ts +138 -0
- package/src/resources/extensions/gsd/memory-store.ts +351 -7
- package/src/resources/extensions/gsd/pre-execution-checks.ts +12 -8
- package/src/resources/extensions/gsd/prompts/add-tests.md +1 -0
- package/src/resources/extensions/gsd/prompts/execute-task.md +1 -1
- package/src/resources/extensions/gsd/prompts/plan-slice.md +1 -0
- package/src/resources/extensions/gsd/tests/auto-dashboard.test.ts +49 -0
- package/src/resources/extensions/gsd/tests/complete-slice.test.ts +2 -2
- package/src/resources/extensions/gsd/tests/complete-task.test.ts +2 -2
- package/src/resources/extensions/gsd/tests/escalation.test.ts +2 -2
- package/src/resources/extensions/gsd/tests/gsd-db.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/integration/doctor-git-symlink-cwd.test.ts +79 -0
- package/src/resources/extensions/gsd/tests/md-importer.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/memory-embeddings.test.ts +213 -0
- package/src/resources/extensions/gsd/tests/memory-ingest.test.ts +153 -0
- package/src/resources/extensions/gsd/tests/memory-maintenance.test.ts +107 -0
- package/src/resources/extensions/gsd/tests/memory-relations.test.ts +175 -0
- package/src/resources/extensions/gsd/tests/memory-store.test.ts +2 -2
- package/src/resources/extensions/gsd/tests/memory-tools.test.ts +295 -0
- package/src/resources/extensions/gsd/tests/pre-execution-checks.test.ts +66 -0
- package/src/resources/extensions/gsd/tests/prompt-budget-enforcement.test.ts +132 -8
- package/src/resources/extensions/gsd/tests/prompts-no-gitignored-test-refs.test.ts +56 -0
- package/src/resources/extensions/gsd/tests/stash-queued-context-files.test.ts +54 -0
- package/src/resources/extensions/gsd/tests/workflow-tool-executors.test.ts +97 -0
- package/src/resources/extensions/gsd/tools/memory-tools.ts +380 -0
- package/src/resources/extensions/gsd/tools/workflow-tool-executors.ts +14 -0
- package/src/resources/extensions/gsd/workflow-logger.ts +3 -1
- package/src/resources/extensions/search-the-web/command-search-provider.ts +4 -1
- package/src/resources/extensions/search-the-web/native-search.ts +13 -3
- package/src/resources/extensions/gsd/tests/onboarding-handler-loader.test.ts +0 -41
- /package/dist/web/standalone/.next/static/{J2z3GMC9QtSLr7gyoM38c → By_yegSJ-AA1OP0QjYbSl}/_buildManifest.js +0 -0
- /package/dist/web/standalone/.next/static/{J2z3GMC9QtSLr7gyoM38c → By_yegSJ-AA1OP0QjYbSl}/_ssgManifest.js +0 -0
|
@@ -128,6 +128,14 @@ Actions (return JSON array):
|
|
|
128
128
|
- UPDATE: {"action": "UPDATE", "id": "<MEM###>", "content": "<revised text>"}
|
|
129
129
|
- REINFORCE: {"action": "REINFORCE", "id": "<MEM###>"}
|
|
130
130
|
- SUPERSEDE: {"action": "SUPERSEDE", "id": "<MEM###>", "superseded_by": "<MEM###>"}
|
|
131
|
+
- LINK: {"action": "LINK", "from": "<MEM###>", "to": "<MEM###>", "rel": "<rel>", "confidence": <0.6-0.95>}
|
|
132
|
+
|
|
133
|
+
Link relation types:
|
|
134
|
+
- related_to — two memories cover the same area
|
|
135
|
+
- depends_on — "to" is a prerequisite for "from"
|
|
136
|
+
- contradicts — "from" conflicts with "to"
|
|
137
|
+
- elaborates — "from" expands on "to"
|
|
138
|
+
- supersedes — "from" replaces "to" (rarely needed; prefer SUPERSEDE)
|
|
131
139
|
|
|
132
140
|
Rules:
|
|
133
141
|
- Don't create memories for one-off bug fixes or temporary state
|
|
@@ -135,6 +143,7 @@ Rules:
|
|
|
135
143
|
- Keep content to 1-3 sentences
|
|
136
144
|
- Confidence: 0.6 tentative, 0.8 solid, 0.95 well-confirmed
|
|
137
145
|
- Prefer fewer high-quality memories over many low-quality ones
|
|
146
|
+
- Only LINK memories that genuinely relate — don't fabricate edges
|
|
138
147
|
- Return empty array [] if nothing worth remembering
|
|
139
148
|
- NEVER include secrets, API keys, or passwords
|
|
140
149
|
|
|
@@ -262,6 +271,21 @@ export function parseMemoryResponse(raw: string): MemoryAction[] {
|
|
|
262
271
|
});
|
|
263
272
|
}
|
|
264
273
|
break;
|
|
274
|
+
case 'LINK':
|
|
275
|
+
if (
|
|
276
|
+
typeof item.from === 'string' &&
|
|
277
|
+
typeof item.to === 'string' &&
|
|
278
|
+
typeof item.rel === 'string'
|
|
279
|
+
) {
|
|
280
|
+
actions.push({
|
|
281
|
+
action: 'LINK',
|
|
282
|
+
from: item.from,
|
|
283
|
+
to: item.to,
|
|
284
|
+
rel: item.rel,
|
|
285
|
+
confidence: typeof item.confidence === 'number' ? item.confidence : undefined,
|
|
286
|
+
});
|
|
287
|
+
}
|
|
288
|
+
break;
|
|
265
289
|
}
|
|
266
290
|
}
|
|
267
291
|
|
|
@@ -273,6 +297,77 @@ export function parseMemoryResponse(raw: string): MemoryAction[] {
|
|
|
273
297
|
|
|
274
298
|
// ─── Main Extraction Function ───────────────────────────────────────────────
|
|
275
299
|
|
|
300
|
+
export interface ExtractFromTranscriptOptions {
|
|
301
|
+
/** Logical source type (e.g. "execute-task", "note", "file", "url", "artifact"). */
|
|
302
|
+
sourceType: string;
|
|
303
|
+
/** Stable identifier for the source (unit id, source id, URL, etc.). */
|
|
304
|
+
sourceId: string;
|
|
305
|
+
/** Optional scope for any CREATE actions ("project" / "global" / custom). */
|
|
306
|
+
scope?: string;
|
|
307
|
+
/** Optional tags applied to any CREATE actions. */
|
|
308
|
+
tags?: string[];
|
|
309
|
+
/** Bypass the mutex + rate-limit when caller has already vetted them. */
|
|
310
|
+
force?: boolean;
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
/**
|
|
314
|
+
* Core extractor — shared by unit post-processing and explicit ingest.
|
|
315
|
+
* Returns the applied actions for observability. Never throws.
|
|
316
|
+
*/
|
|
317
|
+
export async function extractMemoriesFromTranscript(
|
|
318
|
+
transcript: string,
|
|
319
|
+
llmCallFn: LLMCallFn,
|
|
320
|
+
opts: ExtractFromTranscriptOptions,
|
|
321
|
+
): Promise<import('./memory-store.js').MemoryAction[]> {
|
|
322
|
+
if (!opts.force) {
|
|
323
|
+
if (_extracting) return [];
|
|
324
|
+
const now = Date.now();
|
|
325
|
+
if (now - _lastExtractionTime < MIN_EXTRACTION_INTERVAL_MS) return [];
|
|
326
|
+
_lastExtractionTime = now;
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
const acquireMutex = !_extracting;
|
|
330
|
+
if (acquireMutex) _extracting = true;
|
|
331
|
+
try {
|
|
332
|
+
const trimmed = transcript.trim();
|
|
333
|
+
if (!trimmed) return [];
|
|
334
|
+
|
|
335
|
+
const safeTranscript = redactSecrets(trimmed);
|
|
336
|
+
const activeMemories = getActiveMemories().map((m) => ({
|
|
337
|
+
id: m.id,
|
|
338
|
+
category: m.category,
|
|
339
|
+
content: m.content,
|
|
340
|
+
}));
|
|
341
|
+
|
|
342
|
+
const userPrompt = buildExtractionUserPrompt(
|
|
343
|
+
opts.sourceType,
|
|
344
|
+
opts.sourceId,
|
|
345
|
+
activeMemories,
|
|
346
|
+
safeTranscript,
|
|
347
|
+
);
|
|
348
|
+
|
|
349
|
+
const response = await llmCallFn(EXTRACTION_SYSTEM, userPrompt);
|
|
350
|
+
const actions = parseMemoryResponse(response);
|
|
351
|
+
|
|
352
|
+
if (actions.length === 0) return [];
|
|
353
|
+
|
|
354
|
+
const decorated = actions.map((action): import('./memory-store.js').MemoryAction => {
|
|
355
|
+
if (action.action !== 'CREATE') return action;
|
|
356
|
+
return {
|
|
357
|
+
...action,
|
|
358
|
+
scope: action.scope ?? opts.scope,
|
|
359
|
+
tags: action.tags ?? opts.tags,
|
|
360
|
+
};
|
|
361
|
+
});
|
|
362
|
+
applyMemoryActions(decorated, opts.sourceType, opts.sourceId);
|
|
363
|
+
return decorated;
|
|
364
|
+
} catch {
|
|
365
|
+
return [];
|
|
366
|
+
} finally {
|
|
367
|
+
if (acquireMutex) _extracting = false;
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
|
|
276
371
|
/**
|
|
277
372
|
* Extract memories from a completed unit's activity log.
|
|
278
373
|
* Fire-and-forget — never throws, mutex-guarded, respects rate limiting.
|
|
@@ -283,22 +378,16 @@ export async function extractMemoriesFromUnit(
|
|
|
283
378
|
unitId: string,
|
|
284
379
|
llmCallFn: LLMCallFn,
|
|
285
380
|
): Promise<void> {
|
|
286
|
-
// Mutex guard
|
|
287
381
|
if (_extracting) return;
|
|
288
382
|
|
|
289
|
-
// Rate limit
|
|
290
383
|
const now = Date.now();
|
|
291
384
|
if (now - _lastExtractionTime < MIN_EXTRACTION_INTERVAL_MS) return;
|
|
292
385
|
|
|
293
|
-
// Skip certain unit types
|
|
294
386
|
if (SKIP_TYPES.has(unitType)) return;
|
|
295
387
|
|
|
296
388
|
const unitKey = `${unitType}/${unitId}`;
|
|
297
|
-
|
|
298
|
-
// Already processed
|
|
299
389
|
if (isUnitProcessed(unitKey)) return;
|
|
300
390
|
|
|
301
|
-
// Check file size
|
|
302
391
|
try {
|
|
303
392
|
const stat = statSync(activityFile);
|
|
304
393
|
if (stat.size < MIN_ACTIVITY_SIZE) return;
|
|
@@ -308,41 +397,18 @@ export async function extractMemoriesFromUnit(
|
|
|
308
397
|
|
|
309
398
|
_extracting = true;
|
|
310
399
|
_lastExtractionTime = now;
|
|
311
|
-
|
|
312
400
|
try {
|
|
313
|
-
// Read and parse activity file
|
|
314
401
|
const raw = readFileSync(activityFile, 'utf-8');
|
|
315
402
|
const transcript = extractTranscriptFromActivity(raw);
|
|
316
403
|
if (!transcript.trim()) return;
|
|
317
404
|
|
|
318
|
-
|
|
319
|
-
|
|
405
|
+
await extractMemoriesFromTranscript(transcript, llmCallFn, {
|
|
406
|
+
sourceType: unitType,
|
|
407
|
+
sourceId: unitId,
|
|
408
|
+
force: true,
|
|
409
|
+
});
|
|
320
410
|
|
|
321
|
-
// Get current memories for context
|
|
322
|
-
const activeMemories = getActiveMemories().map(m => ({
|
|
323
|
-
id: m.id,
|
|
324
|
-
category: m.category,
|
|
325
|
-
content: m.content,
|
|
326
|
-
}));
|
|
327
|
-
|
|
328
|
-
// Build prompts
|
|
329
|
-
const userPrompt = buildExtractionUserPrompt(unitType, unitId, activeMemories, safeTranscript);
|
|
330
|
-
|
|
331
|
-
// Call LLM
|
|
332
|
-
const response = await llmCallFn(EXTRACTION_SYSTEM, userPrompt);
|
|
333
|
-
|
|
334
|
-
// Parse response
|
|
335
|
-
const actions = parseMemoryResponse(response);
|
|
336
|
-
|
|
337
|
-
// Apply actions
|
|
338
|
-
if (actions.length > 0) {
|
|
339
|
-
applyMemoryActions(actions, unitType, unitId);
|
|
340
|
-
}
|
|
341
|
-
|
|
342
|
-
// Decay stale memories periodically
|
|
343
411
|
decayStaleMemories(20);
|
|
344
|
-
|
|
345
|
-
// Mark unit as processed
|
|
346
412
|
markUnitProcessed(unitKey, activityFile);
|
|
347
413
|
} catch {
|
|
348
414
|
// Non-fatal — memory extraction failure should never affect auto-mode
|
|
@@ -0,0 +1,286 @@
|
|
|
1
|
+
// GSD Memory Ingest — turn raw content into memories
|
|
2
|
+
//
|
|
3
|
+
// Provides four entry points: ingestNote (inline text), ingestFile (local
|
|
4
|
+
// path), ingestUrl (HTTP resource), and ingestArtifact (a named .gsd/ artifact
|
|
5
|
+
// for a given milestone). Each one inserts a row into `memory_sources` and,
|
|
6
|
+
// if an LLM call is available, fires the extractor against the content with
|
|
7
|
+
// source-specific scope/tags.
|
|
8
|
+
//
|
|
9
|
+
// All four functions are safe to call without an LLM — they still persist the
|
|
10
|
+
// source. This means ingestion is decoupled from extraction; a later
|
|
11
|
+
// `/gsd memory rebuild` can re-extract from persisted sources.
|
|
12
|
+
|
|
13
|
+
import { existsSync, readFileSync, statSync } from "node:fs";
|
|
14
|
+
import { basename, isAbsolute, resolve } from "node:path";
|
|
15
|
+
import type { ExtensionContext } from "@gsd/pi-coding-agent";
|
|
16
|
+
|
|
17
|
+
import { createMemorySource, type MemorySource, type MemorySourceKind } from "./memory-source-store.js";
|
|
18
|
+
import { buildMemoryLLMCall, extractMemoriesFromTranscript } from "./memory-extractor.js";
|
|
19
|
+
import type { MemoryAction } from "./memory-store.js";
|
|
20
|
+
import { resolveMilestoneFile } from "./paths.js";
|
|
21
|
+
import { logWarning } from "./workflow-logger.js";
|
|
22
|
+
|
|
23
|
+
// ─── Types ──────────────────────────────────────────────────────────────────
|
|
24
|
+
|
|
25
|
+
export interface IngestOptions {
|
|
26
|
+
scope?: string;
|
|
27
|
+
tags?: string[];
|
|
28
|
+
/** Skip LLM extraction — just persist the source row. */
|
|
29
|
+
extract?: boolean;
|
|
30
|
+
/**
|
|
31
|
+
* Soft upper bound on source content size (bytes). Files/URLs above this
|
|
32
|
+
* are truncated before hashing and storing. Default 256 KiB.
|
|
33
|
+
*/
|
|
34
|
+
maxBytes?: number;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export interface IngestResult {
|
|
38
|
+
sourceId: string;
|
|
39
|
+
duplicate: boolean;
|
|
40
|
+
extracted: MemoryAction[];
|
|
41
|
+
kind: MemorySourceKind;
|
|
42
|
+
title: string | null;
|
|
43
|
+
uri: string | null;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const DEFAULT_MAX_BYTES = 256 * 1024;
|
|
47
|
+
|
|
48
|
+
function truncate(content: string, maxBytes: number): string {
|
|
49
|
+
const buf = Buffer.from(content, "utf-8");
|
|
50
|
+
if (buf.byteLength <= maxBytes) return content;
|
|
51
|
+
return `${buf.subarray(0, maxBytes).toString("utf-8")}\n\n…[truncated to ${maxBytes} bytes]`;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
async function maybeExtract(
|
|
55
|
+
ctx: ExtensionContext | null,
|
|
56
|
+
source: { kind: MemorySourceKind; id: string },
|
|
57
|
+
content: string,
|
|
58
|
+
opts: IngestOptions,
|
|
59
|
+
): Promise<MemoryAction[]> {
|
|
60
|
+
if (opts.extract === false || !ctx) return [];
|
|
61
|
+
const llmCallFn = buildMemoryLLMCall(ctx);
|
|
62
|
+
if (!llmCallFn) return [];
|
|
63
|
+
try {
|
|
64
|
+
return await extractMemoriesFromTranscript(content, llmCallFn, {
|
|
65
|
+
sourceType: source.kind,
|
|
66
|
+
sourceId: source.id,
|
|
67
|
+
scope: opts.scope,
|
|
68
|
+
tags: opts.tags,
|
|
69
|
+
force: true,
|
|
70
|
+
});
|
|
71
|
+
} catch (err) {
|
|
72
|
+
logWarning("memory-ingest", `extraction failed: ${(err as Error).message}`);
|
|
73
|
+
return [];
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function sourceCreateFailure(kind: MemorySourceKind): IngestResult {
|
|
78
|
+
return {
|
|
79
|
+
sourceId: "",
|
|
80
|
+
duplicate: false,
|
|
81
|
+
extracted: [],
|
|
82
|
+
kind,
|
|
83
|
+
title: null,
|
|
84
|
+
uri: null,
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// ─── ingestNote ─────────────────────────────────────────────────────────────
|
|
89
|
+
|
|
90
|
+
export async function ingestNote(
|
|
91
|
+
note: string,
|
|
92
|
+
ctx: ExtensionContext | null,
|
|
93
|
+
opts: IngestOptions = {},
|
|
94
|
+
): Promise<IngestResult> {
|
|
95
|
+
const trimmed = note.trim();
|
|
96
|
+
if (!trimmed) return sourceCreateFailure("note");
|
|
97
|
+
|
|
98
|
+
const maxBytes = opts.maxBytes ?? DEFAULT_MAX_BYTES;
|
|
99
|
+
const content = truncate(trimmed, maxBytes);
|
|
100
|
+
|
|
101
|
+
const created = createMemorySource({
|
|
102
|
+
kind: "note",
|
|
103
|
+
uri: null,
|
|
104
|
+
title: content.slice(0, 80).replace(/\s+/g, " ").trim(),
|
|
105
|
+
content,
|
|
106
|
+
scope: opts.scope,
|
|
107
|
+
tags: opts.tags,
|
|
108
|
+
});
|
|
109
|
+
if (!created) return sourceCreateFailure("note");
|
|
110
|
+
|
|
111
|
+
const extracted = created.duplicate
|
|
112
|
+
? []
|
|
113
|
+
: await maybeExtract(ctx, { kind: "note", id: created.id }, content, opts);
|
|
114
|
+
|
|
115
|
+
return {
|
|
116
|
+
sourceId: created.id,
|
|
117
|
+
duplicate: created.duplicate,
|
|
118
|
+
extracted,
|
|
119
|
+
kind: "note",
|
|
120
|
+
title: content.slice(0, 80),
|
|
121
|
+
uri: null,
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// ─── ingestFile ─────────────────────────────────────────────────────────────
|
|
126
|
+
|
|
127
|
+
export async function ingestFile(
|
|
128
|
+
path: string,
|
|
129
|
+
ctx: ExtensionContext | null,
|
|
130
|
+
opts: IngestOptions = {},
|
|
131
|
+
): Promise<IngestResult> {
|
|
132
|
+
const abs = isAbsolute(path) ? path : resolve(process.cwd(), path);
|
|
133
|
+
if (!existsSync(abs)) {
|
|
134
|
+
throw new Error(`File not found: ${abs}`);
|
|
135
|
+
}
|
|
136
|
+
const stat = statSync(abs);
|
|
137
|
+
if (!stat.isFile()) {
|
|
138
|
+
throw new Error(`Not a file: ${abs}`);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
const maxBytes = opts.maxBytes ?? DEFAULT_MAX_BYTES;
|
|
142
|
+
const raw = readFileSync(abs, "utf-8");
|
|
143
|
+
const content = truncate(raw, maxBytes);
|
|
144
|
+
const title = basename(abs);
|
|
145
|
+
|
|
146
|
+
const created = createMemorySource({
|
|
147
|
+
kind: "file",
|
|
148
|
+
uri: abs,
|
|
149
|
+
title,
|
|
150
|
+
content,
|
|
151
|
+
scope: opts.scope,
|
|
152
|
+
tags: opts.tags,
|
|
153
|
+
});
|
|
154
|
+
if (!created) return { ...sourceCreateFailure("file"), uri: abs, title };
|
|
155
|
+
|
|
156
|
+
const extracted = created.duplicate
|
|
157
|
+
? []
|
|
158
|
+
: await maybeExtract(ctx, { kind: "file", id: created.id }, content, opts);
|
|
159
|
+
|
|
160
|
+
return {
|
|
161
|
+
sourceId: created.id,
|
|
162
|
+
duplicate: created.duplicate,
|
|
163
|
+
extracted,
|
|
164
|
+
kind: "file",
|
|
165
|
+
title,
|
|
166
|
+
uri: abs,
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// ─── ingestUrl ──────────────────────────────────────────────────────────────
|
|
171
|
+
|
|
172
|
+
export async function ingestUrl(
|
|
173
|
+
url: string,
|
|
174
|
+
ctx: ExtensionContext | null,
|
|
175
|
+
opts: IngestOptions = {},
|
|
176
|
+
): Promise<IngestResult> {
|
|
177
|
+
const maxBytes = opts.maxBytes ?? DEFAULT_MAX_BYTES;
|
|
178
|
+
let body: string;
|
|
179
|
+
let title: string | null = null;
|
|
180
|
+
try {
|
|
181
|
+
const res = await fetch(url, { redirect: "follow" });
|
|
182
|
+
if (!res.ok) throw new Error(`HTTP ${res.status} ${res.statusText}`);
|
|
183
|
+
body = await res.text();
|
|
184
|
+
const titleMatch = body.match(/<title[^>]*>([\s\S]*?)<\/title>/i);
|
|
185
|
+
if (titleMatch) title = titleMatch[1].trim().slice(0, 200);
|
|
186
|
+
} catch (err) {
|
|
187
|
+
throw new Error(`Fetch failed for ${url}: ${(err as Error).message}`);
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
const content = truncate(stripHtml(body), maxBytes);
|
|
191
|
+
if (!content.trim()) {
|
|
192
|
+
throw new Error(`URL produced empty content: ${url}`);
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
const created = createMemorySource({
|
|
196
|
+
kind: "url",
|
|
197
|
+
uri: url,
|
|
198
|
+
title: title ?? url,
|
|
199
|
+
content,
|
|
200
|
+
scope: opts.scope,
|
|
201
|
+
tags: opts.tags,
|
|
202
|
+
});
|
|
203
|
+
if (!created) return { ...sourceCreateFailure("url"), uri: url, title };
|
|
204
|
+
|
|
205
|
+
const extracted = created.duplicate
|
|
206
|
+
? []
|
|
207
|
+
: await maybeExtract(ctx, { kind: "url", id: created.id }, content, opts);
|
|
208
|
+
|
|
209
|
+
return {
|
|
210
|
+
sourceId: created.id,
|
|
211
|
+
duplicate: created.duplicate,
|
|
212
|
+
extracted,
|
|
213
|
+
kind: "url",
|
|
214
|
+
title: title ?? url,
|
|
215
|
+
uri: url,
|
|
216
|
+
};
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
function stripHtml(html: string): string {
|
|
220
|
+
return html
|
|
221
|
+
.replace(/<script[\s\S]*?<\/script>/gi, " ")
|
|
222
|
+
.replace(/<style[\s\S]*?<\/style>/gi, " ")
|
|
223
|
+
.replace(/<!--[\s\S]*?-->/g, " ")
|
|
224
|
+
.replace(/<[^>]+>/g, " ")
|
|
225
|
+
.replace(/\s+/g, " ")
|
|
226
|
+
.trim();
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
// ─── ingestArtifact ─────────────────────────────────────────────────────────
|
|
230
|
+
|
|
231
|
+
/**
|
|
232
|
+
* Ingest a named artifact from a milestone directory (e.g. LEARNINGS,
|
|
233
|
+
* SUMMARY, CONTEXT). Resolves through `resolveMilestoneFile` so worktree
|
|
234
|
+
* layouts are handled correctly.
|
|
235
|
+
*/
|
|
236
|
+
export async function ingestArtifact(
|
|
237
|
+
basePath: string,
|
|
238
|
+
milestoneId: string,
|
|
239
|
+
artifactType: string,
|
|
240
|
+
ctx: ExtensionContext | null,
|
|
241
|
+
opts: IngestOptions = {},
|
|
242
|
+
): Promise<IngestResult> {
|
|
243
|
+
const file = resolveMilestoneFile(basePath, milestoneId, artifactType);
|
|
244
|
+
if (!file || !existsSync(file)) {
|
|
245
|
+
throw new Error(`Artifact not found: ${milestoneId}-${artifactType}.md`);
|
|
246
|
+
}
|
|
247
|
+
const maxBytes = opts.maxBytes ?? DEFAULT_MAX_BYTES;
|
|
248
|
+
const content = truncate(readFileSync(file, "utf-8"), maxBytes);
|
|
249
|
+
const title = `${milestoneId}-${artifactType}`;
|
|
250
|
+
const created = createMemorySource({
|
|
251
|
+
kind: "artifact",
|
|
252
|
+
uri: file,
|
|
253
|
+
title,
|
|
254
|
+
content,
|
|
255
|
+
scope: opts.scope,
|
|
256
|
+
tags: [...(opts.tags ?? []), milestoneId, artifactType.toLowerCase()],
|
|
257
|
+
});
|
|
258
|
+
if (!created) return { ...sourceCreateFailure("artifact"), uri: file, title };
|
|
259
|
+
|
|
260
|
+
const extracted = created.duplicate
|
|
261
|
+
? []
|
|
262
|
+
: await maybeExtract(ctx, { kind: "artifact", id: created.id }, content, opts);
|
|
263
|
+
|
|
264
|
+
return {
|
|
265
|
+
sourceId: created.id,
|
|
266
|
+
duplicate: created.duplicate,
|
|
267
|
+
extracted,
|
|
268
|
+
kind: "artifact",
|
|
269
|
+
title,
|
|
270
|
+
uri: file,
|
|
271
|
+
};
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
// ─── Helpers ────────────────────────────────────────────────────────────────
|
|
275
|
+
|
|
276
|
+
export function summarizeIngest(result: IngestResult): string {
|
|
277
|
+
if (!result.sourceId) return "Ingest failed: could not persist source.";
|
|
278
|
+
const status = result.duplicate ? "duplicate (content_hash match)" : "new source";
|
|
279
|
+
const extracted = result.extracted.length === 0
|
|
280
|
+
? "no memories extracted"
|
|
281
|
+
: `${result.extracted.length} memor${result.extracted.length === 1 ? "y" : "ies"} applied`;
|
|
282
|
+
const label = result.title ? ` "${result.title}"` : "";
|
|
283
|
+
return `Ingested ${result.kind}${label} as ${result.sourceId} (${status}, ${extracted}).`;
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
export type { MemorySource };
|