akm-cli 0.9.0-beta.5 → 0.9.0-beta.51
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 +711 -0
- package/README.md +12 -4
- package/dist/akm +38 -0
- package/dist/akm-migrate-storage +38 -0
- package/dist/assets/profiles/default.json +9 -4
- package/dist/assets/profiles/frequent.json +1 -1
- package/dist/assets/profiles/memory-focus.json +1 -1
- package/dist/assets/profiles/quick.json +1 -1
- package/dist/assets/profiles/synthesize.json +15 -0
- package/dist/assets/profiles/thorough.json +1 -1
- package/dist/assets/prompts/consolidate-system.md +23 -0
- package/dist/assets/prompts/contradiction-judge.md +33 -0
- package/dist/assets/prompts/distill-knowledge-system.md +22 -0
- package/dist/assets/prompts/distill-lesson-system.md +36 -0
- package/dist/assets/prompts/extract-session.md +6 -2
- package/dist/assets/prompts/graph-extract-system.md +1 -0
- package/dist/assets/prompts/graph-extract-user-prompt.md +1 -1
- package/dist/assets/prompts/memory-infer-system.md +1 -0
- package/dist/assets/prompts/memory-infer-user.md +5 -0
- package/dist/assets/prompts/metadata-enhance-system.md +1 -0
- package/dist/assets/prompts/procedural-system.md +44 -0
- package/dist/assets/prompts/recombine-system.md +40 -0
- package/dist/assets/prompts/staleness-detect-system.md +6 -0
- package/dist/assets/prompts/validate-summary-judge.md +1 -0
- package/dist/assets/stash-skeleton/facts/conventions/assets/agent.md +38 -0
- package/dist/assets/stash-skeleton/facts/conventions/assets/command.md +38 -0
- package/dist/assets/stash-skeleton/facts/conventions/assets/fact.md +39 -0
- package/dist/assets/stash-skeleton/facts/conventions/assets/knowledge.md +40 -0
- package/dist/assets/stash-skeleton/facts/conventions/assets/lesson.md +43 -0
- package/dist/assets/stash-skeleton/facts/conventions/assets/memory.md +38 -0
- package/dist/assets/stash-skeleton/facts/conventions/assets/script.md +43 -0
- package/dist/assets/stash-skeleton/facts/conventions/assets/skill.md +40 -0
- package/dist/assets/stash-skeleton/facts/conventions/assets/workflow.md +43 -0
- package/dist/assets/templates/html/health.html +281 -111
- package/dist/assets/wiki/ingest-workflow-template.md +38 -10
- package/dist/cli/parse-args.js +46 -1
- package/dist/cli/shared.js +28 -0
- package/dist/cli.js +27 -11
- package/dist/commands/agent/agent-dispatch.js +2 -2
- package/dist/commands/agent/agent-support.js +0 -7
- package/dist/commands/agent/contribute-cli.js +17 -4
- package/dist/commands/config-cli.js +18 -2
- package/dist/commands/env/child-env.js +47 -0
- package/dist/commands/env/env-cli.js +33 -26
- package/dist/commands/env/secret-cli.js +36 -22
- package/dist/commands/feedback-cli.js +15 -6
- package/dist/commands/graph/graph-cli.js +5 -13
- package/dist/commands/graph/graph.js +76 -72
- package/dist/commands/health/checks.js +49 -1
- package/dist/commands/health/html-report.js +422 -80
- package/dist/commands/health.js +386 -9
- package/dist/commands/improve/calibration.js +161 -0
- package/dist/commands/improve/consolidate/chunking.js +141 -0
- package/dist/commands/improve/consolidate/eligibility.js +81 -0
- package/dist/commands/improve/consolidate/merge.js +145 -0
- package/dist/commands/improve/consolidate/sanitize.js +231 -0
- package/dist/commands/{lint.js → improve/consolidate/types.js} +1 -1
- package/dist/commands/improve/consolidate.js +635 -660
- package/dist/commands/improve/dedup.js +482 -0
- package/dist/commands/improve/distill.js +159 -69
- package/dist/commands/improve/eligibility.js +434 -0
- package/dist/commands/improve/encoding-salience.js +205 -0
- package/dist/commands/improve/extract-cli.js +124 -2
- package/dist/commands/improve/extract-prompt.js +39 -2
- package/dist/commands/improve/extract-watch.js +140 -0
- package/dist/commands/improve/extract.js +389 -40
- package/dist/commands/improve/feedback-valence.js +54 -0
- package/dist/commands/improve/homeostatic.js +467 -0
- package/dist/commands/improve/improve-auto-accept.js +138 -7
- package/dist/commands/improve/improve-cli.js +36 -61
- package/dist/commands/improve/improve-profiles.js +14 -0
- package/dist/commands/improve/improve-result-file.js +14 -25
- package/dist/commands/improve/improve-session.js +58 -0
- package/dist/commands/improve/improve.js +485 -2498
- package/dist/commands/improve/locks.js +154 -0
- package/dist/commands/improve/loop-stages.js +1083 -0
- package/dist/commands/improve/memory/memory-contradiction-detect.js +23 -28
- package/dist/commands/improve/outcome-loop.js +256 -0
- package/dist/commands/improve/preparation.js +1966 -0
- package/dist/commands/improve/proactive-maintenance.js +115 -0
- package/dist/commands/improve/procedural.js +418 -0
- package/dist/commands/improve/recombine.js +850 -0
- package/dist/commands/improve/reflect-noise.js +0 -0
- package/dist/commands/improve/reflect.js +183 -40
- package/dist/commands/improve/salience.js +438 -0
- package/dist/commands/improve/triage.js +93 -0
- package/dist/commands/lint/agent-linter.js +19 -24
- package/dist/commands/lint/base-linter.js +173 -60
- package/dist/commands/lint/command-linter.js +19 -24
- package/dist/commands/lint/env-key-rules.js +38 -1
- package/dist/commands/lint/fact-linter.js +39 -0
- package/dist/commands/lint/index.js +31 -13
- package/dist/commands/lint/memory-linter.js +1 -1
- package/dist/commands/lint/registry.js +7 -2
- package/dist/commands/lint/task-linter.js +3 -3
- package/dist/commands/lint/workflow-linter.js +26 -1
- package/dist/commands/proposal/drain-policies.js +5 -0
- package/dist/commands/proposal/drain.js +43 -50
- package/dist/commands/proposal/proposal-cli.js +21 -31
- package/dist/commands/proposal/proposal.js +5 -0
- package/dist/commands/proposal/propose.js +7 -2
- package/dist/commands/proposal/validators/proposal-quality-validators.js +9 -8
- package/dist/commands/proposal/validators/proposals.js +189 -63
- package/dist/commands/read/curate.js +414 -94
- package/dist/commands/read/knowledge.js +6 -3
- package/dist/commands/read/search-cli.js +9 -4
- package/dist/commands/read/search.js +10 -6
- package/dist/commands/read/show.js +86 -7
- package/dist/commands/sources/init.js +49 -17
- package/dist/commands/sources/installed-stashes.js +11 -3
- package/dist/commands/sources/schema-repair.js +43 -45
- package/dist/commands/sources/self-update.js +2 -2
- package/dist/commands/sources/source-add.js +7 -3
- package/dist/commands/sources/stash-cli.js +28 -40
- package/dist/commands/sources/stash-skeleton.js +23 -8
- package/dist/commands/tasks/tasks-cli.js +19 -27
- package/dist/commands/tasks/tasks.js +39 -11
- package/dist/commands/wiki-cli.js +21 -35
- package/dist/core/asset/asset-registry.js +3 -1
- package/dist/core/asset/asset-spec.js +18 -2
- package/dist/core/asset/frontmatter.js +166 -167
- package/dist/core/asset/markdown.js +8 -0
- package/dist/core/authoring-rules.js +92 -0
- package/dist/core/common.js +0 -5
- package/dist/core/config/config-migration.js +12 -11
- package/dist/core/config/config-schema.js +340 -56
- package/dist/core/config/config-types.js +3 -3
- package/dist/core/config/config.js +28 -7
- package/dist/core/events.js +3 -7
- package/dist/core/improve-types.js +11 -8
- package/dist/core/logs-db.js +10 -66
- package/dist/core/parse.js +36 -16
- package/dist/core/paths.js +3 -0
- package/dist/core/standards/resolve-standards-context.js +87 -0
- package/dist/core/standards/resolve-stash-standards.js +99 -0
- package/dist/core/standards/resolve-type-conventions.js +66 -0
- package/dist/core/state/migrations.js +714 -0
- package/dist/core/state-db.js +525 -474
- package/dist/indexer/db/db.js +439 -247
- package/dist/indexer/db/graph-db.js +129 -86
- package/dist/indexer/ensure-index.js +152 -17
- package/dist/indexer/graph/graph-boost.js +51 -41
- package/dist/indexer/graph/graph-extraction.js +218 -4
- package/dist/indexer/index-writer-lock.js +99 -0
- package/dist/indexer/indexer.js +123 -221
- package/dist/indexer/passes/dir-staleness.js +114 -0
- package/dist/indexer/passes/memory-inference.js +13 -5
- package/dist/indexer/passes/staleness-detect.js +2 -5
- package/dist/indexer/search/db-search.js +19 -6
- package/dist/indexer/search/ranking-contributors.js +22 -0
- package/dist/indexer/search/ranking.js +4 -0
- package/dist/indexer/search/search-source.js +17 -18
- package/dist/indexer/search/semantic-status.js +4 -0
- package/dist/indexer/walk/matchers.js +9 -0
- package/dist/integrations/agent/config.js +6 -53
- package/dist/integrations/agent/index.js +2 -18
- package/dist/integrations/agent/prompts.js +75 -9
- package/dist/integrations/agent/runner-dispatch.js +59 -0
- package/dist/integrations/harnesses/claude/session-log.js +11 -1
- package/dist/integrations/harnesses/index.js +2 -3
- package/dist/integrations/harnesses/opencode/session-log.js +173 -3
- package/dist/integrations/harnesses/opencode-sdk/index.js +2 -2
- package/dist/integrations/harnesses/opencode-sdk/sdk-runner.js +0 -2
- package/dist/integrations/session-logs/index.js +16 -0
- package/dist/llm/client.js +45 -15
- package/dist/llm/embedder.js +42 -3
- package/dist/llm/embedders/deterministic.js +66 -0
- package/dist/llm/embedders/local.js +66 -2
- package/dist/llm/feature-gate.js +8 -4
- package/dist/llm/graph-extract.js +67 -44
- package/dist/llm/memory-infer-impl.js +138 -0
- package/dist/llm/memory-infer.js +1 -127
- package/dist/llm/metadata-enhance.js +44 -31
- package/dist/llm/structured-call.js +49 -0
- package/dist/migrate-storage-node.mjs +8 -0
- package/dist/output/context.js +5 -5
- package/dist/output/renderers.js +74 -2
- package/dist/output/shapes/curate.js +14 -2
- package/dist/output/shapes/passthrough.js +0 -1
- package/dist/output/text/helpers.js +16 -1
- package/dist/registry/providers/skills-sh.js +21 -147
- package/dist/registry/providers/static-index.js +15 -157
- package/dist/registry/resolve.js +22 -9
- package/dist/runtime.js +25 -1
- package/dist/scripts/migrate-storage.js +2617 -1961
- package/dist/scripts/migrations/import-fs-improve-runs-to-db.js +759 -510
- package/dist/setup/setup.js +29 -8
- package/dist/sources/include.js +6 -2
- package/dist/sources/providers/filesystem.js +0 -1
- package/dist/sources/providers/git-install.js +210 -0
- package/dist/sources/providers/git-provider.js +234 -0
- package/dist/sources/providers/git-stash.js +248 -0
- package/dist/sources/providers/git.js +10 -661
- package/dist/sources/providers/npm.js +2 -6
- package/dist/sources/providers/provider-utils.js +13 -7
- package/dist/sources/providers/sync-from-ref.js +9 -1
- package/dist/sources/providers/tar-utils.js +16 -8
- package/dist/sources/providers/website.js +9 -5
- package/dist/sources/website-ingest.js +187 -29
- package/dist/sources/wiki-fetchers/registry.js +53 -0
- package/dist/sources/wiki-fetchers/youtube.js +239 -0
- package/dist/storage/database.js +45 -10
- package/dist/storage/managed-db.js +82 -0
- package/dist/storage/repositories/registry-cache.js +92 -0
- package/dist/storage/sqlite-pragmas.js +146 -0
- package/dist/tasks/backends/cron.js +1 -1
- package/dist/tasks/backends/launchd.js +1 -1
- package/dist/tasks/backends/schtasks.js +1 -1
- package/dist/tasks/{resolveAkmBin.js → resolve-akm-bin.js} +2 -2
- package/dist/tasks/runner.js +5 -13
- package/dist/text-import-hook.mjs +0 -0
- package/dist/wiki/wiki.js +37 -0
- package/dist/workflows/db.js +3 -4
- package/dist/workflows/runtime/runs.js +1 -117
- package/dist/workflows/runtime/workflow-asset-loader.js +125 -0
- package/dist/workflows/validate-summary.js +2 -7
- package/docs/data-and-telemetry.md +3 -2
- package/docs/migration/release-notes/0.9.0.md +39 -0
- package/package.json +13 -11
- package/dist/commands/db-cli.js +0 -23
- package/dist/indexer/db/db-backup.js +0 -376
|
@@ -3,8 +3,31 @@
|
|
|
3
3
|
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
|
4
4
|
import { loadConfig } from "../../core/config/config.js";
|
|
5
5
|
import { appendEvent } from "../../core/events.js";
|
|
6
|
+
import { getPhaseThreshold, withStateDb } from "../../core/state-db.js";
|
|
6
7
|
import { info, warn } from "../../core/warn.js";
|
|
7
|
-
import { promoteProposal, recordGateDecision } from "../proposal/validators/proposals.js";
|
|
8
|
+
import { getProposal, promoteProposal, recordGateDecision } from "../proposal/validators/proposals.js";
|
|
9
|
+
async function sha256Hex(input) {
|
|
10
|
+
const data = new TextEncoder().encode(input);
|
|
11
|
+
const digest = await crypto.subtle.digest("SHA-256", data);
|
|
12
|
+
return Array.from(new Uint8Array(digest), (byte) => byte.toString(16).padStart(2, "0")).join("");
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Derive a stable, low-cardinality reason bucket from an auto-accept promotion
|
|
16
|
+
* error. `promoteProposal` throws a `validateProposal` report formatted as
|
|
17
|
+
* `[kind] message` lines; we extract the first finding kind. Non-validation
|
|
18
|
+
* throws collapse to `promote-error`.
|
|
19
|
+
*/
|
|
20
|
+
function classifyPromoteFailure(err) {
|
|
21
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
22
|
+
const finding = /\[([a-z][a-z0-9-]*)\]/i.exec(message);
|
|
23
|
+
if (finding)
|
|
24
|
+
return `validation:${finding[1]}`;
|
|
25
|
+
if (/not pending/i.test(message))
|
|
26
|
+
return "not-pending";
|
|
27
|
+
if (/unknown asset type/i.test(message))
|
|
28
|
+
return "unknown-type";
|
|
29
|
+
return "promote-error";
|
|
30
|
+
}
|
|
8
31
|
// ---------------------------------------------------------------------------
|
|
9
32
|
// Gate implementation
|
|
10
33
|
// ---------------------------------------------------------------------------
|
|
@@ -18,13 +41,32 @@ import { promoteProposal, recordGateDecision } from "../proposal/validators/prop
|
|
|
18
41
|
* @param promoteFn Injectable override for `promoteProposal` (test seam).
|
|
19
42
|
*/
|
|
20
43
|
export async function runAutoAcceptGate(candidates, cfg, promoteFn = promoteProposal) {
|
|
21
|
-
const result = {
|
|
44
|
+
const result = {
|
|
45
|
+
promoted: [],
|
|
46
|
+
skipped: [],
|
|
47
|
+
failed: [],
|
|
48
|
+
suppressed: [],
|
|
49
|
+
failedByReason: {},
|
|
50
|
+
failedBySource: {},
|
|
51
|
+
};
|
|
22
52
|
// --- Guard: gate is disabled or context is incomplete ---
|
|
23
53
|
if (cfg.dryRun || cfg.globalThreshold === undefined || !cfg.stashDir) {
|
|
24
54
|
result.skipped = candidates.map((c) => c.proposalId);
|
|
25
55
|
return result;
|
|
26
56
|
}
|
|
27
|
-
|
|
57
|
+
// WS-4: per-phase threshold from state.db overrides the global threshold.
|
|
58
|
+
// The per-phase value is populated by makeGateConfig when a stateDbPath is
|
|
59
|
+
// available; callers that don't pass it get the global threshold unchanged.
|
|
60
|
+
const resolvedThreshold = cfg.phaseThreshold ?? cfg.globalThreshold;
|
|
61
|
+
const effectiveThreshold = Math.max(resolvedThreshold, cfg.minimumThreshold ?? 0) / 100;
|
|
62
|
+
// WS-4: Exploration budget — promote at most N candidates regardless of
|
|
63
|
+
// confidence to prevent the gate converging to pure exploitation.
|
|
64
|
+
// Exploration candidates are chosen from the LOWEST-confidence eligible set
|
|
65
|
+
// (i.e. those that would be deferred) so the budget truly samples the low-
|
|
66
|
+
// confidence tail and is meaningfully distinct from normal auto-accept.
|
|
67
|
+
// Promoted exploration proposals are logged with eligibilitySource="exploration".
|
|
68
|
+
const explorationBudget = cfg.explorationBudgetCount ?? 0;
|
|
69
|
+
let explorationRemaining = explorationBudget;
|
|
28
70
|
const resolvedConfig = typeof cfg.config === "function" ? cfg.config() : cfg.config;
|
|
29
71
|
const gateLabel = `improve:${cfg.phase}`;
|
|
30
72
|
// #577: stamp the gate's verdict onto each proposal so `akm proposal show`
|
|
@@ -40,7 +82,20 @@ export async function runAutoAcceptGate(candidates, cfg, promoteFn = promoteProp
|
|
|
40
82
|
};
|
|
41
83
|
for (const candidate of candidates) {
|
|
42
84
|
const { proposalId, confidence } = candidate;
|
|
43
|
-
|
|
85
|
+
let currentProposal;
|
|
86
|
+
try {
|
|
87
|
+
currentProposal = cfg.stashDir ? getProposal(cfg.stashDir, proposalId) : undefined;
|
|
88
|
+
}
|
|
89
|
+
catch {
|
|
90
|
+
currentProposal = undefined;
|
|
91
|
+
}
|
|
92
|
+
const currentContentHash = currentProposal ? await sha256Hex(currentProposal.payload.content) : undefined;
|
|
93
|
+
// Determine if this candidate is exploration-eligible: below-threshold
|
|
94
|
+
// (would normally be deferred) but with a valid confidence score and budget
|
|
95
|
+
// remaining. No-confidence candidates are never exploration-promoted.
|
|
96
|
+
const belowThreshold = confidence === undefined || confidence < effectiveThreshold;
|
|
97
|
+
const isExploration = belowThreshold && confidence !== undefined && explorationRemaining > 0;
|
|
98
|
+
if (belowThreshold && !isExploration) {
|
|
44
99
|
stamp(proposalId, {
|
|
45
100
|
outcome: "deferred",
|
|
46
101
|
reason: confidence === undefined ? "no-confidence" : "below-threshold",
|
|
@@ -51,15 +106,32 @@ export async function runAutoAcceptGate(candidates, cfg, promoteFn = promoteProp
|
|
|
51
106
|
result.skipped.push(proposalId);
|
|
52
107
|
continue;
|
|
53
108
|
}
|
|
109
|
+
// Either above-threshold (normal auto-accept) or exploration-budget promoted.
|
|
110
|
+
if (isExploration)
|
|
111
|
+
explorationRemaining -= 1;
|
|
112
|
+
const promoteReason = isExploration ? "exploration-budget" : "above-threshold";
|
|
113
|
+
if (currentProposal?.gateDecision?.outcome === "auto-rejected" &&
|
|
114
|
+
currentProposal.gateDecision.contentHash !== undefined &&
|
|
115
|
+
currentProposal.gateDecision.contentHash === currentContentHash) {
|
|
116
|
+
result.suppressed.push(proposalId);
|
|
117
|
+
continue;
|
|
118
|
+
}
|
|
54
119
|
try {
|
|
55
120
|
const promotion = await promoteFn(cfg.stashDir, resolvedConfig, proposalId, {}, undefined);
|
|
56
121
|
stamp(promotion.proposal.id, {
|
|
57
122
|
outcome: "auto-accepted",
|
|
58
|
-
reason:
|
|
123
|
+
reason: promoteReason,
|
|
59
124
|
confidence,
|
|
60
125
|
thresholds: { autoAccept: effectiveThreshold },
|
|
126
|
+
...(currentContentHash !== undefined ? { contentHash: currentContentHash } : {}),
|
|
61
127
|
gate: gateLabel,
|
|
62
128
|
});
|
|
129
|
+
// Resolve the eligibilitySource: exploration-promoted proposals get
|
|
130
|
+
// eligibilitySource="exploration" (WS-4); normal auto-accepts carry
|
|
131
|
+
// whatever the proposal was tagged with at selection time.
|
|
132
|
+
const resolvedEligibilitySource = isExploration
|
|
133
|
+
? "exploration"
|
|
134
|
+
: promotion.proposal.eligibilitySource;
|
|
63
135
|
appendEvent({
|
|
64
136
|
eventType: "promoted",
|
|
65
137
|
ref: promotion.ref,
|
|
@@ -72,14 +144,42 @@ export async function runAutoAcceptGate(candidates, cfg, promoteFn = promoteProp
|
|
|
72
144
|
confidence,
|
|
73
145
|
threshold: effectiveThreshold,
|
|
74
146
|
phase: cfg.phase,
|
|
147
|
+
// Attribution tagging: carry the eligibility lane from the proposal
|
|
148
|
+
// record onto the auto-accept promoted event so the lane survives to
|
|
149
|
+
// accept time even when promotion happens in a later run.
|
|
150
|
+
...(resolvedEligibilitySource !== undefined ? { eligibilitySource: resolvedEligibilitySource } : {}),
|
|
151
|
+
// WS-4: mark exploration promotions so health/telemetry can
|
|
152
|
+
// distinguish them from calibration-signal promotions.
|
|
153
|
+
...(isExploration ? { explorationBudget: true } : {}),
|
|
75
154
|
},
|
|
76
155
|
}, cfg.eventsCtx ?? {});
|
|
77
|
-
|
|
156
|
+
if (isExploration) {
|
|
157
|
+
info(`[improve] exploration-accepted ${promotion.ref} (${cfg.phase}; confidence=${confidence.toFixed(2)}; budgetRemaining=${explorationRemaining})`);
|
|
158
|
+
}
|
|
159
|
+
else {
|
|
160
|
+
info(`[improve] auto-accepted ${promotion.ref} (${cfg.phase}; confidence=${confidence.toFixed(2)} >= threshold=${effectiveThreshold.toFixed(2)})`);
|
|
161
|
+
}
|
|
78
162
|
result.promoted.push(proposalId);
|
|
79
163
|
}
|
|
80
164
|
catch (err) {
|
|
81
|
-
|
|
165
|
+
const reason = classifyPromoteFailure(err);
|
|
166
|
+
warn(`[improve] ${cfg.phase} auto-accept failed for ${proposalId} (${reason}): ${err instanceof Error ? err.message : String(err)}`);
|
|
82
167
|
result.failed.push(proposalId);
|
|
168
|
+
result.failedByReason[reason] = (result.failedByReason[reason] ?? 0) + 1;
|
|
169
|
+
// Record WHY on the proposal so `akm proposal show` explains the rejection
|
|
170
|
+
// and the leak is no longer blind. Best-effort.
|
|
171
|
+
stamp(proposalId, {
|
|
172
|
+
outcome: "auto-rejected",
|
|
173
|
+
reason,
|
|
174
|
+
confidence,
|
|
175
|
+
thresholds: { autoAccept: effectiveThreshold },
|
|
176
|
+
...(currentContentHash !== undefined ? { contentHash: currentContentHash } : {}),
|
|
177
|
+
gate: gateLabel,
|
|
178
|
+
});
|
|
179
|
+
// If exploration budget was consumed but promotion failed, restore the slot
|
|
180
|
+
// so the budget isn't exhausted on errors.
|
|
181
|
+
if (isExploration)
|
|
182
|
+
explorationRemaining += 1;
|
|
83
183
|
}
|
|
84
184
|
}
|
|
85
185
|
return result;
|
|
@@ -108,15 +208,46 @@ export function resolveExtractConfidence(proposal) {
|
|
|
108
208
|
/**
|
|
109
209
|
* Build a gate config for a phase, inheriting global settings from the
|
|
110
210
|
* improve options. Callers supply only the phase-specific overrides.
|
|
211
|
+
*
|
|
212
|
+
* WS-4 additions:
|
|
213
|
+
* - When `shared.stateDbPath` is provided, reads the persisted per-phase
|
|
214
|
+
* threshold from `improve_gate_thresholds` (Migration 012). The phase
|
|
215
|
+
* value overrides `globalThreshold` but is still floored by
|
|
216
|
+
* `minimumThreshold`. Falls back to `globalThreshold` when no row exists.
|
|
217
|
+
* - Computes `explorationBudgetCount` from
|
|
218
|
+
* `config.improve.exploration.budgetFraction × candidateCount` when the
|
|
219
|
+
* exploration budget is enabled. Defaults to 0 (no exploration).
|
|
111
220
|
*/
|
|
112
221
|
export function makeGateConfig(phase, shared, overrides = {}) {
|
|
222
|
+
// WS-4: read per-phase threshold from state.db when available.
|
|
223
|
+
let phaseThreshold;
|
|
224
|
+
if (shared.stateDbPath && shared.globalThreshold !== undefined) {
|
|
225
|
+
try {
|
|
226
|
+
phaseThreshold = withStateDb((db) => getPhaseThreshold(db, phase) ?? undefined, {
|
|
227
|
+
path: shared.stateDbPath,
|
|
228
|
+
});
|
|
229
|
+
}
|
|
230
|
+
catch {
|
|
231
|
+
// DB unavailable — fall back to globalThreshold silently.
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
// WS-4: compute exploration budget count from config fraction × candidateCount.
|
|
235
|
+
let explorationBudgetCount;
|
|
236
|
+
const resolvedConfig = typeof shared.config === "function" ? shared.config() : shared.config;
|
|
237
|
+
const explorationCfg = resolvedConfig.improve?.exploration;
|
|
238
|
+
if (explorationCfg?.enabled && shared.candidateCount !== undefined && shared.candidateCount > 0) {
|
|
239
|
+
const fraction = Math.min(1, Math.max(0, explorationCfg.budgetFraction ?? 0.05));
|
|
240
|
+
explorationBudgetCount = Math.max(0, Math.floor(fraction * shared.candidateCount));
|
|
241
|
+
}
|
|
113
242
|
return {
|
|
114
243
|
phase,
|
|
115
244
|
globalThreshold: shared.globalThreshold,
|
|
245
|
+
...(phaseThreshold !== undefined ? { phaseThreshold } : {}),
|
|
116
246
|
dryRun: shared.dryRun,
|
|
117
247
|
stashDir: shared.stashDir,
|
|
118
248
|
config: shared.config,
|
|
119
249
|
eventsCtx: shared.eventsCtx,
|
|
250
|
+
...(explorationBudgetCount !== undefined && explorationBudgetCount > 0 ? { explorationBudgetCount } : {}),
|
|
120
251
|
...overrides,
|
|
121
252
|
};
|
|
122
253
|
}
|
|
@@ -13,6 +13,7 @@ import { resolveSourceEntries } from "../../indexer/search/search-source.js";
|
|
|
13
13
|
import { getHyphenatedArg, getHyphenatedBoolean, parseFlagValue } from "../../output/context.js";
|
|
14
14
|
import { akmImprove } from "./improve.js";
|
|
15
15
|
import { buildImproveRunId, recordTerminatedImproveRun, relativeImproveResultPath, writeImproveResultFile, } from "./improve-result-file.js";
|
|
16
|
+
import { runImproveSession } from "./improve-session.js";
|
|
16
17
|
export const improveCommand = defineCommand({
|
|
17
18
|
meta: {
|
|
18
19
|
name: "improve",
|
|
@@ -144,67 +145,44 @@ export const improveCommand = defineCommand({
|
|
|
144
145
|
process.stderr.write(`warning: failed to persist terminated improve run ${runId}: ${err instanceof Error ? err.message : String(err)}\n`);
|
|
145
146
|
}
|
|
146
147
|
};
|
|
147
|
-
//
|
|
148
|
-
//
|
|
149
|
-
//
|
|
150
|
-
// a SIGTERM'd run
|
|
151
|
-
// improve_runs
|
|
152
|
-
//
|
|
153
|
-
//
|
|
154
|
-
|
|
155
|
-
SIGTERM: { code: 143, reason: "SIGTERM", ack: true },
|
|
156
|
-
SIGINT: { code: 130, reason: "SIGINT", ack: true },
|
|
157
|
-
SIGHUP: { code: 129, reason: "SIGHUP", ack: false },
|
|
158
|
-
};
|
|
159
|
-
const makeSignalHandler = (sig) => () => {
|
|
160
|
-
const { code, reason, ack } = SIGNAL_TABLE[sig];
|
|
161
|
-
// Hard-exit fallback: if the synchronous persist ever hangs (e.g. a
|
|
162
|
-
// stuck sqlite lock under contention), the watchdog still exits with
|
|
163
|
-
// the correct code instead of leaving a zombie process. .unref() keeps
|
|
164
|
-
// the timer from holding the loop open on the normal (fast) path.
|
|
165
|
-
const watchdog = setTimeout(() => process.exit(code), 2000);
|
|
166
|
-
if (typeof watchdog.unref === "function")
|
|
167
|
-
watchdog.unref();
|
|
168
|
-
try {
|
|
169
|
-
persistTerminated(reason);
|
|
170
|
-
}
|
|
171
|
-
finally {
|
|
172
|
-
clearTimeout(watchdog);
|
|
173
|
-
}
|
|
174
|
-
if (ack) {
|
|
175
|
-
process.stderr.write(`[improve] received ${sig}; recorded terminated run ${runId}\n`);
|
|
176
|
-
}
|
|
177
|
-
process.exit(code);
|
|
178
|
-
};
|
|
179
|
-
const sigtermHandler = makeSignalHandler("SIGTERM");
|
|
180
|
-
const sigintHandler = makeSignalHandler("SIGINT");
|
|
181
|
-
const sighupHandler = makeSignalHandler("SIGHUP");
|
|
182
|
-
process.once("SIGTERM", sigtermHandler);
|
|
183
|
-
process.once("SIGINT", sigintHandler);
|
|
184
|
-
process.once("SIGHUP", sighupHandler);
|
|
148
|
+
// R8: the signal table / handlers / watchdog / persist-before-exit
|
|
149
|
+
// choreography lives in `runImproveSession`. It registers the
|
|
150
|
+
// SIGTERM/SIGINT/SIGHUP handlers (each persists the terminated-run row
|
|
151
|
+
// BEFORE process.exit so a SIGTERM'd run — e.g. cron timeout — always
|
|
152
|
+
// leaves a row in improve_runs), awaits the work, then removes the
|
|
153
|
+
// handlers on the way out. `onTerminate` persists synchronously
|
|
154
|
+
// (recordTerminatedImproveRun -> bun:sqlite writes are sync), and the
|
|
155
|
+
// 2000ms watchdog inside the session force-exits if that ever hangs.
|
|
185
156
|
let improveResult;
|
|
186
157
|
try {
|
|
187
|
-
improveResult = await
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
target: targetArg,
|
|
192
|
-
autoAccept,
|
|
193
|
-
...(runId !== undefined ? { runId } : {}),
|
|
194
|
-
...(limitRaw !== undefined ? { limit: limitRaw } : {}),
|
|
195
|
-
...(timeoutMs !== undefined ? { timeoutMs } : {}),
|
|
196
|
-
...(minRetrievalCount !== undefined ? { minRetrievalCount } : {}),
|
|
197
|
-
...(requireFeedbackSignal ? { requireFeedbackSignal } : {}),
|
|
198
|
-
...(skipIfLocked ? { skipIfLocked } : {}),
|
|
199
|
-
...(profileArg !== undefined ? { profile: profileArg } : {}),
|
|
200
|
-
...(Object.keys(syncOverride).length > 0 ? { sync: syncOverride } : {}),
|
|
201
|
-
consolidateOptions: {
|
|
202
|
-
target: targetArg,
|
|
158
|
+
improveResult = await runImproveSession({
|
|
159
|
+
runWork: () => akmImprove({
|
|
160
|
+
scope: scopeArg,
|
|
161
|
+
task: taskArg,
|
|
203
162
|
dryRun,
|
|
163
|
+
target: targetArg,
|
|
204
164
|
autoAccept,
|
|
205
|
-
|
|
206
|
-
...(
|
|
207
|
-
|
|
165
|
+
...(runId !== undefined ? { runId } : {}),
|
|
166
|
+
...(limitRaw !== undefined ? { limit: limitRaw } : {}),
|
|
167
|
+
...(timeoutMs !== undefined ? { timeoutMs } : {}),
|
|
168
|
+
...(minRetrievalCount !== undefined ? { minRetrievalCount } : {}),
|
|
169
|
+
...(requireFeedbackSignal ? { requireFeedbackSignal } : {}),
|
|
170
|
+
...(skipIfLocked ? { skipIfLocked } : {}),
|
|
171
|
+
...(profileArg !== undefined ? { profile: profileArg } : {}),
|
|
172
|
+
...(Object.keys(syncOverride).length > 0 ? { sync: syncOverride } : {}),
|
|
173
|
+
consolidateOptions: {
|
|
174
|
+
target: targetArg,
|
|
175
|
+
dryRun,
|
|
176
|
+
autoAccept,
|
|
177
|
+
task: taskArg,
|
|
178
|
+
...(consolidateRecovery !== undefined ? { recoveryMode: consolidateRecovery } : {}),
|
|
179
|
+
},
|
|
180
|
+
}),
|
|
181
|
+
}, {
|
|
182
|
+
signalSource: process,
|
|
183
|
+
exit: process.exit,
|
|
184
|
+
onTerminate: (reason) => persistTerminated(reason),
|
|
185
|
+
ack: (message) => process.stderr.write(`[improve] ${message}; recorded terminated run ${runId}\n`),
|
|
208
186
|
});
|
|
209
187
|
}
|
|
210
188
|
catch (err) {
|
|
@@ -216,9 +194,6 @@ export const improveCommand = defineCommand({
|
|
|
216
194
|
throw err;
|
|
217
195
|
}
|
|
218
196
|
finally {
|
|
219
|
-
process.removeListener("SIGTERM", sigtermHandler);
|
|
220
|
-
process.removeListener("SIGINT", sigintHandler);
|
|
221
|
-
process.removeListener("SIGHUP", sighupHandler);
|
|
222
197
|
clearLogFile();
|
|
223
198
|
}
|
|
224
199
|
const durationMs = Date.now() - startedAtMs;
|
|
@@ -246,7 +221,7 @@ export const improveCommand = defineCommand({
|
|
|
246
221
|
runRecorded = true; // Suppress any late signal-handler write — the success path owns the row now.
|
|
247
222
|
if (primaryStashDir) {
|
|
248
223
|
try {
|
|
249
|
-
writeImproveResultFile(primaryStashDir, runId, improveResult, startedAtIso);
|
|
224
|
+
writeImproveResultFile(primaryStashDir, runId, improveResult, startedAtIso, profileArg ?? null);
|
|
250
225
|
}
|
|
251
226
|
catch (err) {
|
|
252
227
|
// Stderr warning on the failure path is preferable to crashing
|
|
@@ -8,6 +8,7 @@ import profileFrequent from "../../assets/profiles/frequent.json" with { type: "
|
|
|
8
8
|
import profileGraphRefresh from "../../assets/profiles/graph-refresh.json" with { type: "json" };
|
|
9
9
|
import profileMemoryFocus from "../../assets/profiles/memory-focus.json" with { type: "json" };
|
|
10
10
|
import profileQuick from "../../assets/profiles/quick.json" with { type: "json" };
|
|
11
|
+
import profileSynthesize from "../../assets/profiles/synthesize.json" with { type: "json" };
|
|
11
12
|
import profileThorough from "../../assets/profiles/thorough.json" with { type: "json" };
|
|
12
13
|
import { parseAssetRef } from "../../core/asset/asset-ref.js";
|
|
13
14
|
import { warn } from "../../core/warn.js";
|
|
@@ -31,6 +32,7 @@ const BUILTIN_PROFILES = {
|
|
|
31
32
|
frequent: profileFrequent,
|
|
32
33
|
consolidate: profileConsolidate,
|
|
33
34
|
catchup: profileCatchup,
|
|
35
|
+
synthesize: profileSynthesize,
|
|
34
36
|
};
|
|
35
37
|
/**
|
|
36
38
|
* Default enabled-state for known improve processes when neither the user
|
|
@@ -54,6 +56,18 @@ const IMPROVE_PROCESS_DEFAULTS = {
|
|
|
54
56
|
// proposal-queue triage drains the standing backlog. Opt-in (default off),
|
|
55
57
|
// like `validation` — needs an explicit `enabled: true`.
|
|
56
58
|
triage: false,
|
|
59
|
+
// Layer 2 proactive-maintenance selector. Opt-in (default off) — surfaces
|
|
60
|
+
// stale high-value assets on a schedule. Enable per-profile with an explicit
|
|
61
|
+
// `processes.proactiveMaintenance.enabled: true`.
|
|
62
|
+
proactiveMaintenance: false,
|
|
63
|
+
// #609 recombine / synthesize pass — whole-corpus cross-episodic
|
|
64
|
+
// generalization. Opt-in (default off); enable per-profile with an explicit
|
|
65
|
+
// `processes.recombine.enabled: true`.
|
|
66
|
+
recombine: false,
|
|
67
|
+
// #615 procedural-compilation pass — detects recurring successful ordered
|
|
68
|
+
// action sequences and compiles them into workflow proposals. Opt-in (default
|
|
69
|
+
// off); enable per-profile with an explicit `processes.procedural.enabled: true`.
|
|
70
|
+
procedural: false,
|
|
57
71
|
};
|
|
58
72
|
/**
|
|
59
73
|
* Compute the effective enabled-state for a named improve process.
|
|
@@ -27,7 +27,7 @@
|
|
|
27
27
|
*/
|
|
28
28
|
import crypto from "node:crypto";
|
|
29
29
|
import path from "node:path";
|
|
30
|
-
import {
|
|
30
|
+
import { recordImproveRun, withStateDb } from "../../core/state-db.js";
|
|
31
31
|
/**
|
|
32
32
|
* Build a stable run-id for a single improve invocation.
|
|
33
33
|
*
|
|
@@ -72,10 +72,16 @@ export function relativeImproveResultPath(runId) {
|
|
|
72
72
|
* dry-run column is indexed so productivity audits can filter cleanly
|
|
73
73
|
* (closes the dry-run/real-run artifact-trap recorded in MEMORY.md
|
|
74
74
|
* `feedback_akm_dryrun_artifact_trap`).
|
|
75
|
+
*
|
|
76
|
+
* @param profile - The `--profile` value passed to this invocation (e.g.
|
|
77
|
+
* `quick`, `reflect-distill`), or `null`/`undefined` when no profile was
|
|
78
|
+
* given. Mirrors {@link recordTerminatedImproveRun}'s `ctx.profile`
|
|
79
|
+
* convention so successful and terminated runs are equally queryable by
|
|
80
|
+
* profile. Previously hardcoded to `null` here, which meant only
|
|
81
|
+
* abnormally-terminated runs recorded their profile in state.db.
|
|
75
82
|
*/
|
|
76
|
-
export function writeImproveResultFile(stashDir, runId, result, startedAt) {
|
|
77
|
-
|
|
78
|
-
try {
|
|
83
|
+
export function writeImproveResultFile(stashDir, runId, result, startedAt, profile) {
|
|
84
|
+
withStateDb((db) => {
|
|
79
85
|
const completedAt = new Date().toISOString();
|
|
80
86
|
// startedAt is the ISO timestamp captured at process launch (passed from the
|
|
81
87
|
// CLI entry point). If omitted, fall back to the run-id's embedded timestamp
|
|
@@ -88,22 +94,14 @@ export function writeImproveResultFile(stashDir, runId, result, startedAt) {
|
|
|
88
94
|
completedAt,
|
|
89
95
|
stashDir,
|
|
90
96
|
dryRun: Boolean(result.dryRun),
|
|
91
|
-
profile: null,
|
|
97
|
+
profile: profile ?? null,
|
|
92
98
|
scopeMode: result.scope?.mode ?? "all",
|
|
93
99
|
scopeValue: result.scope?.value ?? null,
|
|
94
100
|
guidance: result.guidance ?? null,
|
|
95
101
|
ok: Boolean(result.ok),
|
|
96
102
|
result,
|
|
97
103
|
});
|
|
98
|
-
}
|
|
99
|
-
finally {
|
|
100
|
-
try {
|
|
101
|
-
db.close();
|
|
102
|
-
}
|
|
103
|
-
catch {
|
|
104
|
-
// best-effort
|
|
105
|
-
}
|
|
106
|
-
}
|
|
104
|
+
});
|
|
107
105
|
return relativeImproveResultPath(runId);
|
|
108
106
|
}
|
|
109
107
|
/**
|
|
@@ -138,8 +136,7 @@ export function recordTerminatedImproveRun(stashDir, runId, startedAt, reason, c
|
|
|
138
136
|
...(ctx?.errorMessage ? { errorMessage: ctx.errorMessage } : {}),
|
|
139
137
|
},
|
|
140
138
|
};
|
|
141
|
-
|
|
142
|
-
try {
|
|
139
|
+
withStateDb((db) => {
|
|
143
140
|
recordImproveRun(db, {
|
|
144
141
|
id: runId,
|
|
145
142
|
startedAt,
|
|
@@ -160,13 +157,5 @@ export function recordTerminatedImproveRun(stashDir, runId, startedAt, reason, c
|
|
|
160
157
|
},
|
|
161
158
|
},
|
|
162
159
|
});
|
|
163
|
-
}
|
|
164
|
-
finally {
|
|
165
|
-
try {
|
|
166
|
-
db.close();
|
|
167
|
-
}
|
|
168
|
-
catch {
|
|
169
|
-
// best-effort
|
|
170
|
-
}
|
|
171
|
-
}
|
|
160
|
+
});
|
|
172
161
|
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
// This Source Code Form is subject to the terms of the Mozilla Public
|
|
2
|
+
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
3
|
+
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
|
4
|
+
/** Signal -> {exit code, reason, ack message}. */
|
|
5
|
+
export const SIGNAL_TABLE = {
|
|
6
|
+
SIGTERM: { code: 143, reason: "SIGTERM", ack: true },
|
|
7
|
+
SIGINT: { code: 130, reason: "SIGINT", ack: true },
|
|
8
|
+
SIGHUP: { code: 129, reason: "SIGHUP", ack: false },
|
|
9
|
+
};
|
|
10
|
+
/**
|
|
11
|
+
* Drive the improve-run lifecycle: register signal handlers, await `runWork`,
|
|
12
|
+
* and on clean completion remove the handlers and resolve with the result. The
|
|
13
|
+
* caller (improve-cli.ts) owns success-path result persistence + the final
|
|
14
|
+
* `exit(0)`; this function returns the work result so that choreography stays
|
|
15
|
+
* in the CLI exactly as before.
|
|
16
|
+
*
|
|
17
|
+
* Abnormal paths:
|
|
18
|
+
* - A registered signal fires -> persist terminated row (watchdog-guarded) ->
|
|
19
|
+
* optional ack -> `exit(code)`.
|
|
20
|
+
* - `runWork` rejects -> handlers are removed and the rejection propagates so
|
|
21
|
+
* the CLI's existing catch can persist `"exception"` and rethrow.
|
|
22
|
+
*/
|
|
23
|
+
export async function runImproveSession(opts, deps) {
|
|
24
|
+
const { signalSource, exit, onTerminate, ack } = deps;
|
|
25
|
+
const makeSignalHandler = (sig) => () => {
|
|
26
|
+
const { code, reason, ack: shouldAck } = SIGNAL_TABLE[sig];
|
|
27
|
+
// Hard-exit fallback: if the synchronous persist ever hangs (e.g. a stuck
|
|
28
|
+
// sqlite lock under contention), the watchdog still exits with the correct
|
|
29
|
+
// code instead of leaving a zombie process. .unref() keeps the timer from
|
|
30
|
+
// holding the loop open on the normal (fast) path.
|
|
31
|
+
const watchdog = setTimeout(() => exit(code), 2000);
|
|
32
|
+
if (typeof watchdog.unref === "function")
|
|
33
|
+
watchdog.unref();
|
|
34
|
+
try {
|
|
35
|
+
onTerminate(reason);
|
|
36
|
+
}
|
|
37
|
+
finally {
|
|
38
|
+
clearTimeout(watchdog);
|
|
39
|
+
}
|
|
40
|
+
if (shouldAck)
|
|
41
|
+
ack?.(`received ${sig}`);
|
|
42
|
+
exit(code);
|
|
43
|
+
};
|
|
44
|
+
const sigtermHandler = makeSignalHandler("SIGTERM");
|
|
45
|
+
const sigintHandler = makeSignalHandler("SIGINT");
|
|
46
|
+
const sighupHandler = makeSignalHandler("SIGHUP");
|
|
47
|
+
signalSource.once("SIGTERM", sigtermHandler);
|
|
48
|
+
signalSource.once("SIGINT", sigintHandler);
|
|
49
|
+
signalSource.once("SIGHUP", sighupHandler);
|
|
50
|
+
try {
|
|
51
|
+
return await opts.runWork();
|
|
52
|
+
}
|
|
53
|
+
finally {
|
|
54
|
+
signalSource.removeListener("SIGTERM", sigtermHandler);
|
|
55
|
+
signalSource.removeListener("SIGINT", sigintHandler);
|
|
56
|
+
signalSource.removeListener("SIGHUP", sighupHandler);
|
|
57
|
+
}
|
|
58
|
+
}
|