akm-cli 0.9.6 → 0.9.8-beta.1
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/CHANGELOG.md +408 -0
- package/dist/assets/hints/cli-hints-full.md +3 -3
- package/dist/assets/improve-strategies/catchup.json +40 -11
- package/dist/assets/improve-strategies/thorough.json +45 -7
- package/dist/assets/tasks/improve/akm-improve-frequent.yml +2 -2
- package/dist/commands/agent/contribute-cli.js +11 -0
- package/dist/commands/env/env.js +2 -21
- package/dist/commands/health/checks.js +0 -25
- package/dist/commands/health/improve-metrics.js +8 -34
- package/dist/commands/health/windows.js +0 -4
- package/dist/commands/health.js +1 -35
- package/dist/commands/improve/consolidate/eligibility.js +11 -5
- package/dist/commands/improve/extract.js +36 -32
- package/dist/commands/improve/improve-cli.js +1 -1
- package/dist/commands/improve/improve-strategies.js +0 -4
- package/dist/commands/improve/memory/memory-belief.js +15 -5
- package/dist/commands/improve/memory/memory-contradiction-detect.js +16 -25
- package/dist/commands/improve/memory/memory-improve.js +9 -20
- package/dist/commands/improve/preparation.js +53 -37
- package/dist/commands/improve/reflect.js +14 -18
- package/dist/commands/lint/base-linter.js +182 -19
- package/dist/commands/lint/index.js +21 -9
- package/dist/commands/migrate/config-extra-params.js +61 -0
- package/dist/commands/migrate/dead-residue.js +113 -0
- package/dist/commands/migrate/stale-txn.js +49 -0
- package/dist/commands/migrate-cli.js +42 -1
- package/dist/commands/proposal/proposal.js +1 -21
- package/dist/commands/proposal/repository.js +0 -4
- package/dist/commands/proposal/validators/proposal-quality-validators.js +0 -6
- package/dist/commands/read/curate.js +51 -18
- package/dist/commands/read/search-cli.js +24 -1
- package/dist/commands/read/show.js +2 -1
- package/dist/commands/sources/installed-stashes.js +5 -1
- package/dist/commands/sources/self-update.js +38 -1
- package/dist/commands/sources/sources-cli.js +17 -1
- package/dist/commands/tasks/tasks.js +0 -8
- package/dist/commands/url-checker.js +20 -28
- package/dist/core/adapter/adapters/agent-skills-adapter.js +2 -6
- package/dist/core/adapter/adapters/akm-adapter.js +2 -3
- package/dist/core/adapter/adapters/akm-lint.js +2 -20
- package/dist/core/adapter/adapters/akm-task-adapter.js +2 -6
- package/dist/core/adapter/adapters/akm-workflow-adapter.js +2 -6
- package/dist/core/adapter/adapters/dotenv-adapter.js +2 -22
- package/dist/core/adapter/adapters/generic-files-adapter.js +3 -9
- package/dist/core/adapter/adapters/llm-wiki-adapter.js +2 -6
- package/dist/core/adapter/adapters/okf-adapter.js +7 -18
- package/dist/core/adapter/adapters/shared.js +2 -0
- package/dist/core/adapter/adapters/tool-dir-shared.js +3 -9
- package/dist/core/adapter/adapters/website-snapshot-adapter.js +2 -6
- package/dist/core/adapter/validate-context.js +1 -3
- package/dist/core/asset/asset-placement.js +14 -5
- package/dist/core/asset/frontmatter.js +212 -0
- package/dist/core/asset/memory-archive.js +97 -0
- package/dist/core/common.js +66 -2
- package/dist/core/config/config-walker.js +6 -10
- package/dist/core/config/config.js +11 -8
- package/dist/core/config/engine-semantics.js +0 -2
- package/dist/core/extra-params.js +17 -13
- package/dist/core/improve-result.js +1 -3
- package/dist/core/json-schema.js +9 -11
- package/dist/core/state/migrations.js +52 -2
- package/dist/core/state-db.js +2 -1
- package/dist/execution/executable-identity.js +1 -3
- package/dist/execution/guarded-source.js +1 -6
- package/dist/indexer/bundle-identity-guard.js +6 -1
- package/dist/indexer/db/graph-db.js +139 -154
- package/dist/indexer/ensure-index.js +11 -19
- package/dist/indexer/graph/graph-boost.js +23 -34
- package/dist/indexer/graph/graph-extraction.js +12 -2
- package/dist/indexer/indexer.js +1 -1
- package/dist/indexer/lookup/adapter-concept-owner.js +12 -9
- package/dist/indexer/passes/memory-inference.js +7 -2
- package/dist/indexer/scan/drain-dir.js +2 -4
- package/dist/indexer/search/db-search.js +3 -3
- package/dist/indexer/search/fts-query.js +10 -15
- package/dist/indexer/search/search-source.js +0 -13
- package/dist/indexer/usage/usage-events.js +9 -1
- package/dist/indexer/walk/walker.js +11 -6
- package/dist/integrations/harnesses/claude/result-extractor.js +30 -15
- package/dist/integrations/harnesses/codex/result-extractor.js +43 -6
- package/dist/integrations/harnesses/copilot/result-extractor.js +39 -12
- package/dist/integrations/harnesses/gemini/result-extractor.js +40 -12
- package/dist/integrations/harnesses/index.js +0 -4
- package/dist/integrations/harnesses/openhands/result-extractor.js +51 -19
- package/dist/integrations/harnesses/pi/result-extractor.js +45 -12
- package/dist/integrations/lockfile.js +0 -14
- package/dist/integrations/session-logs/index.js +0 -81
- package/dist/llm/client.js +0 -12
- package/dist/llm/memory-infer.js +1 -3
- package/dist/llm/usage-telemetry.js +1 -3
- package/dist/registry/create-provider-registry.js +4 -0
- package/dist/registry/factory.js +4 -0
- package/dist/registry/resolve.js +23 -8
- package/dist/runtime.js +0 -12
- package/dist/scripts/akm-migrate-node.js +155 -251
- package/dist/scripts/akm-migrate.js +155 -251
- package/dist/setup/setup.js +6 -12
- package/dist/sources/providers/git-install.js +7 -2
- package/dist/sources/providers/tar-utils.js +1 -7
- package/dist/sources/snapshot-fetchers/content-extract.js +0 -17
- package/dist/sources/snapshot-fetchers/website-ingest.js +126 -0
- package/dist/storage/managed-db.js +20 -7
- package/dist/storage/repositories/events-repository.js +0 -81
- package/dist/storage/repositories/index-connection.js +45 -3
- package/dist/storage/repositories/index-entries-repository.js +0 -17
- package/dist/storage/repositories/index-fts-repository.js +24 -30
- package/dist/storage/repositories/index-utility-repository.js +0 -57
- package/dist/storage/repositories/index-vec-repository.js +25 -27
- package/dist/storage/repositories/task-history-repository.js +9 -3
- package/dist/tasks/backends/cron.js +49 -9
- package/dist/tasks/backends/launchd.js +8 -18
- package/dist/tasks/resolve-akm-bin.js +17 -2
- package/dist/tasks/run/task-history.js +21 -31
- package/dist/tasks/scheduler-invocation.js +8 -1
- package/dist/tasks/scheduler-sync.js +1 -6
- package/dist/tasks/source/bounded-document.js +1 -14
- package/dist/tasks/source/parse-task-source.js +23 -9
- package/dist/workflows/exec/child-workflow.js +1 -1
- package/dist/workflows/exec/native-executor.js +2 -2
- package/dist/workflows/exec/step-work.js +5 -17
- package/dist/workflows/exec/worktree.js +40 -6
- package/dist/workflows/freeze/task-bindings.js +2 -4
- package/dist/workflows/ir/compile.js +3 -14
- package/dist/workflows/ir/schema-v4.js +4 -6
- package/dist/workflows/ir/schema.js +2 -5
- package/dist/workflows/parser.js +23 -26
- package/dist/workflows/source-files.js +8 -13
- package/dist/workflows/source-ir/schema.js +1 -19
- package/docs/migration/v0.9.0-troubleshooting.md +12 -2
- package/docs/reference/cli.md +7 -1
- package/docs/reference/workflow-schema.md +24 -0
- package/package.json +2 -5
- package/dist/assets/improve-strategies/frequent.json +0 -15
- package/dist/assets/improve-strategies/memory-focus.json +0 -15
- package/dist/workflows/source-ir/compare.js +0 -17
|
@@ -33,42 +33,16 @@ export function taskFailureDetail(row) {
|
|
|
33
33
|
}
|
|
34
34
|
}
|
|
35
35
|
/**
|
|
36
|
-
*
|
|
37
|
-
*
|
|
38
|
-
*
|
|
39
|
-
*
|
|
40
|
-
* `
|
|
41
|
-
*
|
|
42
|
-
*
|
|
43
|
-
* - LEGACY rows (no marker, written before P1b's F-2 re-code) stored
|
|
44
|
-
* `target_kind: "prompt"` for the same arm — and `"command"` for the
|
|
45
|
-
* UNRELATED native shell/script arm, which must NOT be counted here.
|
|
46
|
-
* So an unmarked `"command"` row is a legacy shell/script run, not an
|
|
47
|
-
* agent/LLM one; a marked `"command"` row (or an unmarked `"prompt"` row) is.
|
|
36
|
+
* `akm health`'s `agentFailureRate` predicate: true for a `task_history` row
|
|
37
|
+
* that represents a prepared command (agent/LLM) result. `target_kind` is
|
|
38
|
+
* read in the current (post-D8) vocabulary — the
|
|
39
|
+
* `025-task-history-vocabulary-backfill` state migration rewrites every
|
|
40
|
+
* legacy-vocabulary row (which stored the agent/LLM arm as `"prompt"` and
|
|
41
|
+
* the native shell/script arm as `"command"`) before this ever runs against
|
|
42
|
+
* it, so a `"command"` row here is unambiguously the agent/LLM arm.
|
|
48
43
|
*/
|
|
49
44
|
export function isAgentTaskHistoryRow(row) {
|
|
50
|
-
|
|
51
|
-
// (e.g. improve-pipeline task_history rows, target_kind "improve") carry
|
|
52
|
-
// metadata_json that predates the metadataVersion:2 shape entirely, and
|
|
53
|
-
// decodeTaskHistoryMetadata throws on that — exactly like the pre-fix
|
|
54
|
-
// `target_kind === "prompt"` filter, which never called it for a row this
|
|
55
|
-
// function isn't going to count anyway. Only decode for the two target
|
|
56
|
-
// kinds this predicate can return true for.
|
|
57
|
-
if (row.target_kind !== "command" && row.target_kind !== "prompt")
|
|
58
|
-
return false;
|
|
59
|
-
// An undecodable metadata_json is by definition unmarked: pre-P1b rows can
|
|
60
|
-
// carry shapes decodeTaskHistoryMetadata rejects, and `akm health` must
|
|
61
|
-
// classify them as legacy rather than throw (round-3 review advisory).
|
|
62
|
-
let marked = false;
|
|
63
|
-
try {
|
|
64
|
-
marked = decodeTaskHistoryMetadata(row.metadata_json).targetVocab === 2;
|
|
65
|
-
}
|
|
66
|
-
catch {
|
|
67
|
-
marked = false;
|
|
68
|
-
}
|
|
69
|
-
if (row.target_kind === "command")
|
|
70
|
-
return marked;
|
|
71
|
-
return !marked;
|
|
45
|
+
return row.target_kind === "command";
|
|
72
46
|
}
|
|
73
47
|
function createUnknownImproveMetrics() {
|
|
74
48
|
return {
|
|
@@ -148,10 +148,6 @@ export function buildWindowMetrics(db, stateDbPath, since, until, now = () => Da
|
|
|
148
148
|
const failedTaskRows = taskRows.filter((row) => row.status === "failed");
|
|
149
149
|
const activeRows = taskRows.filter((row) => row.status === "active" && row.completed_at === null);
|
|
150
150
|
const stuckActiveRuns = activeRows.filter((row) => now() - new Date(row.started_at).getTime() > ACTIVE_RUN_WARN_MS).length;
|
|
151
|
-
// D8 (spec §5.3): a marked "command" row or a legacy (unmarked) "prompt"
|
|
152
|
-
// row is the agent/LLM arm; an unmarked "command" row is the legacy
|
|
153
|
-
// native shell/script arm and must not be counted here (see
|
|
154
|
-
// isAgentTaskHistoryRow's header comment for the full mapping).
|
|
155
151
|
const agentRows = taskRows.filter((row) => isAgentTaskHistoryRow(row));
|
|
156
152
|
const agentFailures = agentRows.filter((row) => {
|
|
157
153
|
const detail = taskFailureDetail(row);
|
package/dist/commands/health.js
CHANGED
|
@@ -7,10 +7,9 @@ import { resolveStashDir } from "../core/common.js";
|
|
|
7
7
|
import { loadConfig } from "../core/config/config.js";
|
|
8
8
|
import { ConfigError, UsageError } from "../core/errors.js";
|
|
9
9
|
import { readEvents } from "../core/events.js";
|
|
10
|
-
import { listTxnJournalsTolerant, TXN_SWEEP_GRACE_MS } from "../core/fs-txn.js";
|
|
11
10
|
import { openLogsDatabase } from "../core/logs-db.js";
|
|
12
11
|
import { classifyPathAccess, describeInaccessiblePath } from "../core/path-access.js";
|
|
13
|
-
import { getConfigPath,
|
|
12
|
+
import { getConfigPath, getDbPath, getStateDbPathInDataDir } from "../core/paths.js";
|
|
14
13
|
import { listExistingTableNames, openStateDatabase } from "../core/state-db.js";
|
|
15
14
|
import { DURATION_UNITS, parseDuration, parseSinceToIso } from "../core/time.js";
|
|
16
15
|
import { closeDatabase, openReadonlyExistingDatabase } from "../storage/repositories/index-connection.js";
|
|
@@ -150,37 +149,6 @@ function gatherTaskHistoryPhase(db, logsDb, since, stateDbPath, now) {
|
|
|
150
149
|
agentFailureRate,
|
|
151
150
|
};
|
|
152
151
|
}
|
|
153
|
-
/**
|
|
154
|
-
* Item 4: leftover durable-transaction journals under `$DATA/txn` (stranded
|
|
155
|
-
* recovery state seen twice in a real 0.9 migration) had zero health
|
|
156
|
-
* visibility. Uses the tolerant scan variant so one corrupt journal.json is
|
|
157
|
-
* counted rather than aborting the whole gather; a journal younger than
|
|
158
|
-
* {@link TXN_SWEEP_GRACE_MS} is presumed to belong to a currently-running
|
|
159
|
-
* operation and is not counted as stale. Best-effort: any unexpected error
|
|
160
|
-
* (e.g. a permissions issue under `$DATA/txn`) degrades to "no signal" rather
|
|
161
|
-
* than aborting the health report.
|
|
162
|
-
*/
|
|
163
|
-
function gatherStaleTxnJournalsPhase(now) {
|
|
164
|
-
const dir = path.join(getDataDir(), "txn");
|
|
165
|
-
try {
|
|
166
|
-
const { matches, unreadableMtimes } = listTxnJournalsTolerant(() => true);
|
|
167
|
-
const nowMs = now();
|
|
168
|
-
const staleAges = [
|
|
169
|
-
...matches.map((m) => nowMs - m.mtimeMs),
|
|
170
|
-
...unreadableMtimes.map((mtimeMs) => nowMs - mtimeMs),
|
|
171
|
-
].filter((ageMs) => ageMs >= TXN_SWEEP_GRACE_MS);
|
|
172
|
-
const staleUnreadableCount = unreadableMtimes.filter((mtimeMs) => nowMs - mtimeMs >= TXN_SWEEP_GRACE_MS).length;
|
|
173
|
-
return {
|
|
174
|
-
dir,
|
|
175
|
-
count: staleAges.length,
|
|
176
|
-
oldestAgeMs: staleAges.length > 0 ? Math.max(...staleAges) : null,
|
|
177
|
-
unreadable: staleUnreadableCount,
|
|
178
|
-
};
|
|
179
|
-
}
|
|
180
|
-
catch {
|
|
181
|
-
return { dir, count: 0, oldestAgeMs: null, unreadable: 0 };
|
|
182
|
-
}
|
|
183
|
-
}
|
|
184
152
|
/**
|
|
185
153
|
* Config fields the surfaces advisory needs. Best-effort: an unloadable
|
|
186
154
|
* config leaves the field undefined and the caller falls back to a generic
|
|
@@ -512,7 +480,6 @@ export function akmHealth(options = {}) {
|
|
|
512
480
|
try {
|
|
513
481
|
const taskHistory = gatherTaskHistoryPhase(db, logsDb, since, stateDbPath, now);
|
|
514
482
|
const { tableNames, missingTables, probe } = taskHistory;
|
|
515
|
-
const staleTxnJournals = gatherStaleTxnJournalsPhase(now);
|
|
516
483
|
const { egressConfigView } = gatherEgressConfigPhase();
|
|
517
484
|
const { improveSummary } = gatherImproveSummaryPhase(db, stateDbPath, since, now);
|
|
518
485
|
advisories.push(...gatherAncillaryAdvisories(db, stateDbPath, since, improveSummary, options, egressConfigView));
|
|
@@ -535,7 +502,6 @@ export function akmHealth(options = {}) {
|
|
|
535
502
|
stuckActiveRuns: taskHistory.stuckActiveRuns,
|
|
536
503
|
stuckActiveTasks: taskHistory.stuckActiveTasks,
|
|
537
504
|
worstTaskFailRate: taskHistory.worstTaskFailRate,
|
|
538
|
-
staleTxnJournals,
|
|
539
505
|
sessionExtraction: improveSummary.sessionExtraction,
|
|
540
506
|
autoAccept: improveSummary.autoAccept,
|
|
541
507
|
engineProbes,
|
|
@@ -18,8 +18,8 @@ export function isConsolidationEligibleMemoryName(name) {
|
|
|
18
18
|
*
|
|
19
19
|
* Reads the file once per check; consolidate runs against ~10 memories per
|
|
20
20
|
* chunk so the IO cost is trivial. Returns false on any read/parse error
|
|
21
|
-
* (fail-safe: an unparseable file is treated as
|
|
22
|
-
*
|
|
21
|
+
* (fail-safe: an unreadable or unparseable file is treated as HOT — protected
|
|
22
|
+
* — because a deletion shield must not fail open; a missing file is not-hot).
|
|
23
23
|
*
|
|
24
24
|
* Defends against four observed defect classes (see
|
|
25
25
|
* `memories/akm-improve-critical-review-2026-05-20`):
|
|
@@ -29,14 +29,20 @@ export function isConsolidationEligibleMemoryName(name) {
|
|
|
29
29
|
* - Cascade deletes (LLM uses ref:X as `contradictedBy` for ref:Y then deletes both)
|
|
30
30
|
*/
|
|
31
31
|
export function isHotCapturedMemory(filePath) {
|
|
32
|
+
if (!fs.existsSync(filePath))
|
|
33
|
+
return false;
|
|
32
34
|
try {
|
|
33
|
-
if (!fs.existsSync(filePath))
|
|
34
|
-
return false;
|
|
35
35
|
const content = fs.readFileSync(filePath, "utf8");
|
|
36
36
|
const parsed = parseFrontmatter(content);
|
|
37
37
|
return hasHotCaptureMode(parsed.data);
|
|
38
38
|
}
|
|
39
39
|
catch {
|
|
40
|
-
|
|
40
|
+
// Fail CLOSED. This predicate is a deletion shield: "hot" memories are
|
|
41
|
+
// protected from consolidate's merge/delete. Returning false on a read or
|
|
42
|
+
// parse failure marked exactly the memories we could not inspect as fair
|
|
43
|
+
// game — the one direction a protection check must never fail. A missing
|
|
44
|
+
// file stays false (nothing to protect); an unreadable one is protected
|
|
45
|
+
// until someone can actually read it.
|
|
46
|
+
return true;
|
|
41
47
|
}
|
|
42
48
|
}
|
|
@@ -632,8 +632,40 @@ function unavailableExtractionResult(args) {
|
|
|
632
632
|
...args.notices,
|
|
633
633
|
};
|
|
634
634
|
}
|
|
635
|
+
// #561 — ADDITIVE session indexing. Generate + write the session asset
|
|
636
|
+
// (`sessions/<harness>/<id>.md`). FAIL-OPEN: any failure only returns a
|
|
637
|
+
// warning; it NEVER changes the proposal/skip outcome of extract. Returns the
|
|
638
|
+
// frontmatter fields to merge into the per-session result for state-db
|
|
639
|
+
// correlation. When disabled this makes NO LLM call and writes NOTHING.
|
|
640
|
+
async function maybeWriteSessionAsset(runCtx, session) {
|
|
641
|
+
const { stashDir, lease, sessionIndexing, dryRun } = runCtx;
|
|
642
|
+
const { data } = session.gate;
|
|
643
|
+
if (!sessionIndexing.enabled || dryRun)
|
|
644
|
+
return {};
|
|
645
|
+
if (!sessionMeetsDurationGate(data, sessionIndexing.minDurationMinutes))
|
|
646
|
+
return {};
|
|
647
|
+
try {
|
|
648
|
+
const result = await writeSessionAsset(data, stashDir, (summaryData) => sessionIndexing.generate(summaryData, lease));
|
|
649
|
+
if (result.written) {
|
|
650
|
+
// Write-path indexing (itself fail-open): standalone `akm extract`
|
|
651
|
+
// (session-end hook) has no post-loop reindex to pick this file up.
|
|
652
|
+
if (result.filePath)
|
|
653
|
+
await indexWrittenAssets(stashDir, [result.filePath]);
|
|
654
|
+
return {
|
|
655
|
+
...(result.ref ? { sessionAssetRef: result.ref } : {}),
|
|
656
|
+
...(result.logPath ? { sessionLogPath: result.logPath } : {}),
|
|
657
|
+
};
|
|
658
|
+
}
|
|
659
|
+
}
|
|
660
|
+
catch (err) {
|
|
661
|
+
if (err instanceof ConfigError)
|
|
662
|
+
throw err;
|
|
663
|
+
return { warning: `session asset write failed: ${err instanceof Error ? err.message : String(err)}` };
|
|
664
|
+
}
|
|
665
|
+
return {};
|
|
666
|
+
}
|
|
635
667
|
async function processSession(runCtx, session) {
|
|
636
|
-
const { harness, stashDir, config, llmRunner, lease, onNotices, getNotices, chat, ctx, eventsCtx, sourceRun, dryRun, timeoutMs,
|
|
668
|
+
const { harness, stashDir, config, llmRunner, lease, onNotices, getNotices, chat, ctx, eventsCtx, sourceRun, dryRun, timeoutMs, signal, standardsContext, } = runCtx;
|
|
637
669
|
const { sessionRef, gate } = session;
|
|
638
670
|
const warnings = [];
|
|
639
671
|
const { data, filtered, contentHash } = gate;
|
|
@@ -645,36 +677,6 @@ async function processSession(runCtx, session) {
|
|
|
645
677
|
inlineRefs: data.inlineRefs,
|
|
646
678
|
...(standardsContext.trim() ? { standardsContext } : {}),
|
|
647
679
|
});
|
|
648
|
-
// #561 — ADDITIVE session indexing. Generate + write the session asset
|
|
649
|
-
// (`sessions/<harness>/<id>.md`). FAIL-OPEN: any failure only records a
|
|
650
|
-
// warning; it NEVER changes the proposal/skip outcome of extract. Returns the
|
|
651
|
-
// frontmatter fields to merge into the per-session result for state-db
|
|
652
|
-
// correlation. When disabled this closure makes NO LLM call and writes NOTHING.
|
|
653
|
-
const maybeWriteSessionAsset = async () => {
|
|
654
|
-
if (!sessionIndexing.enabled || dryRun)
|
|
655
|
-
return {};
|
|
656
|
-
if (!sessionMeetsDurationGate(data, sessionIndexing.minDurationMinutes))
|
|
657
|
-
return {};
|
|
658
|
-
try {
|
|
659
|
-
const result = await writeSessionAsset(data, stashDir, (summaryData) => sessionIndexing.generate(summaryData, lease));
|
|
660
|
-
if (result.written) {
|
|
661
|
-
// Write-path indexing (itself fail-open): standalone `akm extract`
|
|
662
|
-
// (session-end hook) has no post-loop reindex to pick this file up.
|
|
663
|
-
if (result.filePath)
|
|
664
|
-
await indexWrittenAssets(stashDir, [result.filePath]);
|
|
665
|
-
return {
|
|
666
|
-
...(result.ref ? { sessionAssetRef: result.ref } : {}),
|
|
667
|
-
...(result.logPath ? { sessionLogPath: result.logPath } : {}),
|
|
668
|
-
};
|
|
669
|
-
}
|
|
670
|
-
}
|
|
671
|
-
catch (err) {
|
|
672
|
-
if (err instanceof ConfigError)
|
|
673
|
-
throw err;
|
|
674
|
-
warnings.push(`session asset write failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
675
|
-
}
|
|
676
|
-
return {};
|
|
677
|
-
};
|
|
678
680
|
const extraction = await runSessionExtractionLlmCall({
|
|
679
681
|
config,
|
|
680
682
|
llmRunner,
|
|
@@ -708,7 +710,9 @@ async function processSession(runCtx, session) {
|
|
|
708
710
|
const { payload } = extraction;
|
|
709
711
|
const proposalIds = [];
|
|
710
712
|
// Provenance refs are added only after the cited session asset exists.
|
|
711
|
-
const sessionAsset = await maybeWriteSessionAsset();
|
|
713
|
+
const sessionAsset = await maybeWriteSessionAsset(runCtx, session);
|
|
714
|
+
if (sessionAsset.warning)
|
|
715
|
+
warnings.push(sessionAsset.warning);
|
|
712
716
|
if (payload.candidates.length === 0) {
|
|
713
717
|
appendEvent({
|
|
714
718
|
eventType: "extract_invoked",
|
|
@@ -114,7 +114,7 @@ export const improveCommand = defineCommand({
|
|
|
114
114
|
},
|
|
115
115
|
strategy: {
|
|
116
116
|
type: "string",
|
|
117
|
-
description: "Named improve strategy from improve.strategies or built-in strategies (catchup, consolidate, default,
|
|
117
|
+
description: "Named improve strategy from improve.strategies or built-in strategies (catchup, consolidate, default, graph-refresh, proactive-maintenance, quick, reflect-distill, thorough). Controls which sub-processes run and which asset types are processed.",
|
|
118
118
|
},
|
|
119
119
|
sync: {
|
|
120
120
|
type: "boolean",
|
|
@@ -4,9 +4,7 @@
|
|
|
4
4
|
import catchup from "../../assets/improve-strategies/catchup.json" with { type: "json" };
|
|
5
5
|
import consolidate from "../../assets/improve-strategies/consolidate.json" with { type: "json" };
|
|
6
6
|
import defaultStrategy from "../../assets/improve-strategies/default.json" with { type: "json" };
|
|
7
|
-
import frequent from "../../assets/improve-strategies/frequent.json" with { type: "json" };
|
|
8
7
|
import graphRefresh from "../../assets/improve-strategies/graph-refresh.json" with { type: "json" };
|
|
9
|
-
import memoryFocus from "../../assets/improve-strategies/memory-focus.json" with { type: "json" };
|
|
10
8
|
import proactiveMaintenance from "../../assets/improve-strategies/proactive-maintenance.json" with { type: "json" };
|
|
11
9
|
import quick from "../../assets/improve-strategies/quick.json" with { type: "json" };
|
|
12
10
|
import reflectDistill from "../../assets/improve-strategies/reflect-distill.json" with { type: "json" };
|
|
@@ -45,9 +43,7 @@ const BUILTIN_STRATEGIES = {
|
|
|
45
43
|
default: defaultStrategy,
|
|
46
44
|
quick,
|
|
47
45
|
thorough,
|
|
48
|
-
"memory-focus": memoryFocus,
|
|
49
46
|
"graph-refresh": graphRefresh,
|
|
50
|
-
frequent,
|
|
51
47
|
consolidate,
|
|
52
48
|
catchup,
|
|
53
49
|
"reflect-distill": reflectDistill,
|
|
@@ -52,10 +52,17 @@ function readEdgeList(value) {
|
|
|
52
52
|
* Write `contradictedBy` and `beliefState: contradicted` edges to a memory
|
|
53
53
|
* file's frontmatter (C-3 / #382).
|
|
54
54
|
*
|
|
55
|
-
*
|
|
56
|
-
*
|
|
57
|
-
*
|
|
58
|
-
*
|
|
55
|
+
* The shared primitive for APPENDING one contradiction edge. Used by
|
|
56
|
+
* `memory-contradiction-detect.ts`'s automated contradiction pass.
|
|
57
|
+
*
|
|
58
|
+
* NOT used by `persistBeliefStateTransition` in `memory-improve.ts`, and that
|
|
59
|
+
* is deliberate (#885): the SCC resolver is a state-TRANSITION writer, not an
|
|
60
|
+
* edge appender. It replaces `contradictedBy` wholesale from recomputed
|
|
61
|
+
* `currentBeliefRefs`, deletes the key when a memory transitions away from
|
|
62
|
+
* `contradicted`, and moves a memory to any target state including back to
|
|
63
|
+
* active. An append-only primitive that never weakens a demotion cannot
|
|
64
|
+
* express those, and routing it through this would break the resolver's
|
|
65
|
+
* ability to clear an edge.
|
|
59
66
|
*
|
|
60
67
|
* Idempotent: if the `contradictedByRef` is already in `contradictedBy` AND
|
|
61
68
|
* the file already carries the demotion state, the file is not rewritten. The
|
|
@@ -72,9 +79,12 @@ function readEdgeList(value) {
|
|
|
72
79
|
*
|
|
73
80
|
* @param filePath - Absolute path to the memory markdown file.
|
|
74
81
|
* @param contradictedByRef - The ref that contradicts this memory.
|
|
82
|
+
* @returns `true` when the file was rewritten, `false` when the edge and the
|
|
83
|
+
* demotion were already present (the idempotent no-op). Callers count
|
|
84
|
+
* edges written from this.
|
|
75
85
|
*/
|
|
76
86
|
export function writeContradictEdge(filePath, contradictedByRef) {
|
|
77
|
-
mutateFrontmatter(filePath, (parsed) => {
|
|
87
|
+
return mutateFrontmatter(filePath, (parsed) => {
|
|
78
88
|
const existing = readEdgeList(parsed.data.contradictedBy);
|
|
79
89
|
const currentState = parsed.data.beliefState;
|
|
80
90
|
const nextState = currentState === "archived" ? currentState : "contradicted";
|
|
@@ -17,8 +17,19 @@
|
|
|
17
17
|
* 2. For each family, enumerate candidate pairs.
|
|
18
18
|
* 3. For each pair, call the LLM to judge whether the two memories are in
|
|
19
19
|
* direct factual conflict.
|
|
20
|
-
* 4. For confirmed contradictions,
|
|
21
|
-
*
|
|
20
|
+
* 4. For confirmed contradictions, append a `contradictedBy` edge to the
|
|
21
|
+
* losing memory's frontmatter via `writeContradictEdge`
|
|
22
|
+
* (`./memory-belief.ts`).
|
|
23
|
+
*
|
|
24
|
+
* That last step used to call a private near-copy of `writeContradictEdge`
|
|
25
|
+
* living in this file. The copy had drifted (#885): it read `contradictedBy`
|
|
26
|
+
* with `Array.isArray` only, so a SCALAR edge — live data the indexer accepts
|
|
27
|
+
* and lint never flags — read as "no edges" and was overwritten out of
|
|
28
|
+
* existence; and it set `beliefState: "contradicted"` unconditionally,
|
|
29
|
+
* promoting an `archived` memory back up (archived ranks BELOW contradicted).
|
|
30
|
+
* Both behaviors had tests, but the tests exercised the shared primitive,
|
|
31
|
+
* which nothing called — so they guarded dead code while the live path
|
|
32
|
+
* carried the bugs.
|
|
22
33
|
*
|
|
23
34
|
* # LLM Feature Gate
|
|
24
35
|
*
|
|
@@ -36,12 +47,13 @@
|
|
|
36
47
|
import fs from "node:fs";
|
|
37
48
|
import path from "node:path";
|
|
38
49
|
import contradictionJudgeTemplate from "../../../assets/prompts/contradiction-judge.md" with { type: "text" };
|
|
39
|
-
import {
|
|
50
|
+
import { parseFrontmatter } from "../../../core/asset/frontmatter.js";
|
|
40
51
|
import { parseEmbeddedJsonResponse } from "../../../core/parse.js";
|
|
41
52
|
import { disposeLoweredExecutionDispatchLease } from "../../../integrations/agent/execution-lowering.js";
|
|
42
53
|
import { callStructured, preflightStructuredLlmRunner } from "../../../llm/structured-call.js";
|
|
43
54
|
import { resolveImproveLlmExecution } from "../execution.js";
|
|
44
55
|
import { isDerivedMemory, memoryIdentityRef, resolveParentRef } from "./derived-ref.js";
|
|
56
|
+
import { writeContradictEdge } from "./memory-belief.js";
|
|
45
57
|
// ── Constants ────────────────────────────────────────────────────────────────
|
|
46
58
|
/**
|
|
47
59
|
* Minimum confidence required to write a contradiction edge. Below this
|
|
@@ -89,27 +101,6 @@ function toMemoryRef(memoriesDir, filePath) {
|
|
|
89
101
|
return memoryIdentityRef(name);
|
|
90
102
|
}
|
|
91
103
|
// ── Edge writing ─────────────────────────────────────────────────────────────
|
|
92
|
-
/**
|
|
93
|
-
* Write a `contradictedBy` edge to the losing memory's frontmatter file.
|
|
94
|
-
* Preserves all existing frontmatter keys; only adds/updates `contradictedBy`
|
|
95
|
-
* and `beliefState: contradicted`.
|
|
96
|
-
*/
|
|
97
|
-
/** Returns true if the edge was newly written, false if it already existed. */
|
|
98
|
-
function writeContradictedByEdge(filePath, contradictedByRef) {
|
|
99
|
-
return mutateFrontmatter(filePath, (parsed) => {
|
|
100
|
-
const existing = Array.isArray(parsed.data.contradictedBy)
|
|
101
|
-
? parsed.data.contradictedBy
|
|
102
|
-
: [];
|
|
103
|
-
if (existing.includes(contradictedByRef))
|
|
104
|
-
return null; // Edge already written.
|
|
105
|
-
const updatedContradictedBy = [...new Set([...existing, contradictedByRef])].sort();
|
|
106
|
-
return {
|
|
107
|
-
...parsed.data,
|
|
108
|
-
contradictedBy: updatedContradictedBy,
|
|
109
|
-
beliefState: "contradicted",
|
|
110
|
-
};
|
|
111
|
-
});
|
|
112
|
-
}
|
|
113
104
|
/**
|
|
114
105
|
* Deterministically pick, for a confirmed-contradiction pair, the LOSER memory
|
|
115
106
|
* that receives the single directed `contradictedBy` edge (SCC-resolved to
|
|
@@ -284,7 +275,7 @@ export async function detectAndWriteContradictions(stashDir, config, chat, strat
|
|
|
284
275
|
// a 2-cycle that the SCC resolver refreshes back to active, erasing the
|
|
285
276
|
// contradiction every run (see pickContradictionLoser).
|
|
286
277
|
try {
|
|
287
|
-
const wrote =
|
|
278
|
+
const wrote = writeContradictEdge(loser.filePath, winnerRef);
|
|
288
279
|
result.edgesWritten += wrote ? 1 : 0;
|
|
289
280
|
}
|
|
290
281
|
catch (err) {
|
|
@@ -5,10 +5,13 @@ import fs from "node:fs";
|
|
|
5
5
|
import path from "node:path";
|
|
6
6
|
import { assembleAsset } from "../../../core/asset/asset-serialize.js";
|
|
7
7
|
import { mutateFrontmatter, parseFrontmatter } from "../../../core/asset/frontmatter.js";
|
|
8
|
+
import { MEMORY_ARCHIVE_REL } from "../../../core/asset/memory-archive.js";
|
|
8
9
|
import { conceptIdFromTypeName } from "../../../core/asset/resolve-ref.js";
|
|
9
10
|
import { asNonEmptyString, groupBy, stringArray } from "../../../core/common.js";
|
|
10
11
|
import { DERIVED_SUFFIX } from "../../../core/recognition-util.js";
|
|
12
|
+
import { warn } from "../../../core/warn.js";
|
|
11
13
|
import { recordWrittenPath } from "../../../core/write-provenance.js";
|
|
14
|
+
import { walkMarkdownFiles } from "../../../indexer/walk/walker.js";
|
|
12
15
|
import { isDerivedMemory, memoryIdentityRef, parseMemoryName, resolveParentRef } from "./derived-ref.js";
|
|
13
16
|
export function analyzeMemoryCleanup(stashDir, options = {}) {
|
|
14
17
|
const records = collectDerivedMemories(stashDir, options.parentRef);
|
|
@@ -553,7 +556,7 @@ function priorBeliefStateForArchive(candidate) {
|
|
|
553
556
|
}
|
|
554
557
|
function createArchiveDir(stashDir, ref, archivedAt) {
|
|
555
558
|
const baseName = `${archivedAt.replace(/[:.]/g, "-")}-${sanitizeRef(ref)}`;
|
|
556
|
-
const root = path.join(stashDir,
|
|
559
|
+
const root = path.join(stashDir, MEMORY_ARCHIVE_REL);
|
|
557
560
|
fs.mkdirSync(root, { recursive: true });
|
|
558
561
|
let attempt = 0;
|
|
559
562
|
while (true) {
|
|
@@ -573,7 +576,11 @@ function collectDerivedMemories(stashDir, parentRefFilter) {
|
|
|
573
576
|
if (!fs.existsSync(memoriesDir))
|
|
574
577
|
return [];
|
|
575
578
|
const records = [];
|
|
576
|
-
|
|
579
|
+
const walked = walkMarkdownFiles(memoriesDir);
|
|
580
|
+
if (!walked.complete) {
|
|
581
|
+
warn(`memory improve: directory scan under ${memoriesDir} is incomplete — some derived memories may be missing`);
|
|
582
|
+
}
|
|
583
|
+
for (const filePath of walked.files) {
|
|
577
584
|
const name = toMemoryName(memoriesDir, filePath);
|
|
578
585
|
if (!name)
|
|
579
586
|
continue;
|
|
@@ -777,24 +784,6 @@ function firstNonEmpty(values) {
|
|
|
777
784
|
}
|
|
778
785
|
return undefined;
|
|
779
786
|
}
|
|
780
|
-
function* walkMarkdownFiles(root) {
|
|
781
|
-
let entries;
|
|
782
|
-
try {
|
|
783
|
-
entries = fs.readdirSync(root, { withFileTypes: true });
|
|
784
|
-
}
|
|
785
|
-
catch {
|
|
786
|
-
return;
|
|
787
|
-
}
|
|
788
|
-
for (const entry of entries) {
|
|
789
|
-
const full = path.join(root, entry.name);
|
|
790
|
-
if (entry.isDirectory()) {
|
|
791
|
-
yield* walkMarkdownFiles(full);
|
|
792
|
-
}
|
|
793
|
-
else if (entry.isFile() && entry.name.toLowerCase().endsWith(".md")) {
|
|
794
|
-
yield full;
|
|
795
|
-
}
|
|
796
|
-
}
|
|
797
|
-
}
|
|
798
787
|
function toMemoryName(memoriesDir, filePath) {
|
|
799
788
|
const rel = path.relative(memoriesDir, filePath);
|
|
800
789
|
if (!rel || rel.startsWith(".."))
|
|
@@ -760,7 +760,7 @@ async function runPreparationPrelude(args) {
|
|
|
760
760
|
};
|
|
761
761
|
}
|
|
762
762
|
export async function runImprovePreparationStage(args) {
|
|
763
|
-
const { scope, options,
|
|
763
|
+
const { scope, options, primaryStashDir, eventsCtx, initialCleanupWarnings, improveProfile, resolvedPlan, planOnly = options.dryRun === true, } = args;
|
|
764
764
|
const actions = [];
|
|
765
765
|
const cleanupWarnings = initialCleanupWarnings ? [...initialCleanupWarnings] : [];
|
|
766
766
|
const { memoryIndexHealth, consolidationPass, extractPlan, extractResults, appliedCleanup, postCleanupRefs, cleanupGate, validationFailures, validationFailureRefs, schemaRepairs, } = await runPreparationPrelude({ ...args, planOnly, actions, cleanupWarnings });
|
|
@@ -789,6 +789,14 @@ export async function runImprovePreparationStage(args) {
|
|
|
789
789
|
snapshot,
|
|
790
790
|
persist: !planOnly,
|
|
791
791
|
});
|
|
792
|
+
const eligibilitySourceByRef = stampEligibilitySource({
|
|
793
|
+
scope,
|
|
794
|
+
processableRefs: gathered.processableRefs,
|
|
795
|
+
mergedRefs: gathered.mergedRefs,
|
|
796
|
+
signalFiltered: gathered.signalFiltered,
|
|
797
|
+
proactiveRefs: gathered.proactiveRefs,
|
|
798
|
+
highSalienceRefs: gathered.highSalienceRefs,
|
|
799
|
+
});
|
|
792
800
|
// Shared admission boundary for every synthetic fallback lane. Cleanup and
|
|
793
801
|
// structural validation are exclusive selectors: no later rank/replay state
|
|
794
802
|
// may re-create a candidate they removed. Keep the exact surviving objects
|
|
@@ -800,7 +808,7 @@ export async function runImprovePreparationStage(args) {
|
|
|
800
808
|
primaryStashDir,
|
|
801
809
|
eventsCtx,
|
|
802
810
|
mergedRefs: gathered.mergedRefs,
|
|
803
|
-
eligibilitySourceByRef
|
|
811
|
+
eligibilitySourceByRef,
|
|
804
812
|
feedbackSummary: gathered.feedbackSummary,
|
|
805
813
|
retrievalCounts: gathered.retrievalCounts,
|
|
806
814
|
signalFiltered: gathered.signalFiltered,
|
|
@@ -820,7 +828,7 @@ export async function runImprovePreparationStage(args) {
|
|
|
820
828
|
eventsCtx,
|
|
821
829
|
mergedRefs: scored.mergedRefs,
|
|
822
830
|
salienceMap: scored.salienceMap,
|
|
823
|
-
eligibilitySourceByRef
|
|
831
|
+
eligibilitySourceByRef,
|
|
824
832
|
distillOnlyRefs: gathered.distillOnlyRefs,
|
|
825
833
|
validationFailureRefs,
|
|
826
834
|
summary: {
|
|
@@ -1054,7 +1062,9 @@ export function buildSnapshotManifest(args) {
|
|
|
1054
1062
|
/**
|
|
1055
1063
|
* Pass: candidate-gather — the signal-delta partition, the bulk feedback
|
|
1056
1064
|
* summary, retrieval signals, the Layer-2 proactive and Layer-3 high-salience
|
|
1057
|
-
* rescue lanes, the merged candidate set
|
|
1065
|
+
* rescue lanes, and the merged candidate set. Lane attribution stamping is a
|
|
1066
|
+
* separate pass — see `stampEligibilitySource` — run by the caller once
|
|
1067
|
+
* `mergedRefs` is known.
|
|
1058
1068
|
*/
|
|
1059
1069
|
function gatherCandidates(args) {
|
|
1060
1070
|
const { scope, options, primaryStashDir, eventsCtx, improveProfile, resolvedPlan, postCleanupRefs, persist } = args;
|
|
@@ -1152,23 +1162,44 @@ function gatherCandidates(args) {
|
|
|
1152
1162
|
// that want feedback-only runs.
|
|
1153
1163
|
const signalAndRetrievalRefs = dedupeRefs([...signalFiltered, ...proactiveRefs, ...highSalienceRefs]);
|
|
1154
1164
|
const mergedRefs = scope.mode === "ref" ? processableRefs : options.requireFeedbackSignal ? signalFiltered : signalAndRetrievalRefs;
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1165
|
+
return {
|
|
1166
|
+
distillCooledRefs,
|
|
1167
|
+
preCooldownCount,
|
|
1168
|
+
distillOnlyRefs,
|
|
1169
|
+
feedbackSummary,
|
|
1170
|
+
signalFiltered,
|
|
1171
|
+
signalBearingSet,
|
|
1172
|
+
retrievalCounts,
|
|
1173
|
+
proactiveRefs,
|
|
1174
|
+
proactiveMaintenanceSummary,
|
|
1175
|
+
proactivePlan: proactive.proactivePlan,
|
|
1176
|
+
highSalienceRefs,
|
|
1177
|
+
signalAndRetrievalRefs,
|
|
1178
|
+
mergedRefs,
|
|
1179
|
+
processableRefs,
|
|
1180
|
+
};
|
|
1181
|
+
}
|
|
1182
|
+
/**
|
|
1183
|
+
* Attribution tagging: stamp each ref with the eligibility lane that selected
|
|
1184
|
+
* it. Every reflect/distill proposal must record WHICH lane chose its source
|
|
1185
|
+
* asset so downstream accept/reject/revert/retrieval outcomes can be sliced by
|
|
1186
|
+
* lane (does the PROACTIVE lane produce value vs the reactive lanes?). We
|
|
1187
|
+
* build the lane map here — the one place all three lanes are known — and
|
|
1188
|
+
* stamp it onto each ImproveEligibleRef object. Because the ref objects are
|
|
1189
|
+
* shared by reference across buckets, the stamp travels with the ref through
|
|
1190
|
+
* the sort, disk-check, and loop stages down to the reflect/distill event
|
|
1191
|
+
* emit sites and createProposal calls. See EligibilitySource for the lane
|
|
1192
|
+
* vocabulary.
|
|
1193
|
+
*
|
|
1194
|
+
* Precedence (prefer the most specific reactive signal):
|
|
1195
|
+
* scope > signal-delta > proactive > high-salience
|
|
1196
|
+
* A ref with real feedback is attributed to feedback even if it was also due
|
|
1197
|
+
* for proactive maintenance or had high encoding salience. We apply lanes
|
|
1198
|
+
* weakest-first so the strongest overwrites; the explicit --scope <ref> bypass
|
|
1199
|
+
* wins outright (user intent).
|
|
1200
|
+
*/
|
|
1201
|
+
function stampEligibilitySource(args) {
|
|
1202
|
+
const { scope, processableRefs, mergedRefs, signalFiltered, proactiveRefs, highSalienceRefs } = args;
|
|
1172
1203
|
const eligibilitySourceByRef = new Map();
|
|
1173
1204
|
for (const r of highSalienceRefs)
|
|
1174
1205
|
eligibilitySourceByRef.set(r.ref, "high-salience");
|
|
@@ -1188,22 +1219,7 @@ function gatherCandidates(args) {
|
|
|
1188
1219
|
// mergedRefs is always a subset of the four lanes above).
|
|
1189
1220
|
r.eligibilitySource = eligibilitySourceByRef.get(r.ref) ?? "unknown";
|
|
1190
1221
|
}
|
|
1191
|
-
return
|
|
1192
|
-
distillCooledRefs,
|
|
1193
|
-
preCooldownCount,
|
|
1194
|
-
distillOnlyRefs,
|
|
1195
|
-
feedbackSummary,
|
|
1196
|
-
signalFiltered,
|
|
1197
|
-
signalBearingSet,
|
|
1198
|
-
retrievalCounts,
|
|
1199
|
-
proactiveRefs,
|
|
1200
|
-
proactiveMaintenanceSummary,
|
|
1201
|
-
proactivePlan: proactive.proactivePlan,
|
|
1202
|
-
highSalienceRefs,
|
|
1203
|
-
signalAndRetrievalRefs,
|
|
1204
|
-
mergedRefs,
|
|
1205
|
-
eligibilitySourceByRef,
|
|
1206
|
-
};
|
|
1222
|
+
return eligibilitySourceByRef;
|
|
1207
1223
|
}
|
|
1208
1224
|
/**
|
|
1209
1225
|
* The signal-delta partition of postCleanupRefs into the four buckets (pass:
|
|
@@ -133,27 +133,23 @@ export const REFLECT_ALLOWED_TYPES = new Set([
|
|
|
133
133
|
const PROTECTED_FRONTMATTER_FIELDS = new Set(["name", "ref", "id", "slug", "type"]);
|
|
134
134
|
/**
|
|
135
135
|
* Read the last 1–3 archived rejected proposals for a given ref from the
|
|
136
|
-
* proposal store.
|
|
137
|
-
* or the ref is undefined
|
|
138
|
-
*
|
|
139
|
-
*
|
|
136
|
+
* proposal store. Returns `[]` when the proposals store is absent (not yet
|
|
137
|
+
* created) or the ref is undefined — `listProposalsReadOnly` already handles
|
|
138
|
+
* that case; a genuine read failure propagates instead of being swallowed,
|
|
139
|
+
* since silently dropping this Reflexion-style context risks re-proposing
|
|
140
|
+
* content that was already rejected (arXiv:2303.11366).
|
|
140
141
|
*/
|
|
141
142
|
function readRejectedProposals(stash, ref, proposalsCtx) {
|
|
142
143
|
if (!ref)
|
|
143
144
|
return [];
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
}));
|
|
153
|
-
}
|
|
154
|
-
catch {
|
|
155
|
-
return [];
|
|
156
|
-
}
|
|
145
|
+
return listProposalsReadOnly(stash, { ref, status: "rejected", includeArchive: true }, proposalsCtx)
|
|
146
|
+
.sort((a, b) => new Date(b.updatedAt ?? 0).getTime() - new Date(a.updatedAt ?? 0).getTime())
|
|
147
|
+
.slice(0, MAX_REJECTED_PROPOSALS)
|
|
148
|
+
.map((p) => ({
|
|
149
|
+
ref: p.ref,
|
|
150
|
+
reason: p.review?.reason ?? "no reason given",
|
|
151
|
+
contentPreview: proposalContent(p).slice(0, 500),
|
|
152
|
+
}));
|
|
157
153
|
}
|
|
158
154
|
/**
|
|
159
155
|
* Synthesize a tmp draft-file path for the agent/sdk file-write contract.
|
|
@@ -790,7 +786,7 @@ function parseDirectReflectOutput(raw, mode, targetRef) {
|
|
|
790
786
|
export async function runReflectViaLlm(opts) {
|
|
791
787
|
const start = Date.now();
|
|
792
788
|
let repairAttempts = 0;
|
|
793
|
-
const
|
|
789
|
+
const _connection = opts.runner.connection;
|
|
794
790
|
const messages = [{ role: "user", content: opts.prompt ?? "" }];
|
|
795
791
|
const configuredTimeout = Object.hasOwn(opts, "timeoutMs")
|
|
796
792
|
? (opts.timeoutMs ?? null)
|