akm-cli 0.8.6 → 0.8.14
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 +442 -0
- package/dist/assets/help/help-proposals.md +1 -2
- package/dist/assets/hints/cli-hints-full.md +34 -19
- package/dist/assets/hints/cli-hints-short.md +1 -1
- package/dist/assets/profiles/catchup.json +13 -0
- package/dist/assets/profiles/consolidate.json +13 -0
- package/dist/assets/profiles/frequent.json +13 -0
- package/dist/assets/tasks/core/backup.yml +4 -0
- package/dist/assets/tasks/core/extract.yml +4 -0
- package/dist/assets/tasks/core/improve.yml +4 -0
- package/dist/assets/tasks/core/index-refresh.yml +4 -0
- package/dist/assets/tasks/core/sync.yml +4 -0
- package/dist/assets/tasks/core/update-stashes.yml +4 -0
- package/dist/assets/tasks/core/version-check.yml +4 -0
- package/dist/assets/templates/html/default.html +78 -0
- package/dist/assets/templates/html/health.html +560 -0
- package/dist/assets/templates/html/vendor/echarts.min.js +45 -0
- package/dist/cli/config-migrate.js +6 -6
- package/dist/cli/config-validate.js +4 -4
- package/dist/cli/confirm.js +3 -3
- package/dist/cli/parse-args.js +1 -1
- package/dist/cli/shared.js +72 -19
- package/dist/cli-node.mjs +26 -0
- package/dist/cli.js +206 -3866
- package/dist/commands/{agent-dispatch.js → agent/agent-dispatch.js} +6 -6
- package/dist/commands/{agent-support.js → agent/agent-support.js} +2 -2
- package/dist/commands/agent/contribute-cli.js +200 -0
- package/dist/commands/completions.js +1 -1
- package/dist/commands/config-cli.js +230 -3
- package/dist/commands/db-cli.js +2 -2
- package/dist/commands/env/env-cli.js +529 -0
- package/dist/commands/env/env.js +410 -0
- package/dist/commands/env/secret-cli.js +259 -0
- package/dist/commands/{secret.js → env/secret.js} +6 -47
- package/dist/commands/events.js +4 -4
- package/dist/commands/feedback-cli.js +18 -34
- package/dist/commands/graph/graph-cli.js +132 -0
- package/dist/commands/{graph.js → graph/graph.js} +22 -16
- package/dist/commands/health/checks.js +279 -0
- package/dist/commands/health/html-report.js +448 -0
- package/dist/commands/health.js +189 -266
- package/dist/commands/{consolidate.js → improve/consolidate.js} +63 -38
- package/dist/commands/{distill-promotion-policy.js → improve/distill-promotion-policy.js} +3 -3
- package/dist/commands/{distill.js → improve/distill.js} +39 -18
- package/dist/commands/{eval-cases.js → improve/eval-cases.js} +1 -1
- package/dist/commands/{extract-cli.js → improve/extract-cli.js} +4 -4
- package/dist/commands/{extract-prompt.js → improve/extract-prompt.js} +2 -2
- package/dist/commands/{extract.js → improve/extract.js} +221 -26
- package/dist/commands/{improve-auto-accept.js → improve/improve-auto-accept.js} +30 -4
- package/dist/commands/{improve-cli.js → improve/improve-cli.js} +44 -22
- package/dist/commands/{improve-profiles.js → improve/improve-profiles.js} +13 -7
- package/dist/commands/{improve-result-file.js → improve/improve-result-file.js} +1 -1
- package/dist/commands/{improve.js → improve/improve.js} +672 -292
- package/dist/{core → commands/improve/memory}/memory-belief.js +2 -2
- package/dist/{core → commands/improve/memory}/memory-contradiction-detect.js +5 -5
- package/dist/{core → commands/improve/memory}/memory-improve.js +4 -4
- package/dist/commands/improve/reflect-noise.js +0 -0
- package/dist/commands/{reflect.js → improve/reflect.js} +58 -28
- package/dist/commands/improve/session-asset.js +248 -0
- package/dist/commands/lint/agent-linter.js +1 -1
- package/dist/commands/lint/base-linter.js +55 -37
- package/dist/commands/lint/command-linter.js +1 -1
- package/dist/commands/lint/default-linter.js +1 -1
- package/dist/commands/lint/env-key-rules.js +1 -1
- package/dist/commands/lint/index.js +19 -25
- package/dist/commands/lint/knowledge-linter.js +1 -1
- package/dist/commands/lint/memory-linter.js +1 -1
- package/dist/commands/lint/registry.js +8 -8
- package/dist/commands/lint/skill-linter.js +1 -1
- package/dist/commands/lint/task-linter.js +1 -1
- package/dist/commands/lint/workflow-linter.js +1 -1
- package/dist/commands/lint.js +1 -1
- package/dist/commands/observability-cli.js +244 -0
- package/dist/commands/proposal/drain-policies.js +3 -3
- package/dist/commands/proposal/drain.js +87 -15
- package/dist/commands/proposal/proposal-cli.js +490 -0
- package/dist/commands/{proposal.js → proposal/proposal.js} +17 -6
- package/dist/commands/{propose.js → proposal/propose.js} +11 -11
- package/dist/{core → commands/proposal/validators}/proposal-quality-validators.js +8 -3
- package/dist/{core → commands/proposal/validators}/proposal-validators.js +5 -5
- package/dist/{core → commands/proposal/validators}/proposals.js +374 -345
- package/dist/commands/{curate.js → read/curate.js} +7 -7
- package/dist/commands/{knowledge.js → read/knowledge.js} +22 -9
- package/dist/commands/{registry-search.js → read/registry-search.js} +5 -5
- package/dist/commands/{remember-cli.js → read/remember-cli.js} +15 -7
- package/dist/commands/read/search-cli.js +207 -0
- package/dist/commands/{search.js → read/search.js} +22 -27
- package/dist/commands/{show.js → read/show.js} +31 -45
- package/dist/commands/registry-cli.js +8 -8
- package/dist/commands/remember.js +14 -10
- package/dist/commands/sources/add-cli.js +293 -0
- package/dist/commands/{history.js → sources/history.js} +27 -25
- package/dist/commands/{info.js → sources/info.js} +6 -6
- package/dist/commands/{init.js → sources/init.js} +6 -6
- package/dist/commands/{installed-stashes.js → sources/installed-stashes.js} +12 -12
- package/dist/commands/{migration-help.js → sources/migration-help.js} +3 -2
- package/dist/commands/{schema-repair.js → sources/schema-repair.js} +8 -8
- package/dist/commands/{self-update.js → sources/self-update.js} +10 -9
- package/dist/commands/{source-add.js → sources/source-add.js} +10 -10
- package/dist/commands/{source-clone.js → sources/source-clone.js} +7 -7
- package/dist/commands/{source-manage.js → sources/source-manage.js} +4 -4
- package/dist/commands/sources/sources-cli.js +305 -0
- package/dist/commands/sources/stash-cli.js +219 -0
- package/dist/commands/{stash-skeleton.js → sources/stash-skeleton.js} +2 -1
- package/dist/commands/tasks/default-tasks.js +173 -0
- package/dist/commands/tasks/tasks-cli.js +210 -0
- package/dist/commands/{tasks.js → tasks/tasks.js} +14 -14
- package/dist/commands/wiki-cli.js +307 -0
- package/dist/commands/workflow-cli.js +329 -0
- package/dist/core/action-contributors.js +1 -1
- package/dist/core/assert.js +40 -0
- package/dist/core/asset/asset-create.js +54 -0
- package/dist/core/{asset-ref.js → asset/asset-ref.js} +21 -4
- package/dist/core/{asset-registry.js → asset/asset-registry.js} +3 -3
- package/dist/core/{asset-spec.js → asset/asset-spec.js} +17 -31
- package/dist/core/{markdown.js → asset/markdown.js} +1 -1
- package/dist/core/{stash-meta.js → asset/stash-meta.js} +1 -1
- package/dist/core/best-effort.js +64 -0
- package/dist/core/common.js +32 -18
- package/dist/core/{config-io.js → config/config-io.js} +29 -19
- package/dist/core/{config-migration.js → config/config-migration.js} +11 -9
- package/dist/core/{config-schema.js → config/config-schema.js} +50 -7
- package/dist/core/config/config-types.js +16 -0
- package/dist/core/{config-walker.js → config/config-walker.js} +2 -2
- package/dist/core/{config.js → config/config.js} +10 -8
- package/dist/core/env-secret-ref.js +90 -0
- package/dist/core/errors.js +13 -3
- package/dist/core/events.js +27 -4
- package/dist/core/file-lock.js +1 -1
- package/dist/core/improve-types.js +48 -0
- package/dist/core/lesson-lint.js +2 -2
- package/dist/core/logs-db.js +304 -0
- package/dist/core/paths.js +2 -2
- package/dist/core/ripgrep/install.js +2 -2
- package/dist/core/ripgrep/resolve.js +2 -2
- package/dist/core/state-db.js +195 -60
- package/dist/core/text-truncation.js +148 -0
- package/dist/core/time.js +1 -1
- package/dist/core/write-source.js +98 -85
- package/dist/indexer/{db-backup.js → db/db-backup.js} +9 -24
- package/dist/indexer/{db.js → db/db.js} +128 -118
- package/dist/indexer/{graph-db.js → db/graph-db.js} +9 -4
- package/dist/indexer/{llm-cache.js → db/llm-cache.js} +15 -12
- package/dist/indexer/ensure-index.js +4 -4
- package/dist/indexer/{graph-boost.js → graph/graph-boost.js} +1 -1
- package/dist/indexer/{graph-extraction.js → graph/graph-extraction.js} +55 -13
- package/dist/indexer/indexer.js +37 -30
- package/dist/indexer/init.js +54 -0
- package/dist/indexer/manifest.js +10 -10
- package/dist/indexer/{memory-inference.js → passes/memory-inference.js} +141 -33
- package/dist/indexer/{metadata-contributors.js → passes/metadata-contributors.js} +10 -8
- package/dist/indexer/{metadata.js → passes/metadata.js} +15 -19
- package/dist/indexer/{staleness-detect.js → passes/staleness-detect.js} +53 -12
- package/dist/indexer/{db-search.js → search/db-search.js} +28 -16
- package/dist/indexer/{ranking-contributors.js → search/ranking-contributors.js} +1 -1
- package/dist/indexer/{ranking.js → search/ranking.js} +2 -2
- package/dist/indexer/{search-hit-enrichers.js → search/search-hit-enrichers.js} +3 -3
- package/dist/indexer/{search-source.js → search/search-source.js} +8 -8
- package/dist/indexer/{semantic-status.js → search/semantic-status.js} +3 -3
- package/dist/indexer/usage/unmigrated-vaults-guard.js +94 -0
- package/dist/indexer/{usage-events.js → usage/usage-events.js} +32 -0
- package/dist/indexer/{file-context.js → walk/file-context.js} +10 -15
- package/dist/indexer/{matchers.js → walk/matchers.js} +13 -9
- package/dist/indexer/{path-resolver.js → walk/path-resolver.js} +6 -6
- package/dist/indexer/{project-context.js → walk/project-context.js} +1 -1
- package/dist/indexer/{walker.js → walk/walker.js} +4 -3
- package/dist/integrations/agent/builder-shared.js +39 -0
- package/dist/integrations/agent/builders.js +14 -81
- package/dist/integrations/agent/config.js +6 -4
- package/dist/integrations/agent/detect.js +1 -1
- package/dist/integrations/agent/index.js +23 -8
- package/dist/integrations/agent/prompts.js +2 -3
- package/dist/integrations/agent/runner.js +22 -3
- package/dist/integrations/agent/spawn.js +9 -10
- package/dist/integrations/harnesses/claude/agent-builder.js +48 -0
- package/dist/integrations/harnesses/claude/config-import.js +70 -0
- package/dist/integrations/harnesses/claude/index.js +64 -0
- package/dist/integrations/{session-logs/providers/claude-code.js → harnesses/claude/session-log.js} +32 -5
- package/dist/integrations/harnesses/index.js +144 -0
- package/dist/integrations/harnesses/opencode/agent-builder.js +43 -0
- package/dist/integrations/harnesses/opencode/config-import.js +82 -0
- package/dist/integrations/harnesses/opencode/index.js +59 -0
- package/dist/integrations/{session-logs/providers/opencode.js → harnesses/opencode/session-log.js} +1 -1
- package/dist/integrations/harnesses/opencode-sdk/index.js +49 -0
- package/dist/integrations/harnesses/opencode-sdk/sdk-runner.js +234 -0
- package/dist/integrations/harnesses/types.js +43 -0
- package/dist/integrations/lockfile.js +7 -16
- package/dist/integrations/session-logs/index.js +82 -9
- package/dist/llm/call-ai.js +4 -4
- package/dist/llm/client.js +146 -6
- package/dist/llm/embedder.js +6 -6
- package/dist/llm/embedders/local.js +9 -22
- package/dist/llm/embedders/remote.js +2 -2
- package/dist/llm/embedders/types.js +1 -1
- package/dist/llm/graph-extract.js +31 -12
- package/dist/llm/index-passes.js +1 -1
- package/dist/llm/memory-infer.js +12 -5
- package/dist/llm/metadata-enhance.js +2 -2
- package/dist/llm/usage-persist.js +77 -0
- package/dist/llm/usage-telemetry.js +103 -0
- package/dist/output/context.js +9 -46
- package/dist/output/html-render.js +73 -0
- package/dist/output/renderers.js +88 -58
- package/dist/output/shapes/curate.js +7 -3
- package/dist/output/shapes/distill.js +7 -3
- package/dist/output/shapes/env-list.js +18 -16
- package/dist/output/shapes/events.js +5 -4
- package/dist/output/shapes/helpers.js +19 -5
- package/dist/output/shapes/history.js +7 -3
- package/dist/output/shapes/passthrough.js +8 -11
- package/dist/output/shapes/{proposal-accept.js → proposal/accept.js} +7 -3
- package/dist/output/shapes/{proposal-diff.js → proposal/diff.js} +7 -3
- package/dist/output/shapes/{proposal-list.js → proposal/list.js} +7 -3
- package/dist/output/shapes/{proposal-producer.js → proposal/producer.js} +5 -4
- package/dist/output/shapes/{proposal-reject.js → proposal/reject.js} +7 -3
- package/dist/output/shapes/{proposal-show.js → proposal/show.js} +7 -3
- package/dist/output/shapes/registry-search.js +7 -3
- package/dist/output/shapes/registry.js +12 -0
- package/dist/output/shapes/search.js +7 -3
- package/dist/output/shapes/secret-list.js +18 -16
- package/dist/output/shapes/show.js +7 -3
- package/dist/output/shapes.js +55 -30
- package/dist/output/text/add.js +2 -3
- package/dist/output/text/clone.js +2 -3
- package/dist/output/text/config.js +2 -3
- package/dist/output/text/curate.js +4 -3
- package/dist/output/text/distill.js +2 -3
- package/dist/output/text/enable-disable.js +5 -4
- package/dist/output/text/env.js +13 -0
- package/dist/output/text/events.js +5 -4
- package/dist/output/text/feedback.js +4 -3
- package/dist/output/text/helpers.js +123 -40
- package/dist/output/text/history.js +2 -3
- package/dist/output/text/import.js +2 -3
- package/dist/output/text/index.js +2 -3
- package/dist/output/text/info.js +2 -3
- package/dist/output/text/init.js +2 -3
- package/dist/output/text/list.js +2 -3
- package/dist/output/text/proposal/producer.js +9 -0
- package/dist/output/text/proposal/proposal.js +13 -0
- package/dist/output/text/registry-commands.js +8 -7
- package/dist/output/text/registry.js +12 -0
- package/dist/output/text/remember.js +4 -3
- package/dist/output/text/remove.js +2 -3
- package/dist/output/text/save.js +2 -3
- package/dist/output/text/search.js +4 -3
- package/dist/output/text/show.js +4 -3
- package/dist/output/text/update.js +2 -3
- package/dist/output/text/upgrade.js +2 -3
- package/dist/output/text/wiki.js +12 -11
- package/dist/output/text/workflow.js +12 -10
- package/dist/output/text.js +66 -32
- package/dist/registry/build-index.js +11 -10
- package/dist/registry/factory.js +1 -1
- package/dist/registry/origin-resolve.js +1 -1
- package/dist/registry/providers/index.js +2 -2
- package/dist/registry/providers/skills-sh.js +91 -72
- package/dist/registry/providers/static-index.js +75 -52
- package/dist/registry/resolve.js +3 -3
- package/dist/runtime.js +242 -0
- package/dist/scripts/migrate-storage.js +1654 -683
- package/dist/scripts/migrations/import-fs-improve-runs-to-db.js +254 -168
- package/dist/setup/detect.js +311 -9
- package/dist/setup/harness-config-import.js +6 -120
- package/dist/setup/setup.js +454 -43
- package/dist/sources/include.js +1 -1
- package/dist/sources/provider-factory.js +2 -2
- package/dist/sources/providers/filesystem.js +3 -3
- package/dist/sources/providers/git.js +9 -9
- package/dist/sources/providers/index.js +4 -4
- package/dist/sources/providers/npm.js +6 -6
- package/dist/sources/providers/provider-utils.js +13 -20
- package/dist/sources/providers/sync-from-ref.js +5 -5
- package/dist/sources/providers/tar-utils.js +2 -2
- package/dist/sources/providers/website.js +2 -2
- package/dist/sources/resolve.js +5 -5
- package/dist/sources/website-ingest.js +5 -5
- package/dist/storage/database.js +102 -0
- package/dist/storage/engines/sqlite-migrations.js +42 -0
- package/dist/storage/locations.js +25 -0
- package/dist/storage/repositories/index-db.js +43 -0
- package/dist/storage/repositories/workflow-runs-repository.js +141 -0
- package/dist/tasks/backends/cron.js +4 -4
- package/dist/tasks/backends/exec-utils.js +32 -0
- package/dist/tasks/backends/index.js +3 -3
- package/dist/tasks/backends/launchd.js +7 -14
- package/dist/tasks/backends/schtasks.js +7 -16
- package/dist/tasks/embedded.js +71 -0
- package/dist/tasks/parser.js +2 -2
- package/dist/tasks/resolveAkmBin.js +1 -1
- package/dist/tasks/runner.js +127 -31
- package/dist/tasks/schedule.js +1 -1
- package/dist/tasks/validator.js +7 -7
- package/dist/text-import-hook.mjs +51 -0
- package/dist/version.js +2 -1
- package/dist/wiki/wiki.js +7 -7
- package/dist/workflows/{authoring.js → authoring/authoring.js} +6 -6
- package/dist/workflows/{scope-key.js → authoring/scope-key.js} +1 -1
- package/dist/workflows/cli.js +1 -1
- package/dist/workflows/db.js +54 -32
- package/dist/workflows/parser.js +4 -4
- package/dist/workflows/renderer.js +5 -5
- package/dist/workflows/runtime/agent-identity.js +56 -0
- package/dist/workflows/runtime/checkin.js +57 -0
- package/dist/workflows/{runs.js → runtime/runs.js} +197 -101
- package/dist/workflows/validate-summary.js +82 -0
- package/docs/README.md +1 -1
- package/docs/data-and-telemetry.md +6 -6
- package/package.json +17 -8
- package/dist/commands/add-cli.js +0 -279
- package/dist/commands/env.js +0 -213
- package/dist/integrations/agent/sdk-runner.js +0 -126
- package/dist/output/shapes/vault-list.js +0 -19
- package/dist/output/text/proposal-producer.js +0 -8
- package/dist/output/text/proposal.js +0 -12
- package/dist/output/text/vault.js +0 -16
- /package/dist/core/{asset-serialize.js → asset/asset-serialize.js} +0 -0
- /package/dist/core/{frontmatter.js → asset/frontmatter.js} +0 -0
- /package/dist/core/{config-sources.js → config/config-sources.js} +0 -0
- /package/dist/indexer/{graph-dedup.js → graph/graph-dedup.js} +0 -0
- /package/dist/{core/config-types.js → indexer/passes/pass-context.js} +0 -0
- /package/dist/indexer/{search-fields.js → search/search-fields.js} +0 -0
- /package/dist/indexer/{index-context.js → walk/index-context.js} +0 -0
- /package/dist/workflows/{document-cache.js → runtime/document-cache.js} +0 -0
package/dist/core/state-db.js
CHANGED
|
@@ -49,11 +49,13 @@
|
|
|
49
49
|
*
|
|
50
50
|
* @module state-db
|
|
51
51
|
*/
|
|
52
|
-
import { Database } from "bun:sqlite";
|
|
53
52
|
import fs from "node:fs";
|
|
54
53
|
import path from "node:path";
|
|
55
|
-
import {
|
|
56
|
-
import {
|
|
54
|
+
import { openDatabase } from "../storage/database.js";
|
|
55
|
+
import { runMigrations as runSqliteMigrations } from "../storage/engines/sqlite-migrations.js";
|
|
56
|
+
import { classifyImproveAction } from "./improve-types.js";
|
|
57
|
+
import { getDataDir } from "./paths.js";
|
|
58
|
+
import { error } from "./warn.js";
|
|
57
59
|
// ── Path helper ──────────────────────────────────────────────────────────────
|
|
58
60
|
/**
|
|
59
61
|
* Default path: `<dataDir>/state.db`.
|
|
@@ -84,11 +86,12 @@ export function getStateDbPath() {
|
|
|
84
86
|
* backwards compatibility; enabling them prevents orphaned rows in tables
|
|
85
87
|
* that reference each other (not used in v1 schema but guards future ones).
|
|
86
88
|
*
|
|
87
|
-
* busy_timeout =
|
|
89
|
+
* busy_timeout = 30000
|
|
88
90
|
* When another connection holds a write lock, SQLite retries for up to
|
|
89
|
-
*
|
|
90
|
-
* is 0 ms — any concurrent writer causes an immediate error.
|
|
91
|
-
* the
|
|
91
|
+
* 30 000 ms before returning SQLITE_BUSY. Without this, the default timeout
|
|
92
|
+
* is 0 ms — any concurrent writer causes an immediate error. 30 s (#589)
|
|
93
|
+
* matches the value used in openDatabase() for index.db; 5 s proved too
|
|
94
|
+
* narrow when a post-inference reindex overlapped a parallel event write.
|
|
92
95
|
*/
|
|
93
96
|
export function openStateDatabase(dbPath) {
|
|
94
97
|
const resolvedPath = dbPath ?? getStateDbPath();
|
|
@@ -96,14 +99,20 @@ export function openStateDatabase(dbPath) {
|
|
|
96
99
|
if (!fs.existsSync(dir)) {
|
|
97
100
|
fs.mkdirSync(dir, { recursive: true });
|
|
98
101
|
}
|
|
99
|
-
const db =
|
|
102
|
+
const db = openDatabase(resolvedPath);
|
|
100
103
|
// PRAGMAs must run before any DDL or DML.
|
|
101
104
|
db.exec("PRAGMA journal_mode = WAL");
|
|
102
105
|
db.exec("PRAGMA foreign_keys = ON");
|
|
103
|
-
db.exec("PRAGMA busy_timeout =
|
|
106
|
+
db.exec("PRAGMA busy_timeout = 30000");
|
|
104
107
|
runMigrations(db);
|
|
105
108
|
return db;
|
|
106
109
|
}
|
|
110
|
+
// ── Migration engine ─────────────────────────────────────────────────────────
|
|
111
|
+
//
|
|
112
|
+
// The runner itself (ensureMigrationsTable + runMigrations) lives in the shared
|
|
113
|
+
// engine at src/storage/engines/sqlite-migrations.ts. This module owns only its
|
|
114
|
+
// own MIGRATIONS array and delegates application to that shared runner. The
|
|
115
|
+
// {@link Migration} interface is imported from there.
|
|
107
116
|
/**
|
|
108
117
|
* All migrations in application order. New migrations are APPENDED to this
|
|
109
118
|
* array — never inserted in the middle or reordered.
|
|
@@ -182,7 +191,9 @@ const MIGRATIONS = [
|
|
|
182
191
|
--
|
|
183
192
|
-- Extensible (metadata_json) columns:
|
|
184
193
|
-- metadata_json TEXT — JSON object for future proposal fields.
|
|
185
|
-
-- Current fields stored here: sourceRun,
|
|
194
|
+
-- Current fields stored here: sourceRun,
|
|
195
|
+
-- review, confidence, gateDecision (#577),
|
|
196
|
+
-- backupContent.
|
|
186
197
|
--
|
|
187
198
|
-- ADD COLUMN extension points (future migrations):
|
|
188
199
|
-- ALTER TABLE proposals ADD COLUMN source_run TEXT DEFAULT NULL;
|
|
@@ -450,42 +461,44 @@ const MIGRATIONS = [
|
|
|
450
461
|
ON extract_sessions_seen(processed_at);
|
|
451
462
|
`,
|
|
452
463
|
},
|
|
464
|
+
// ── Migration 005 — proposal_fs_imports ─────────────────────────────────────
|
|
465
|
+
//
|
|
466
|
+
// One-shot ledger for the legacy filesystem→SQLite proposal import (#578).
|
|
467
|
+
//
|
|
468
|
+
// Before 0.9.0 the proposal queue lived as per-uuid JSON directories under
|
|
469
|
+
// `<stashDir>/.akm/proposals/` and the `proposals` table (created in 001) was
|
|
470
|
+
// dead weight. 0.9.0 makes the table canonical; the first proposal operation
|
|
471
|
+
// against a stash imports any legacy `proposal.json` files it finds (INSERT
|
|
472
|
+
// OR IGNORE, so re-runs never duplicate) and records the stash here so later
|
|
473
|
+
// invocations skip the directory walk entirely.
|
|
474
|
+
//
|
|
475
|
+
// Indexed (query) columns:
|
|
476
|
+
// stash_dir TEXT PK — absolute stash root the import ran against.
|
|
477
|
+
//
|
|
478
|
+
// Non-indexed columns:
|
|
479
|
+
// imported_at TEXT — ISO-8601 UTC; when the import completed.
|
|
480
|
+
// imported_count INTEGER — rows actually inserted by the import.
|
|
481
|
+
{
|
|
482
|
+
id: "005-proposal-fs-imports",
|
|
483
|
+
up: `
|
|
484
|
+
CREATE TABLE IF NOT EXISTS proposal_fs_imports (
|
|
485
|
+
stash_dir TEXT PRIMARY KEY,
|
|
486
|
+
imported_at TEXT NOT NULL,
|
|
487
|
+
imported_count INTEGER NOT NULL DEFAULT 0
|
|
488
|
+
);
|
|
489
|
+
`,
|
|
490
|
+
},
|
|
453
491
|
];
|
|
454
|
-
/**
|
|
455
|
-
* Create the migrations table if it does not exist. This must be called
|
|
456
|
-
* unconditionally on every open so a fresh database bootstraps correctly.
|
|
457
|
-
*/
|
|
458
|
-
function ensureMigrationsTable(db) {
|
|
459
|
-
db.exec(`
|
|
460
|
-
CREATE TABLE IF NOT EXISTS schema_migrations (
|
|
461
|
-
id TEXT PRIMARY KEY,
|
|
462
|
-
applied_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
463
|
-
);
|
|
464
|
-
`);
|
|
465
|
-
}
|
|
466
492
|
/**
|
|
467
493
|
* Apply every pending migration in a single transaction per migration.
|
|
468
494
|
*
|
|
469
|
-
*
|
|
470
|
-
*
|
|
471
|
-
* inserted AFTER the DDL succeeds, so a crash mid-migration leaves no row and
|
|
472
|
-
* the migration will be retried on next open (all DDL in `up` uses IF NOT
|
|
473
|
-
* EXISTS so the retry is safe).
|
|
495
|
+
* Delegates to the shared SQLite migration engine; state.db has no
|
|
496
|
+
* pre-versioning bootstrap step, so no `bootstrap` hook is passed.
|
|
474
497
|
*
|
|
475
498
|
* Called automatically by `openStateDatabase()`.
|
|
476
499
|
*/
|
|
477
500
|
export function runMigrations(db) {
|
|
478
|
-
|
|
479
|
-
const appliedRows = db.prepare("SELECT id FROM schema_migrations").all();
|
|
480
|
-
const applied = new Set(appliedRows.map((r) => r.id));
|
|
481
|
-
for (const migration of MIGRATIONS) {
|
|
482
|
-
if (applied.has(migration.id))
|
|
483
|
-
continue;
|
|
484
|
-
db.transaction(() => {
|
|
485
|
-
db.exec(migration.up);
|
|
486
|
-
db.prepare("INSERT INTO schema_migrations (id) VALUES (?)").run(migration.id);
|
|
487
|
-
})();
|
|
488
|
-
}
|
|
501
|
+
runSqliteMigrations(db, MIGRATIONS);
|
|
489
502
|
}
|
|
490
503
|
/**
|
|
491
504
|
* Convert a raw `EventRow` from the database to the public `EventEnvelope`
|
|
@@ -546,6 +559,9 @@ export function proposalRowToProposal(row) {
|
|
|
546
559
|
...(frontmatter !== undefined ? { frontmatter } : {}),
|
|
547
560
|
},
|
|
548
561
|
...(meta.review !== undefined ? { review: meta.review } : {}),
|
|
562
|
+
...(typeof meta.confidence === "number" ? { confidence: meta.confidence } : {}),
|
|
563
|
+
...(meta.gateDecision !== undefined ? { gateDecision: meta.gateDecision } : {}),
|
|
564
|
+
...(typeof meta.backupContent === "string" ? { backupContent: meta.backupContent } : {}),
|
|
549
565
|
};
|
|
550
566
|
}
|
|
551
567
|
/**
|
|
@@ -559,6 +575,12 @@ export function proposalToRowValues(proposal, stashDir) {
|
|
|
559
575
|
metaObj.sourceRun = proposal.sourceRun;
|
|
560
576
|
if (proposal.review !== undefined)
|
|
561
577
|
metaObj.review = proposal.review;
|
|
578
|
+
if (proposal.confidence !== undefined)
|
|
579
|
+
metaObj.confidence = proposal.confidence;
|
|
580
|
+
if (proposal.gateDecision !== undefined)
|
|
581
|
+
metaObj.gateDecision = proposal.gateDecision;
|
|
582
|
+
if (proposal.backupContent !== undefined)
|
|
583
|
+
metaObj.backupContent = proposal.backupContent;
|
|
562
584
|
return {
|
|
563
585
|
id: proposal.id,
|
|
564
586
|
stash_dir: stashDir,
|
|
@@ -673,7 +695,10 @@ export function upsertProposal(db, proposal, stashDir) {
|
|
|
673
695
|
}
|
|
674
696
|
/**
|
|
675
697
|
* List proposals, optionally filtered by stashDir, status, and/or ref.
|
|
676
|
-
*
|
|
698
|
+
*
|
|
699
|
+
* Results are ordered by `created_at ASC` (matching the historical
|
|
700
|
+
* `listProposals()` sort), with `rowid` as a deterministic tiebreak so two
|
|
701
|
+
* proposals created in the same millisecond list in insertion order.
|
|
677
702
|
*/
|
|
678
703
|
export function listStateProposals(db, options = {}) {
|
|
679
704
|
const conditions = [];
|
|
@@ -694,21 +719,72 @@ export function listStateProposals(db, options = {}) {
|
|
|
694
719
|
const rows = db
|
|
695
720
|
.prepare(`SELECT id, stash_dir, ref, status, source, created_at, updated_at,
|
|
696
721
|
content, frontmatter_json, metadata_json
|
|
697
|
-
FROM proposals ${where} ORDER BY created_at ASC`)
|
|
722
|
+
FROM proposals ${where} ORDER BY created_at ASC, rowid ASC`)
|
|
698
723
|
.all(...params);
|
|
699
724
|
return rows.map(proposalRowToProposal);
|
|
700
725
|
}
|
|
701
726
|
/**
|
|
702
|
-
* Look up a single proposal by id
|
|
727
|
+
* Look up a single proposal by id, optionally scoped to one stash root.
|
|
728
|
+
* Returns undefined when not found.
|
|
703
729
|
*/
|
|
704
|
-
export function getStateProposal(db, id) {
|
|
705
|
-
const
|
|
706
|
-
.prepare(`SELECT id, stash_dir, ref, status, source, created_at, updated_at,
|
|
730
|
+
export function getStateProposal(db, id, stashDir) {
|
|
731
|
+
const sql = `SELECT id, stash_dir, ref, status, source, created_at, updated_at,
|
|
707
732
|
content, frontmatter_json, metadata_json
|
|
708
|
-
FROM proposals WHERE id =
|
|
709
|
-
|
|
733
|
+
FROM proposals WHERE id = ?${stashDir ? " AND stash_dir = ?" : ""}`;
|
|
734
|
+
const row = (stashDir ? db.prepare(sql).get(id, stashDir) : db.prepare(sql).get(id));
|
|
710
735
|
return row ? proposalRowToProposal(row) : undefined;
|
|
711
736
|
}
|
|
737
|
+
/**
|
|
738
|
+
* Find PENDING proposal ids in one stash whose id starts with `idPrefix`.
|
|
739
|
+
* Backs the UUID-prefix form of `akm proposal show/accept/... <prefix>` —
|
|
740
|
+
* prefix resolution is deliberately scoped to the live (pending) queue,
|
|
741
|
+
* mirroring the historical behaviour of scanning only the live directory.
|
|
742
|
+
*
|
|
743
|
+
* `%` / `_` / `\` in the prefix are escaped so the LIKE pattern is literal.
|
|
744
|
+
*/
|
|
745
|
+
export function listStateProposalIdsByPrefix(db, stashDir, idPrefix) {
|
|
746
|
+
const escaped = idPrefix.replace(/[\\%_]/g, (ch) => `\\${ch}`);
|
|
747
|
+
const rows = db
|
|
748
|
+
.prepare(`SELECT id FROM proposals
|
|
749
|
+
WHERE stash_dir = ? AND status = 'pending' AND id LIKE ? ESCAPE '\\'
|
|
750
|
+
ORDER BY id ASC`)
|
|
751
|
+
.all(stashDir, `${escaped}%`);
|
|
752
|
+
return rows.map((r) => r.id);
|
|
753
|
+
}
|
|
754
|
+
/**
|
|
755
|
+
* Whether the legacy filesystem proposal import has already run for `stashDir`.
|
|
756
|
+
* See migration 005 (`proposal_fs_imports`).
|
|
757
|
+
*/
|
|
758
|
+
export function hasImportedFsProposals(db, stashDir) {
|
|
759
|
+
// Drivers disagree on the no-row sentinel (bun:sqlite → null,
|
|
760
|
+
// better-sqlite3 → undefined) — Boolean() covers both.
|
|
761
|
+
return Boolean(db.prepare("SELECT 1 FROM proposal_fs_imports WHERE stash_dir = ?").get(stashDir));
|
|
762
|
+
}
|
|
763
|
+
/**
|
|
764
|
+
* Record that the legacy filesystem proposal import completed for `stashDir`
|
|
765
|
+
* so subsequent invocations skip the directory walk. INSERT OR REPLACE keeps
|
|
766
|
+
* the call idempotent.
|
|
767
|
+
*/
|
|
768
|
+
export function recordFsProposalsImport(db, stashDir, importedCount) {
|
|
769
|
+
db.prepare("INSERT OR REPLACE INTO proposal_fs_imports (stash_dir, imported_at, imported_count) VALUES (?, ?, ?)").run(stashDir, new Date().toISOString(), importedCount);
|
|
770
|
+
}
|
|
771
|
+
/**
|
|
772
|
+
* Insert a proposal row ONLY when the id is not already present (used by the
|
|
773
|
+
* legacy filesystem import so re-runs never clobber rows that have since been
|
|
774
|
+
* mutated through the canonical store). Returns true when a row was inserted.
|
|
775
|
+
*/
|
|
776
|
+
export function insertProposalIfAbsent(db, proposal, stashDir) {
|
|
777
|
+
const v = proposalToRowValues(proposal, stashDir);
|
|
778
|
+
const result = db
|
|
779
|
+
.prepare(`
|
|
780
|
+
INSERT OR IGNORE INTO proposals
|
|
781
|
+
(id, stash_dir, ref, status, source, created_at, updated_at, content, frontmatter_json, metadata_json)
|
|
782
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
783
|
+
`)
|
|
784
|
+
.run(v.id, v.stash_dir, v.ref, v.status, v.source, v.created_at, v.updated_at, v.content, v.frontmatter_json, v.metadata_json);
|
|
785
|
+
const changes = result.changes ?? 0;
|
|
786
|
+
return Number(changes) > 0;
|
|
787
|
+
}
|
|
712
788
|
// ── task_history table helpers ───────────────────────────────────────────────
|
|
713
789
|
/**
|
|
714
790
|
* Upsert a task history row.
|
|
@@ -779,6 +855,47 @@ export function queryTaskHistory(db, options = {}) {
|
|
|
779
855
|
FROM task_history ${where} ORDER BY started_at DESC`)
|
|
780
856
|
.all(...params);
|
|
781
857
|
}
|
|
858
|
+
/**
|
|
859
|
+
* Read COMPLETED `akm-improve` task_history runs whose `started_at` falls in
|
|
860
|
+
* `[since, until)` (or `started_at >= since` when `until` is omitted), ordered
|
|
861
|
+
* oldest-first by `started_at`. Only rows with a non-null `completed_at` are
|
|
862
|
+
* returned (in-flight runs are excluded). The `task_id = 'akm-improve'`
|
|
863
|
+
* predicate is fixed because the only caller (commands/health.ts
|
|
864
|
+
* `loadTaskIntervals`) builds wall-time intervals for the improve cron task.
|
|
865
|
+
*
|
|
866
|
+
* Owns the SQL formerly inlined in commands/health.ts. Note the bound is
|
|
867
|
+
* EXCLUSIVE on the upper end (`started_at < ?`) — callers pass an already
|
|
868
|
+
* widened window; this helper does not widen.
|
|
869
|
+
*
|
|
870
|
+
* Connection-lifetime rule (WS5): `.all()` materializes a plain array before
|
|
871
|
+
* returning.
|
|
872
|
+
*/
|
|
873
|
+
export function queryCompletedTaskIntervals(db, since, until) {
|
|
874
|
+
const sql = until
|
|
875
|
+
? "SELECT started_at, completed_at FROM task_history WHERE task_id = 'akm-improve' AND started_at >= ? AND started_at < ? AND completed_at IS NOT NULL ORDER BY started_at"
|
|
876
|
+
: "SELECT started_at, completed_at FROM task_history WHERE task_id = 'akm-improve' AND started_at >= ? AND completed_at IS NOT NULL ORDER BY started_at";
|
|
877
|
+
return (until ? db.prepare(sql).all(since, until) : db.prepare(sql).all(since));
|
|
878
|
+
}
|
|
879
|
+
// ── schema introspection ─────────────────────────────────────────────────────
|
|
880
|
+
/**
|
|
881
|
+
* Return the subset of `names` that exist as TABLEs in this database, ordered
|
|
882
|
+
* by name. Used by health's state-db-schema check to detect missing required
|
|
883
|
+
* tables without leaking a `sqlite_master` query into command code.
|
|
884
|
+
*
|
|
885
|
+
* The `IN (...)` predicate is built from parameter placeholders so table names
|
|
886
|
+
* are bound, never interpolated.
|
|
887
|
+
*
|
|
888
|
+
* Connection-lifetime rule (WS5): `.all()` materializes a plain array before
|
|
889
|
+
* returning.
|
|
890
|
+
*/
|
|
891
|
+
export function listExistingTableNames(db, names) {
|
|
892
|
+
if (names.length === 0)
|
|
893
|
+
return [];
|
|
894
|
+
const placeholders = names.map(() => "?").join(", ");
|
|
895
|
+
return db
|
|
896
|
+
.prepare(`SELECT name FROM sqlite_master WHERE type = 'table' AND name IN (${placeholders}) ORDER BY name`)
|
|
897
|
+
.all(...names);
|
|
898
|
+
}
|
|
782
899
|
// ── events.jsonl import ──────────────────────────────────────────────────────
|
|
783
900
|
/**
|
|
784
901
|
* Import all events from an `events.jsonl` file into the `events` table.
|
|
@@ -877,25 +994,23 @@ export function computeImproveRunMetrics(result) {
|
|
|
877
994
|
let autoAcceptedCount = 0;
|
|
878
995
|
let errorCount = 0;
|
|
879
996
|
for (const action of actions) {
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
997
|
+
// Bucketing delegated to the shared classifyImproveAction so this aggregate
|
|
998
|
+
// and the improve_completed event in improve.ts can never disagree, and so a
|
|
999
|
+
// new union variant is a compile error rather than a silent drop. Note:
|
|
1000
|
+
// `reflect-guard-rejected` now counts as "rejected" (previously this switch
|
|
1001
|
+
// omitted it entirely — a data-integrity miscount). "noop" (memory-prune) is
|
|
1002
|
+
// intentionally counted in none of the three numeric buckets.
|
|
1003
|
+
switch (classifyImproveAction(action.mode)) {
|
|
1004
|
+
case "accepted":
|
|
885
1005
|
acceptedCount++;
|
|
886
1006
|
break;
|
|
887
|
-
case "
|
|
888
|
-
case "reflect-skipped":
|
|
889
|
-
case "distill-skipped":
|
|
1007
|
+
case "rejected":
|
|
890
1008
|
rejectedCount++;
|
|
891
1009
|
break;
|
|
892
|
-
case "reflect-failed":
|
|
893
1010
|
case "error":
|
|
894
1011
|
errorCount++;
|
|
895
1012
|
break;
|
|
896
|
-
case "
|
|
897
|
-
// Prune is bookkeeping, not "accepted" content authoring; count
|
|
898
|
-
// separately as a no-op for the audit aggregate.
|
|
1013
|
+
case "noop":
|
|
899
1014
|
break;
|
|
900
1015
|
}
|
|
901
1016
|
// Legacy: pre-gate action results may carry autoAccepted: true (reflect path).
|
|
@@ -929,6 +1044,26 @@ export function recordImproveRun(db, input) {
|
|
|
929
1044
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
930
1045
|
`).run(input.id, input.startedAt, input.completedAt, input.stashDir, input.dryRun ? 1 : 0, input.profile, input.scopeMode, input.scopeValue, input.guidance, input.ok ? 1 : 0, JSON.stringify(input.result), JSON.stringify(metricsObj), JSON.stringify(input.metadata ?? {}));
|
|
931
1046
|
}
|
|
1047
|
+
/**
|
|
1048
|
+
* Read real (non-dry-run) improve_runs rows whose `started_at` falls in the
|
|
1049
|
+
* window `[since, until)`. When `until` is omitted the window is open-ended
|
|
1050
|
+
* (`started_at >= since`). Rows are returned newest-first (`ORDER BY
|
|
1051
|
+
* started_at DESC`).
|
|
1052
|
+
*
|
|
1053
|
+
* Owns the SQL formerly inlined in commands/health.ts (`loadImproveRunRows`).
|
|
1054
|
+
* The `dry_run = 0` filter is first-class so dry-run probes never pollute
|
|
1055
|
+
* productivity audits.
|
|
1056
|
+
*
|
|
1057
|
+
* Connection-lifetime rule (WS5): `.all()` fully materializes the result set
|
|
1058
|
+
* into a plain array before returning — no live cursor escapes the caller's
|
|
1059
|
+
* `openStateDatabase` scope.
|
|
1060
|
+
*/
|
|
1061
|
+
export function queryImproveRuns(db, since, until) {
|
|
1062
|
+
const sql = until
|
|
1063
|
+
? "SELECT id, started_at, completed_at, ok, scope_mode, scope_value, result_json FROM improve_runs WHERE started_at >= ? AND started_at < ? AND dry_run = 0 ORDER BY started_at DESC"
|
|
1064
|
+
: "SELECT id, started_at, completed_at, ok, scope_mode, scope_value, result_json FROM improve_runs WHERE started_at >= ? AND dry_run = 0 ORDER BY started_at DESC";
|
|
1065
|
+
return (until ? db.prepare(sql).all(since, until) : db.prepare(sql).all(since));
|
|
1066
|
+
}
|
|
932
1067
|
/**
|
|
933
1068
|
* Delete improve_runs rows older than `retentionDays` (default: 90). Mirrors
|
|
934
1069
|
* {@link purgeOldEvents} — same default, same return shape (number of rows
|
|
@@ -1027,7 +1162,7 @@ export function shouldSkipAlreadyExtractedSession(prior, liveSessionEndedAtMs) {
|
|
|
1027
1162
|
// ── registry_index_cache (goes in index.db, not state.db) ───────────────────
|
|
1028
1163
|
/**
|
|
1029
1164
|
* DDL for the `registry_index_cache` table that lives in the EXISTING index.db
|
|
1030
|
-
* (managed by src/indexer/db.ts).
|
|
1165
|
+
* (managed by src/indexer/db/db.ts).
|
|
1031
1166
|
*
|
|
1032
1167
|
* Design: uses the same migration-safe ADD COLUMN approach. The table is
|
|
1033
1168
|
* created with CREATE TABLE IF NOT EXISTS so it is safe to call inside
|
|
@@ -1051,7 +1186,7 @@ export function shouldSkipAlreadyExtractedSession(prior, liveSessionEndedAtMs) {
|
|
|
1051
1186
|
* ALTER TABLE registry_index_cache ADD COLUMN error_message TEXT DEFAULT NULL;
|
|
1052
1187
|
*
|
|
1053
1188
|
* To add this table to index.db, call ensureRegistryIndexCacheSchema(db) from
|
|
1054
|
-
* within ensureSchema() in src/indexer/db.ts, or add it as a new CREATE TABLE
|
|
1189
|
+
* within ensureSchema() in src/indexer/db/db.ts, or add it as a new CREATE TABLE
|
|
1055
1190
|
* IF NOT EXISTS block inside the existing ensureSchema() call.
|
|
1056
1191
|
*/
|
|
1057
1192
|
export const REGISTRY_INDEX_CACHE_DDL = `
|
|
@@ -105,3 +105,151 @@ export function detectTruncatedDescription(description) {
|
|
|
105
105
|
}
|
|
106
106
|
return null;
|
|
107
107
|
}
|
|
108
|
+
// ── Post-generation repair pass (issue #556) ─────────────────────────────────
|
|
109
|
+
/**
|
|
110
|
+
* Minimum length (chars) for a repaired description to be considered usable.
|
|
111
|
+
* Mirrors the floor in `isValidDescription` (≥20). Kept local so this module
|
|
112
|
+
* stays dependency-free of the validators (avoids an import cycle).
|
|
113
|
+
*/
|
|
114
|
+
const MIN_REPAIRED_DESCRIPTION_LEN = 20;
|
|
115
|
+
/** Maximum length (chars) for a repaired description. Mirrors `isValidDescription` (≤400). */
|
|
116
|
+
const MAX_REPAIRED_DESCRIPTION_LEN = 400;
|
|
117
|
+
/**
|
|
118
|
+
* Strip trailing truncation fragments from a candidate clause: repeatedly drop
|
|
119
|
+
* a hanging-connector word and/or trailing `,` `;` `:` `+` and ellipses until
|
|
120
|
+
* the clause no longer looks truncated. PURE — no fabrication, only removal.
|
|
121
|
+
*
|
|
122
|
+
* Returns the trimmed-to-last-complete-token clause (may be empty if the whole
|
|
123
|
+
* thing was connectors/punctuation).
|
|
124
|
+
*/
|
|
125
|
+
function stripTrailingTruncationFragment(clause) {
|
|
126
|
+
let s = clause.trim();
|
|
127
|
+
// Loop because a tail can stack: "… related to the" → drop "the" → drop "to".
|
|
128
|
+
for (let guard = 0; guard < 64; guard++) {
|
|
129
|
+
const before = s;
|
|
130
|
+
// Drop trailing ellipsis / connector-only punctuation+operators.
|
|
131
|
+
s = s.replace(/\s*(\.{3,}|…)$/u, "").trim();
|
|
132
|
+
s = s.replace(/\s*[,;:+]+$/u, "").trim();
|
|
133
|
+
// Drop a trailing hanging-connector word (and any punctuation glued to it).
|
|
134
|
+
const m = s.match(/(?:^|\s)([A-Za-z']+)[.!?]*$/u);
|
|
135
|
+
if (m) {
|
|
136
|
+
const word = (m[1] ?? "").toLowerCase();
|
|
137
|
+
if (TRUNCATION_TRAILING_WORDS.has(word)) {
|
|
138
|
+
// Remove just the final word token (keep preceding text).
|
|
139
|
+
s = s.slice(0, s.length - m[0].length).trim();
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
if (s === before)
|
|
143
|
+
break;
|
|
144
|
+
}
|
|
145
|
+
return s;
|
|
146
|
+
}
|
|
147
|
+
/**
|
|
148
|
+
* Split prose into sentences on `.`/`!`/`?` boundaries (followed by whitespace
|
|
149
|
+
* or end). Keeps the terminating punctuation. Deliberately simple — good enough
|
|
150
|
+
* for the short, single-paragraph descriptions/bodies this repair operates on.
|
|
151
|
+
*/
|
|
152
|
+
function splitSentences(text) {
|
|
153
|
+
const out = [];
|
|
154
|
+
for (const match of text.matchAll(/[^.!?]+[.!?]+(?=\s|$)|[^.!?]+$/gu)) {
|
|
155
|
+
const s = match[0].trim();
|
|
156
|
+
if (s)
|
|
157
|
+
out.push(s);
|
|
158
|
+
}
|
|
159
|
+
return out;
|
|
160
|
+
}
|
|
161
|
+
/**
|
|
162
|
+
* True when `candidate` is a self-contained, non-truncated clause of acceptable
|
|
163
|
+
* length. Used to gate repair outputs without importing the full validator
|
|
164
|
+
* (which would create a cycle). The full `isValidDescription` still runs
|
|
165
|
+
* downstream — this is only the truncation/length subset the repair targets.
|
|
166
|
+
*/
|
|
167
|
+
function isUsableClause(candidate) {
|
|
168
|
+
const c = candidate.trim();
|
|
169
|
+
if (c.length < MIN_REPAIRED_DESCRIPTION_LEN)
|
|
170
|
+
return false;
|
|
171
|
+
if (c.length > MAX_REPAIRED_DESCRIPTION_LEN)
|
|
172
|
+
return false;
|
|
173
|
+
if (detectTruncatedDescription(c) !== null)
|
|
174
|
+
return false;
|
|
175
|
+
// Must contain at least one word character (not pure punctuation).
|
|
176
|
+
if (!/[A-Za-z0-9]/.test(c))
|
|
177
|
+
return false;
|
|
178
|
+
return true;
|
|
179
|
+
}
|
|
180
|
+
/** Append a period when the clause lacks terminal sentence punctuation. PURE. */
|
|
181
|
+
function ensureTerminalPunctuation(clause) {
|
|
182
|
+
const c = clause.trim();
|
|
183
|
+
if (/[.!?]$/.test(c))
|
|
184
|
+
return c;
|
|
185
|
+
return `${c}.`;
|
|
186
|
+
}
|
|
187
|
+
/**
|
|
188
|
+
* Deterministically repair an LLM-generated description that was sliced
|
|
189
|
+
* mid-sentence (issue #556). The repair NEVER fabricates new claims:
|
|
190
|
+
*
|
|
191
|
+
* 1. If the description is not truncated, it is returned **byte-identical**
|
|
192
|
+
* (zero behaviour change for already-valid descriptions).
|
|
193
|
+
* 2. Otherwise:
|
|
194
|
+
* a. **trim-to-last-complete-clause** — strip the trailing
|
|
195
|
+
* truncation-indicator word(s)/punctuation; if a complete earlier
|
|
196
|
+
* sentence survives, use the longest non-truncated prefix.
|
|
197
|
+
* b. **swap-in first body sentence** — if (a) yields nothing usable and a
|
|
198
|
+
* `body` is provided, use the first clean, complete sentence of the
|
|
199
|
+
* body.
|
|
200
|
+
* c. **fallback** — if neither produces a usable, non-truncated clause,
|
|
201
|
+
* return the original string unchanged so the existing validation
|
|
202
|
+
* rejects it exactly as before (no regression, no fabrication).
|
|
203
|
+
*
|
|
204
|
+
* @param description The raw description (possibly truncated).
|
|
205
|
+
* @param body Optional asset body to source a clean completion sentence from.
|
|
206
|
+
*/
|
|
207
|
+
export function repairTruncatedDescription(description, body) {
|
|
208
|
+
if (typeof description !== "string")
|
|
209
|
+
return description;
|
|
210
|
+
// Guarantee: untruncated input passes through byte-identical.
|
|
211
|
+
if (detectTruncatedDescription(description) === null)
|
|
212
|
+
return description;
|
|
213
|
+
const trimmed = description.trim();
|
|
214
|
+
// (a) trim-to-last-complete-clause.
|
|
215
|
+
// First, try the whole string with its trailing fragment stripped.
|
|
216
|
+
const stripped = stripTrailingTruncationFragment(trimmed);
|
|
217
|
+
if (isUsableClause(stripped)) {
|
|
218
|
+
return ensureTerminalPunctuation(stripped);
|
|
219
|
+
}
|
|
220
|
+
// If the description has multiple sentences, the truncation is in the last
|
|
221
|
+
// one — fall back to the longest leading run of complete sentences.
|
|
222
|
+
const sentences = splitSentences(trimmed);
|
|
223
|
+
if (sentences.length > 1) {
|
|
224
|
+
for (let take = sentences.length - 1; take >= 1; take--) {
|
|
225
|
+
const prefix = sentences.slice(0, take).join(" ").trim();
|
|
226
|
+
const cleaned = stripTrailingTruncationFragment(prefix);
|
|
227
|
+
if (isUsableClause(cleaned))
|
|
228
|
+
return ensureTerminalPunctuation(cleaned);
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
// (b) swap-in first clean, complete body sentence.
|
|
232
|
+
if (typeof body === "string" && body.trim().length > 0) {
|
|
233
|
+
const bodyText = body
|
|
234
|
+
.replace(/^---\r?\n[\s\S]*?\r?\n---\r?\n?/, "") // drop any frontmatter
|
|
235
|
+
.replace(/```[\s\S]*?```/g, " ") // drop fenced code
|
|
236
|
+
.replace(/`[^`]*`/g, " "); // drop inline code spans
|
|
237
|
+
for (const rawLine of bodyText.split(/\n/)) {
|
|
238
|
+
const line = rawLine
|
|
239
|
+
.replace(/\*\*([^*]+)\*\*/g, "$1")
|
|
240
|
+
.replace(/\*([^*]+)\*/g, "$1")
|
|
241
|
+
.replace(/^[#*\->_\s]+/, "")
|
|
242
|
+
.trim();
|
|
243
|
+
if (!line)
|
|
244
|
+
continue;
|
|
245
|
+
if (/^[a-z_]+:\s/i.test(line))
|
|
246
|
+
continue; // skip yaml-ish leak lines
|
|
247
|
+
for (const sentence of splitSentences(line)) {
|
|
248
|
+
if (isUsableClause(sentence))
|
|
249
|
+
return ensureTerminalPunctuation(sentence);
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
// (c) fallback: return original unchanged — validation rejects as before.
|
|
254
|
+
return description;
|
|
255
|
+
}
|
package/dist/core/time.js
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
* interpret the same set of formats (ISO-8601, epoch ms, plain date strings)
|
|
9
9
|
* consistently without private re-implementations drifting apart.
|
|
10
10
|
*/
|
|
11
|
-
import { UsageError } from "./errors";
|
|
11
|
+
import { UsageError } from "./errors.js";
|
|
12
12
|
// ── Since-flag parsing ───────────────────────────────────────────────────────
|
|
13
13
|
/**
|
|
14
14
|
* Parse a user-supplied `--since` value and return an ISO-8601 timestamp
|