akm-cli 0.9.17-alpha.2 → 0.9.17-alpha.4
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 +756 -0
- package/dist/akm +94 -196
- package/dist/cli/shared.js +6 -2
- package/dist/cli.js +22 -9
- package/dist/commands/agent/agent-dispatch.js +1 -1
- package/dist/commands/command/command-execution.js +24 -62
- package/dist/commands/feedback-cli.js +0 -1
- package/dist/commands/health/accept-rate.js +2 -2
- package/dist/commands/health/checks.js +30 -75
- package/dist/commands/health/config-skew.js +38 -0
- package/dist/commands/health/egress.js +54 -0
- package/dist/commands/health/html-report.js +0 -38
- package/dist/commands/health/improve-metrics.js +123 -562
- package/dist/commands/health/plugin-staleness.js +53 -3
- package/dist/commands/health/renderers.js +12 -4
- package/dist/commands/health/report-view-model.js +11 -106
- package/dist/commands/health/types-improve.js +4 -19
- package/dist/commands/health/windows.js +64 -73
- package/dist/commands/health.js +122 -143
- package/dist/commands/improve/consolidate/chunking.js +25 -100
- package/dist/commands/improve/consolidate/sanitize.js +54 -149
- package/dist/commands/improve/consolidate.js +538 -1075
- package/dist/commands/improve/content-hash.js +16 -24
- package/dist/commands/improve/distill/content-repair.js +18 -100
- package/dist/commands/improve/distill-guards.js +20 -81
- package/dist/commands/improve/distill-promotion-policy.js +23 -243
- package/dist/commands/improve/distill.js +608 -1075
- package/dist/commands/improve/eligibility.js +126 -400
- package/dist/commands/improve/execution.js +3 -5
- package/dist/commands/improve/extract.js +487 -1046
- package/dist/commands/improve/feedback-valence.js +0 -25
- package/dist/commands/improve/improve-cli.js +29 -166
- package/dist/commands/improve/improve-result-file.js +10 -66
- package/dist/commands/improve/improve-strategies.js +12 -7
- package/dist/commands/improve/improve-usage-report.js +18 -64
- package/dist/commands/improve/improve.js +443 -1063
- package/dist/commands/improve/ledger.js +114 -0
- package/dist/commands/improve/locks.js +2 -8
- package/dist/commands/improve/loop-stages.js +459 -1172
- package/dist/commands/improve/memory/derived-ref.js +12 -77
- package/dist/commands/improve/memory/memory-belief.js +14 -118
- package/dist/commands/improve/memory/memory-improve.js +4 -3
- package/dist/commands/improve/outcome-loop.js +28 -156
- package/dist/commands/improve/planner.js +5 -10
- package/dist/commands/improve/preparation.js +851 -2339
- package/dist/commands/improve/proactive-maintenance.js +34 -101
- package/dist/commands/improve/reflect-noise.js +104 -280
- package/dist/commands/improve/reflect.js +621 -1367
- package/dist/commands/improve/salience.js +46 -232
- package/dist/commands/improve/session-asset.js +19 -100
- package/dist/commands/improve/stage.js +323 -0
- package/dist/commands/proposal/drain.js +251 -644
- package/dist/commands/proposal/proposal-cli.js +3 -18
- package/dist/commands/proposal/proposal-types.js +20 -41
- package/dist/commands/proposal/proposal.js +1 -2
- package/dist/commands/proposal/propose.js +134 -160
- package/dist/commands/proposal/repository.js +502 -1487
- package/dist/commands/proposal/validators/proposal-quality-validators.js +71 -174
- package/dist/commands/proposal/validators/proposal-validators.js +1 -1
- package/dist/commands/proposal/validators/proposals.js +13 -89
- package/dist/commands/read/curate.js +63 -413
- package/dist/commands/read/search-cli.js +16 -33
- package/dist/commands/read/search.js +17 -23
- package/dist/commands/read/show.js +2 -13
- package/dist/commands/sources/bundle-cli.js +25 -2
- package/dist/commands/sources/bundle-config-ops.js +7 -0
- package/dist/commands/sources/dangerous-env-audit.js +1 -2
- package/dist/commands/sources/info.js +2 -11
- package/dist/commands/sources/installed-stashes.js +197 -746
- package/dist/commands/sources/schema-repair.js +98 -129
- package/dist/commands/sources/source-add.js +62 -12
- package/dist/commands/sources/stash-cli.js +1 -1
- package/dist/commands/tasks/explain.js +10 -13
- package/dist/commands/tasks/tasks-cli.js +9 -8
- package/dist/commands/tasks/tasks.js +326 -930
- package/dist/commands/tasks/validate.js +42 -21
- package/dist/commands/workflow/plan.js +22 -29
- package/dist/commands/workflow-cli.js +4 -4
- package/dist/core/adapter/adapters/akm-adapter.js +0 -1
- package/dist/core/adapter/adapters/akm-lint.js +2 -3
- package/dist/core/adapter/adapters/akm-metadata.js +11 -12
- package/dist/core/adapter/adapters/akm-workflow-adapter.js +1 -1
- package/dist/core/adapter/execution-source.js +17 -29
- package/dist/core/asset/resolve-ref.js +1 -1
- package/dist/core/bundle-id.js +42 -5
- package/dist/core/bundle-rename.js +291 -0
- package/dist/core/config/config-io.js +1 -2
- package/dist/core/config/config-schema.js +1 -33
- package/dist/core/config/config-walker.js +1 -1
- package/dist/core/config/config.js +163 -68
- package/dist/core/config/legacy-source-shape-shim.js +38 -9
- package/dist/core/config/schema/embedding.js +20 -5
- package/dist/core/config/schema/engines.js +5 -0
- package/dist/core/config/schema/execution.js +1 -1
- package/dist/core/config/schema/experimental.js +1 -1
- package/dist/core/config/schema/improve-processes.js +21 -95
- package/dist/core/config/schema/improve.js +4 -42
- package/dist/core/config/schema/scheduler.js +12 -12
- package/dist/core/config/schema/search.js +6 -22
- package/dist/core/env-secret-ref.js +0 -1
- package/dist/core/errors.js +8 -9
- package/dist/core/file-lock.js +76 -173
- package/dist/core/logs-db.js +2 -2
- package/dist/core/paths.js +0 -27
- package/dist/core/redaction.js +109 -2
- package/dist/core/run-lock.js +2 -5
- package/dist/core/spawn-env.js +1 -1
- package/dist/core/state/migrations.js +108 -61
- package/dist/core/state-db-scope.js +2 -4
- package/dist/core/state-db.js +126 -692
- package/dist/core/type-presentation.js +1 -9
- package/dist/core/write-source.js +293 -1012
- package/dist/execution/input-contract.js +1 -1
- package/dist/execution/resolved-request.js +135 -689
- package/dist/execution/source.js +63 -257
- package/dist/execution/target-ref.js +1 -1
- package/dist/indexer/bundle-identity-guard.js +2 -2
- package/dist/indexer/db/graph-db.js +106 -46
- package/dist/indexer/ensure-index.js +44 -85
- package/dist/indexer/graph/graph-extraction.js +340 -562
- package/dist/indexer/graph/graph-related.js +130 -0
- package/dist/indexer/index-rebuild-lock.js +3 -11
- package/dist/indexer/index-writer-lock.js +8 -17
- package/dist/indexer/index-written-assets.js +139 -151
- package/dist/indexer/indexer.js +524 -846
- package/dist/indexer/materialize-embeddings.js +60 -397
- package/dist/indexer/passes/memory-inference.js +81 -90
- package/dist/indexer/passes/metadata.js +132 -200
- package/dist/indexer/read-preflight.js +0 -7
- package/dist/indexer/scan/doc-to-entry.js +1 -3
- package/dist/indexer/scan/drain-dir.js +1 -1
- package/dist/indexer/search/db-search.js +181 -590
- package/dist/indexer/search/fts-query.js +30 -41
- package/dist/indexer/search/ranking.js +28 -154
- package/dist/indexer/search/search-attribution.js +12 -32
- package/dist/indexer/search/search-fields.js +11 -15
- package/dist/indexer/search/search-hit-enrichers.js +54 -85
- package/dist/indexer/search/search-source.js +1 -4
- package/dist/indexer/usage/usage-events.js +2 -7
- package/dist/integrations/agent/engine-fallback.js +23 -40
- package/dist/integrations/agent/engine-resolution.js +93 -183
- package/dist/integrations/agent/execution.js +507 -0
- package/dist/integrations/agent/model-map.js +28 -156
- package/dist/integrations/agent/request-lowering.js +66 -141
- package/dist/integrations/agent/runner-dispatch.js +143 -321
- package/dist/integrations/agent/runner.js +54 -14
- package/dist/integrations/lockfile.js +53 -101
- package/dist/llm/embedders/deterministic.js +2 -3
- package/dist/llm/embedders/profile.js +71 -0
- package/dist/llm/embedders/remote.js +10 -15
- package/dist/llm/graph-extract.js +3 -12
- package/dist/llm/index-passes.js +3 -5
- package/dist/llm/memory-infer.js +1 -2
- package/dist/llm/metadata-enhance.js +1 -2
- package/dist/llm/structured-call.js +5 -24
- package/dist/output/generic-render.js +23 -11
- package/dist/output/html-render.js +13 -10
- package/dist/output/render-registry.js +3 -32
- package/dist/output/shapes/helpers.js +2 -34
- package/dist/output/shapes/passthrough.js +1 -9
- package/dist/{indexer/search/ranking-types.js → output/text/bundle-rename.js} +4 -1
- package/dist/output/text/command-format.js +60 -23
- package/dist/output/text/helpers.js +1 -1
- package/dist/output/text/migrate.js +5 -14
- package/dist/output/text/proposal-format.js +1 -2
- package/dist/output/text/workflow-format.js +0 -32
- package/dist/output/text.js +2 -0
- package/dist/registry/factory.js +4 -19
- package/dist/registry/network.js +66 -220
- package/dist/registry/providers/index.js +0 -2
- package/dist/registry/providers/skills-sh.js +3 -14
- package/dist/registry/providers/static-index.js +24 -26
- package/dist/registry/resolve.js +55 -131
- package/dist/scripts/akm-migrate-node.js +43937 -93313
- package/dist/scripts/akm-migrate.js +43697 -93071
- package/dist/setup/registry-stash-loader.js +4 -13
- package/dist/setup/semantic-assets.js +3 -44
- package/dist/setup/setup.js +1 -1
- package/dist/setup/steps/tasks.js +25 -15
- package/dist/sources/provider-factory.js +17 -18
- package/dist/sources/providers/filesystem.js +2 -3
- package/dist/sources/providers/git-install.js +7 -1
- package/dist/sources/providers/git-provider.js +0 -3
- package/dist/sources/providers/git-stash.js +0 -17
- package/dist/sources/providers/npm.js +2 -4
- package/dist/sources/providers/provider-utils.js +5 -10
- package/dist/sources/providers/website.js +0 -2
- package/dist/sources/snapshot-fetchers/website-ingest.js +1 -1
- package/dist/sources/website-url.js +2 -2
- package/dist/storage/database.js +9 -35
- package/dist/storage/repositories/improve-ledger-repository.js +168 -0
- package/dist/storage/repositories/index-connection.js +34 -70
- package/dist/storage/repositories/index-entries-repository.js +69 -111
- package/dist/storage/repositories/index-entry-mapper.js +1 -2
- package/dist/storage/repositories/index-entry-schema.js +83 -269
- package/dist/storage/repositories/index-fts-repository.js +86 -256
- package/dist/storage/repositories/index-llm-cache-repository.js +17 -0
- package/dist/storage/repositories/index-meta-repository.js +6 -4
- package/dist/storage/repositories/index-schema.js +192 -220
- package/dist/storage/repositories/index-utility-repository.js +8 -29
- package/dist/storage/repositories/index-vec-repository.js +133 -414
- package/dist/storage/repositories/outcome-repository.js +2 -1
- package/dist/storage/repositories/proposals-repository.js +35 -0
- package/dist/storage/repositories/registry-index-cache-repository.js +100 -0
- package/dist/storage/repositories/task-history-repository.js +26 -4
- package/dist/storage/repositories/workflow-runs-repository.js +53 -244
- package/dist/storage/sqlite-migrations.js +136 -0
- package/dist/storage/sqlite-pragmas.js +11 -9
- package/dist/storage/sqlite-transaction.js +170 -0
- package/dist/storage/state-db-integrity.js +34 -27
- package/dist/tasks/activation-config.js +134 -62
- package/dist/tasks/backends/cron.js +129 -277
- package/dist/tasks/backends/exec-utils.js +2 -5
- package/dist/tasks/backends/launchd.js +125 -745
- package/dist/tasks/backends/schtasks.js +101 -620
- package/dist/tasks/prepare/prepare-support.js +5 -15
- package/dist/tasks/prepare/prepare.js +0 -2
- package/dist/tasks/resolve-akm-bin.js +20 -79
- package/dist/tasks/run/attempt-lifecycle.js +0 -1
- package/dist/tasks/scheduler-binding.js +18 -238
- package/dist/tasks/scheduler-invocation.js +52 -52
- package/dist/tasks/scheduler-lock.js +53 -0
- package/dist/tasks/scheduler-sync.js +363 -679
- package/dist/tasks/source/parse-task-source.js +160 -10
- package/dist/tasks/source/task-source-v3-frozen.js +3 -4
- package/dist/tasks/source/task-to-v4.js +2 -2
- package/dist/workflows/authoring/authoring.js +3 -12
- package/dist/workflows/compile.js +211 -0
- package/dist/workflows/concurrency-policy.js +13 -74
- package/dist/workflows/exec/child-invocation.js +3 -17
- package/dist/workflows/exec/child-workflow.js +32 -141
- package/dist/workflows/exec/dispatch-redaction.js +13 -53
- package/dist/workflows/exec/environment.js +98 -0
- package/dist/workflows/exec/exec-unit.js +33 -140
- package/dist/workflows/exec/frozen-judge.js +7 -59
- package/dist/workflows/exec/native-executor.js +82 -341
- package/dist/workflows/exec/param-secrets.js +29 -47
- package/dist/workflows/exec/run-workflow.js +154 -387
- package/dist/workflows/exec/scheduler.js +9 -36
- package/dist/workflows/exec/step-work.js +127 -430
- package/dist/workflows/exec/unit-dispatch.js +11 -63
- package/dist/workflows/exec/unit-writer.js +8 -52
- package/dist/workflows/exec/worktree.js +39 -273
- package/dist/workflows/freeze/child-output-references.js +4 -15
- package/dist/workflows/freeze/environment.js +99 -92
- package/dist/workflows/freeze/freeze.js +172 -0
- package/dist/workflows/freeze/step-values.js +19 -21
- package/dist/workflows/freeze/targets/child-workflow.js +23 -92
- package/dist/workflows/freeze/targets/command.js +10 -33
- package/dist/workflows/freeze/targets/script.js +5 -12
- package/dist/workflows/freeze/targets/shell.js +3 -6
- package/dist/workflows/freeze/targets/task.js +25 -80
- package/dist/workflows/freeze/task-bindings.js +20 -67
- package/dist/workflows/{source-ir/github-yaml.js → github-yaml.js} +88 -206
- package/dist/workflows/ir/params.js +6 -51
- package/dist/workflows/ir/plan-hash.js +2 -34
- package/dist/workflows/parser.js +140 -43
- package/dist/{commands/improve/consolidate/types.js → workflows/plan.js} +2 -1
- package/dist/workflows/renderer.js +36 -69
- package/dist/workflows/resource-limits.js +12 -120
- package/dist/workflows/runtime/agent-identity.js +8 -40
- package/dist/workflows/runtime/run-outputs.js +3 -6
- package/dist/workflows/runtime/run-plan.js +316 -0
- package/dist/workflows/runtime/runs.js +48 -200
- package/dist/workflows/runtime/workflow-asset-loader.js +24 -57
- package/dist/workflows/{source-ir/semantics.js → source-semantics.js} +16 -20
- package/dist/workflows/validate-summary.js +2 -7
- package/docs/integration/bundling-akm.md +49 -42
- package/docs/migration/README.md +1 -0
- package/docs/migration/release-notes/0.9.17.md +41 -0
- package/docs/migration/v0.9.1-to-v0.9.2.md +19 -7
- package/docs/reference/cli.md +182 -125
- package/docs/reference/configuration.md +49 -56
- package/docs/reference/data-and-telemetry.md +19 -20
- package/docs/reference/tasks.md +86 -38
- package/docs/reference/workflow-schema.md +14 -18
- package/docs/reference/workflows.md +6 -9
- package/package.json +1 -1
- package/schemas/akm-config.json +87 -406
- package/dist/commands/health/advisories.js +0 -150
- package/dist/commands/health/metrics.js +0 -329
- package/dist/commands/health/surfaces.js +0 -102
- package/dist/commands/improve/anti-collapse.js +0 -83
- package/dist/commands/improve/collapse-detector.js +0 -432
- package/dist/commands/improve/consolidate/eligibility.js +0 -48
- package/dist/commands/improve/consolidate/merge.js +0 -146
- package/dist/commands/improve/distill/promote-memory.js +0 -329
- package/dist/commands/improve/distill/quality-gate.js +0 -500
- package/dist/commands/improve/memory/memory-contradiction-detect.js +0 -291
- package/dist/commands/improve/proposal-envelope.js +0 -31
- package/dist/commands/improve/run-context.js +0 -123
- package/dist/commands/improve/shared.js +0 -21
- package/dist/commands/improve/source-identity.js +0 -28
- package/dist/commands/improve/triage.js +0 -96
- package/dist/commands/proposal/drain-policies.js +0 -151
- package/dist/commands/sources/update-transaction.js +0 -220
- package/dist/core/action-contributors.js +0 -28
- package/dist/core/config/config-version-shim.js +0 -101
- package/dist/core/config/retired-experimental-keys-shim.js +0 -62
- package/dist/core/fs-txn.js +0 -405
- package/dist/core/lexical-score.js +0 -25
- package/dist/core/maintenance-barrier.js +0 -167
- package/dist/execution/executable-identity.js +0 -105
- package/dist/execution/guarded-source.js +0 -427
- package/dist/indexer/graph/graph-boost.js +0 -427
- package/dist/indexer/graph/graph-dedup.js +0 -95
- package/dist/indexer/search/name-match.js +0 -35
- package/dist/indexer/search/ranking-contributors.js +0 -515
- package/dist/indexer/walk/project-context.js +0 -192
- package/dist/integrations/agent/execution-cascade.js +0 -566
- package/dist/integrations/agent/execution-definitions.js +0 -202
- package/dist/integrations/agent/execution-lowering.js +0 -841
- package/dist/integrations/agent/execution-preparation.js +0 -98
- package/dist/integrations/agent/inline-execution.js +0 -74
- package/dist/registry/create-provider-registry.js +0 -29
- package/dist/registry/pinned-request-helper.js +0 -247
- package/dist/registry/pinned-transport.js +0 -717
- package/dist/sources/providers/index.js +0 -14
- package/dist/storage/engines/sqlite-migrations.js +0 -271
- package/dist/storage/repositories/canaries-repository.js +0 -107
- package/dist/storage/repositories/embedding-salvage-repository.js +0 -184
- package/dist/storage/repositories/registry-cache.js +0 -113
- package/dist/tasks/scheduler-sync-preview.js +0 -52
- package/dist/workflows/freeze/resolve-steps.js +0 -86
- package/dist/workflows/freeze/source-freeze.js +0 -64
- package/dist/workflows/ir/compile.js +0 -321
- package/dist/workflows/ir/environment-v4.js +0 -330
- package/dist/workflows/ir/freeze-v4.js +0 -153
- package/dist/workflows/ir/schema-v4.js +0 -745
- package/dist/workflows/ir/schema.js +0 -354
- package/dist/workflows/program/schema.js +0 -78
- package/dist/workflows/runtime/checkin.js +0 -57
- package/dist/workflows/runtime/plan-classifier.js +0 -196
- package/dist/workflows/runtime/unit-checkin.js +0 -45
- package/dist/workflows/runtime/unit-phases.js +0 -20
- package/dist/workflows/schema.js +0 -4
- package/dist/workflows/source-ir/compile.js +0 -200
- package/dist/workflows/source-ir/program.js +0 -50
- package/dist/workflows/source-ir/result.js +0 -26
- package/dist/workflows/source-ir/schema.js +0 -786
- package/dist/workflows/source-ir/triggers.js +0 -79
- package/dist/workflows/source-ir/uses.js +0 -40
- package/dist/workflows/validator.js +0 -60
|
@@ -2,77 +2,27 @@
|
|
|
2
2
|
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
3
3
|
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
|
4
4
|
/**
|
|
5
|
-
*
|
|
6
|
-
* (`distill`, `consolidate`, `reflect`) and by the `proposal accept` gate.
|
|
5
|
+
* Content-quality validators for the improve stages and `proposal accept`.
|
|
7
6
|
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
* ### Problem with the original fixed-ratio gate
|
|
16
|
-
*
|
|
17
|
-
* - Small sources (~420 bytes, 16th pct): the 200% expansion ceiling fires at
|
|
18
|
-
* only 840 bytes proposed — one good paragraph. Hair-trigger for a terse
|
|
19
|
-
* reference note.
|
|
20
|
-
* - Large sources (~7KB, 75th pct): 200% ceiling = 14KB; reasonable, but a hard
|
|
21
|
-
* cap prevents runaway expansion from LLM hallucinations.
|
|
22
|
-
*
|
|
23
|
-
* ### Blended-bound formula
|
|
24
|
-
*
|
|
25
|
-
* Shrinkage floor (accept if proposed >= lower):
|
|
26
|
-
* lower = max(REFLECT_SHRINK_RATIO_MIN * sourceLen, REFLECT_ABSOLUTE_FLOOR_BYTES)
|
|
27
|
-
* → For tiny sources (sourceLen < 300), the absolute floor dominates so a
|
|
28
|
-
* genuinely tightened note still passes.
|
|
29
|
-
* → For large sources (>1KB), the ratio floor dominates (50% of 7KB = 3.5KB).
|
|
30
|
-
*
|
|
31
|
-
* Expansion ceiling (accept if proposed <= upper):
|
|
32
|
-
* upper = max(REFLECT_EXPAND_RATIO_MAX * sourceLen, REFLECT_ABSOLUTE_CEILING_BYTES)
|
|
33
|
-
* …but always capped at REFLECT_ABSOLUTE_MAX_BYTES.
|
|
34
|
-
* → For small sources (≤778 bytes, p25), the absolute ceiling (2000 bytes)
|
|
35
|
-
* dominates — one substantive paragraph is always acceptable.
|
|
36
|
-
* → For medium/large sources (>1KB), the ratio ceiling dominates.
|
|
37
|
-
* → Any proposal exceeding 25000 bytes is always rejected regardless of ratio.
|
|
38
|
-
*
|
|
39
|
-
* ### Constant calibration rationale
|
|
40
|
-
*
|
|
41
|
-
* REFLECT_ABSOLUTE_FLOOR_BYTES = 150
|
|
42
|
-
* Half of p10 (371) ≈ 185; we set 150 so even very aggressive condensation
|
|
43
|
-
* of a seed note is allowed down to roughly a two-sentence summary.
|
|
44
|
-
*
|
|
45
|
-
* REFLECT_ABSOLUTE_CEILING_BYTES = 2500
|
|
46
|
-
* Raised from 2000 (2026-05-22): small-source rejections at 248–281% on
|
|
47
|
-
* 900–953 byte assets were borderline false positives. 2500 gives a short
|
|
48
|
-
* lesson or command ~1.5KB of room to grow before the absolute kicks in.
|
|
49
|
-
*
|
|
50
|
-
* REFLECT_ABSOLUTE_MAX_BYTES = 25000
|
|
51
|
-
* Below p99 (43463). Catches genuine LLM runaway (whole-chapter insertions)
|
|
52
|
-
* without blocking legitimate large rewrites of large sources.
|
|
53
|
-
*
|
|
54
|
-
* REFLECT_EXPAND_RATIO_MAX = 2.5
|
|
55
|
-
* Raised from 2.0 (2026-05-22): 2× was too tight for dense short assets
|
|
56
|
-
* (lessons, commands) that have legitimate room to grow. 2.5× resolves
|
|
57
|
-
* 248% expansion on a 900-byte lesson while still catching 281%+ on ~1KB
|
|
58
|
-
* assets where the absolute ceiling takes over.
|
|
7
|
+
* The reflect size gate bounds a rewrite's body against its source's:
|
|
8
|
+
* shrinking below max(50% of the source, 150 bytes) suggests deleted content,
|
|
9
|
+
* growing past min(max(250% of the source, 2500 bytes), 25000 bytes) suggests
|
|
10
|
+
* speculation. The absolute bounds keep small assets (a p25 source is ~780
|
|
11
|
+
* bytes) from tripping on one good paragraph, and 25000 (below p99) still
|
|
12
|
+
* catches runaway expansion. Sources under 200 bytes are too noisy to judge.
|
|
59
13
|
*/
|
|
60
|
-
// ── Reflect-size guard ───────────────────────────────────────────────────────
|
|
61
14
|
import { parseFrontmatter } from "../../../core/asset/frontmatter.js";
|
|
62
15
|
import { parseRefInput } from "../../../core/asset/resolve-ref.js";
|
|
63
16
|
import { DESCRIPTION_MAX_CHARS, DESCRIPTION_MIN_CHARS, WHEN_TO_USE_MAX_CHARS, WHEN_TO_USE_MIN_CHARS, } from "../../../core/authoring-rules.js";
|
|
64
17
|
import { containsRedactedContent, containsReflectPromptScaffolding, REDACTED_CONTENT_MARKER, REFLECT_AVOID_PATTERNS_HEADING, } from "../../../core/content-safety.js";
|
|
65
18
|
import { proposalContent } from "../../../core/file-change.js";
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
*/
|
|
19
|
+
import { detectTruncatedDescription, TRUNCATION_TRAILING_WORDS } from "../../../core/text-truncation.js";
|
|
20
|
+
import { REFLECT_TRUNCATION_MARKER } from "../../../integrations/agent/prompts.js";
|
|
21
|
+
import { splitFrontmatter } from "../../improve/reflect-noise.js";
|
|
22
|
+
/** The asset name a ref names, lower-cased — what the "just restates the ref" checks compare. */
|
|
71
23
|
function refNameTail(inputRef) {
|
|
72
24
|
return parseRefInput(inputRef).name.toLowerCase();
|
|
73
25
|
}
|
|
74
|
-
import { detectTruncatedDescription, TRUNCATION_TRAILING_WORDS } from "../../../core/text-truncation.js";
|
|
75
|
-
import { REFLECT_TRUNCATION_MARKER } from "../../../integrations/agent/prompts.js";
|
|
76
26
|
// ── Description / when_to_use shape ─────────────────────────────────────────
|
|
77
27
|
export const HEADING_FRAGMENT_PATTERNS = [
|
|
78
28
|
/^for example\b/i,
|
|
@@ -164,9 +114,8 @@ export function detectDoubleFrontmatter(content) {
|
|
|
164
114
|
kind: "double-frontmatter-fence",
|
|
165
115
|
message: `Content contains ${fenceLines.length} \`---\` fence lines; assets with frontmatter must have exactly 2 (one open, one close).`,
|
|
166
116
|
};
|
|
167
|
-
const
|
|
168
|
-
|
|
169
|
-
.split(/\r?\n/)
|
|
117
|
+
const pseudoLine = splitFrontmatter(content)
|
|
118
|
+
.body.split(/\r?\n/)
|
|
170
119
|
.find((l) => /^\s*(\*\*|__)?\s*(description|when_to_use)\s*(\*\*|__)?\s*:/i.test(l));
|
|
171
120
|
if (pseudoLine)
|
|
172
121
|
return {
|
|
@@ -191,50 +140,13 @@ export function hasSupersededStatus(frontmatter) {
|
|
|
191
140
|
export function hasHotCaptureMode(frontmatter) {
|
|
192
141
|
return frontmatter?.captureMode === "hot";
|
|
193
142
|
}
|
|
194
|
-
// ── Consolidate merge size gate ──────────────────────────────────────────────
|
|
195
|
-
// ── Reflect size gate ────────────────────────────────────────────────────────
|
|
196
|
-
/** Ratio lower-bound: proposed body must be at least this fraction of source. */
|
|
197
143
|
export const REFLECT_SHRINK_RATIO_MIN = 0.5;
|
|
198
|
-
/** Ratio upper-bound: proposed body must not exceed this fraction of source. */
|
|
199
144
|
export const REFLECT_EXPAND_RATIO_MAX = 2.5;
|
|
200
|
-
/**
|
|
201
|
-
* Below this byte count, ratio checks are too noisy — skip them entirely.
|
|
202
|
-
* Unchanged from the original gate.
|
|
203
|
-
*/
|
|
204
145
|
export const REFLECT_SIZE_GUARD_MIN_BYTES = 200;
|
|
205
|
-
/**
|
|
206
|
-
* Absolute shrinkage floor (bytes). Even if `ratio * sourceLen` is lower, a
|
|
207
|
-
* proposed body of at least this many bytes is always accepted on the shrinkage
|
|
208
|
-
* side. Protects against false positives when the source is small (<300 bytes).
|
|
209
|
-
*/
|
|
210
146
|
export const REFLECT_ABSOLUTE_FLOOR_BYTES = 150;
|
|
211
|
-
/**
|
|
212
|
-
* Absolute expansion ceiling (bytes). Even if `ratio * sourceLen` is lower, a
|
|
213
|
-
* proposed body up to this many bytes is always accepted on the expansion side.
|
|
214
|
-
* Protects against false positives when the source is small (≤778 bytes, p25).
|
|
215
|
-
*/
|
|
216
147
|
export const REFLECT_ABSOLUTE_CEILING_BYTES = 2500;
|
|
217
|
-
/**
|
|
218
|
-
* Hard expansion cap (bytes). Regardless of ratio, a proposed body exceeding
|
|
219
|
-
* this limit is always rejected. Guards against runaway LLM hallucinations on
|
|
220
|
-
* large sources.
|
|
221
|
-
*/
|
|
222
148
|
export const REFLECT_ABSOLUTE_MAX_BYTES = 25000;
|
|
223
|
-
/**
|
|
224
|
-
* Calibrated size check: compare proposed body length against source body
|
|
225
|
-
* length using a blended-bound formula.
|
|
226
|
-
*
|
|
227
|
-
* **Shrinkage** — accept if:
|
|
228
|
-
* `proposedLen >= max(REFLECT_SHRINK_RATIO_MIN * sourceLen, REFLECT_ABSOLUTE_FLOOR_BYTES)`
|
|
229
|
-
*
|
|
230
|
-
* **Expansion** — accept if:
|
|
231
|
-
* `proposedLen <= min(max(REFLECT_EXPAND_RATIO_MAX * sourceLen, REFLECT_ABSOLUTE_CEILING_BYTES), REFLECT_ABSOLUTE_MAX_BYTES)`
|
|
232
|
-
*
|
|
233
|
-
* Returns `{ ok: true }` when:
|
|
234
|
-
* - `sourceBody` is absent or `undefined`
|
|
235
|
-
* - source body is shorter than {@link REFLECT_SIZE_GUARD_MIN_BYTES}
|
|
236
|
-
* - the proposed length is within the blended bounds
|
|
237
|
-
*/
|
|
149
|
+
/** The reflect size gate (see the module note); no source or a tiny one passes. */
|
|
238
150
|
export function checkReflectSize(sourceBody, proposedBody) {
|
|
239
151
|
if (typeof sourceBody !== "string")
|
|
240
152
|
return { ok: true };
|
|
@@ -243,19 +155,14 @@ export function checkReflectSize(sourceBody, proposedBody) {
|
|
|
243
155
|
return { ok: true };
|
|
244
156
|
const proposedLen = proposedBody.trim().length;
|
|
245
157
|
const ratio = proposedLen / sourceLen;
|
|
246
|
-
|
|
247
|
-
const shrinkFloor = Math.max(REFLECT_SHRINK_RATIO_MIN * sourceLen, REFLECT_ABSOLUTE_FLOOR_BYTES);
|
|
248
|
-
if (proposedLen < shrinkFloor) {
|
|
158
|
+
if (proposedLen < Math.max(REFLECT_SHRINK_RATIO_MIN * sourceLen, REFLECT_ABSOLUTE_FLOOR_BYTES)) {
|
|
249
159
|
return { ok: false, code: "EXCESSIVE_SHRINKAGE", ratio };
|
|
250
160
|
}
|
|
251
|
-
// Expansion check: upper bound = min(max(ratio ceiling, absolute ceiling), hard cap)
|
|
252
161
|
const expandCeiling = Math.min(Math.max(REFLECT_EXPAND_RATIO_MAX * sourceLen, REFLECT_ABSOLUTE_CEILING_BYTES), REFLECT_ABSOLUTE_MAX_BYTES);
|
|
253
|
-
if (proposedLen > expandCeiling)
|
|
162
|
+
if (proposedLen > expandCeiling)
|
|
254
163
|
return { ok: false, code: "EXCESSIVE_EXPANSION", ratio };
|
|
255
|
-
}
|
|
256
164
|
return { ok: true };
|
|
257
165
|
}
|
|
258
|
-
// ── ProposalValidator entries (registered with proposal-validators.ts) ──────
|
|
259
166
|
const descriptionQualityValidator = {
|
|
260
167
|
name: "description-quality",
|
|
261
168
|
appliesTo(_proposal, ctx) {
|
|
@@ -282,6 +189,46 @@ const descriptionQualityValidator = {
|
|
|
282
189
|
];
|
|
283
190
|
},
|
|
284
191
|
};
|
|
192
|
+
/**
|
|
193
|
+
* The lesson checks distill and accept share: a valid description and
|
|
194
|
+
* when_to_use that differ, and no pseudo-frontmatter in the body. `text`
|
|
195
|
+
* continues a caller-specific subject.
|
|
196
|
+
*/
|
|
197
|
+
export function lessonQualityIssues(fm, content, inputRef) {
|
|
198
|
+
const issues = [];
|
|
199
|
+
const descCheck = isValidDescription(fm.description, inputRef);
|
|
200
|
+
if (!descCheck.ok) {
|
|
201
|
+
issues.push({
|
|
202
|
+
kind: "invalid-description",
|
|
203
|
+
field: "description",
|
|
204
|
+
text: ` has an invalid description: ${descCheck.reason}.`,
|
|
205
|
+
...(descCheck.severity ? { severity: descCheck.severity } : {}),
|
|
206
|
+
});
|
|
207
|
+
}
|
|
208
|
+
const wtuCheck = isValidWhenToUse(fm.when_to_use, inputRef);
|
|
209
|
+
if (!wtuCheck.ok) {
|
|
210
|
+
issues.push({
|
|
211
|
+
kind: "invalid-when_to_use",
|
|
212
|
+
field: "when_to_use",
|
|
213
|
+
text: ` has an invalid when_to_use: ${wtuCheck.reason}.`,
|
|
214
|
+
});
|
|
215
|
+
}
|
|
216
|
+
if (descCheck.ok &&
|
|
217
|
+
wtuCheck.ok &&
|
|
218
|
+
typeof fm.description === "string" &&
|
|
219
|
+
typeof fm.when_to_use === "string" &&
|
|
220
|
+
fm.description.trim().toLowerCase() === fm.when_to_use.trim().toLowerCase()) {
|
|
221
|
+
issues.push({
|
|
222
|
+
kind: "description-equals-when_to_use",
|
|
223
|
+
field: "description",
|
|
224
|
+
text: " has identical description and when_to_use.",
|
|
225
|
+
});
|
|
226
|
+
}
|
|
227
|
+
const dfm = detectDoubleFrontmatter(content);
|
|
228
|
+
if (dfm)
|
|
229
|
+
issues.push({ kind: dfm.kind, field: "body", text: `: ${dfm.message}` });
|
|
230
|
+
return issues;
|
|
231
|
+
}
|
|
285
232
|
const lessonContentQualityValidator = {
|
|
286
233
|
name: "lesson-content-quality",
|
|
287
234
|
appliesTo(_proposal, ctx) {
|
|
@@ -297,34 +244,11 @@ const lessonContentQualityValidator = {
|
|
|
297
244
|
catch {
|
|
298
245
|
return [];
|
|
299
246
|
}
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
message: `Lesson proposal ${proposal.id} (${proposal.ref}) has an invalid description: ${descCheck.reason}.`,
|
|
306
|
-
...(descCheck.severity ? { severity: descCheck.severity } : {}),
|
|
307
|
-
});
|
|
308
|
-
const wtuCheck = isValidWhenToUse(fm.when_to_use, proposal.ref);
|
|
309
|
-
if (!wtuCheck.ok)
|
|
310
|
-
findings.push({
|
|
311
|
-
kind: "invalid-when_to_use",
|
|
312
|
-
message: `Lesson proposal ${proposal.id} (${proposal.ref}) has an invalid when_to_use: ${wtuCheck.reason}.`,
|
|
313
|
-
});
|
|
314
|
-
if (descCheck.ok &&
|
|
315
|
-
wtuCheck.ok &&
|
|
316
|
-
typeof fm.description === "string" &&
|
|
317
|
-
typeof fm.when_to_use === "string" &&
|
|
318
|
-
fm.description.trim().toLowerCase() === fm.when_to_use.trim().toLowerCase()) {
|
|
319
|
-
findings.push({
|
|
320
|
-
kind: "description-equals-when_to_use",
|
|
321
|
-
message: `Lesson proposal ${proposal.id} (${proposal.ref}) has identical description and when_to_use.`,
|
|
322
|
-
});
|
|
323
|
-
}
|
|
324
|
-
const dfm = detectDoubleFrontmatter(proposalContent(proposal));
|
|
325
|
-
if (dfm)
|
|
326
|
-
findings.push({ kind: dfm.kind, message: `Lesson proposal ${proposal.id} (${proposal.ref}): ${dfm.message}` });
|
|
327
|
-
return findings;
|
|
247
|
+
return lessonQualityIssues(fm, proposalContent(proposal), proposal.ref).map((issue) => ({
|
|
248
|
+
kind: issue.kind,
|
|
249
|
+
message: `Lesson proposal ${proposal.id} (${proposal.ref})${issue.text}`,
|
|
250
|
+
...(issue.severity ? { severity: issue.severity } : {}),
|
|
251
|
+
}));
|
|
328
252
|
},
|
|
329
253
|
};
|
|
330
254
|
const sourceNotSupersededValidator = {
|
|
@@ -344,18 +268,14 @@ const sourceNotSupersededValidator = {
|
|
|
344
268
|
return [];
|
|
345
269
|
},
|
|
346
270
|
};
|
|
347
|
-
/** Strip an opening frontmatter block (`---\n…\n---`) from `content`, returning the body. */
|
|
348
|
-
function stripFrontmatterBody(content) {
|
|
349
|
-
return content.replace(/^---\r?\n[\s\S]*?\r?\n---\r?\n?/, "");
|
|
350
|
-
}
|
|
351
271
|
const reflectSizeGuardValidator = {
|
|
352
272
|
name: "reflect-size-guard",
|
|
353
273
|
appliesTo(proposal, ctx) {
|
|
354
274
|
return proposal.source === "reflect" && typeof ctx.source?.content === "string";
|
|
355
275
|
},
|
|
356
276
|
validate(proposal, ctx) {
|
|
357
|
-
const sourceBody =
|
|
358
|
-
const proposedBody = typeof proposal.payload?.content === "string" ?
|
|
277
|
+
const sourceBody = splitFrontmatter(ctx.source?.content ?? "").body;
|
|
278
|
+
const proposedBody = typeof proposal.payload?.content === "string" ? splitFrontmatter(proposalContent(proposal)).body : "";
|
|
359
279
|
const outcome = checkReflectSize(sourceBody, proposedBody);
|
|
360
280
|
if (outcome.ok)
|
|
361
281
|
return [];
|
|
@@ -373,16 +293,10 @@ const reflectSizeGuardValidator = {
|
|
|
373
293
|
},
|
|
374
294
|
};
|
|
375
295
|
/**
|
|
376
|
-
*
|
|
377
|
-
*
|
|
378
|
-
*
|
|
379
|
-
*
|
|
380
|
-
* proposal can reach `proposal accept` / drain promotion without ever going
|
|
381
|
-
* through it (e.g. a defer that a human then accepts anyway, or a future
|
|
382
|
-
* reflect code path that mints proposals directly). A leaked marker replacing
|
|
383
|
-
* real asset content on disk is data loss, not a quality nit, so unlike the
|
|
384
|
-
* rest of this file's validators this one is NOT wrapped by {@link advisory}
|
|
385
|
-
* — it blocks acceptance the same way the generic/canonical validators do.
|
|
296
|
+
* A body still carrying {@link REFLECT_TRUNCATION_MARKER} (its source was too
|
|
297
|
+
* large to send in full) would overwrite the asset with an incomplete rewrite:
|
|
298
|
+
* data loss, so this blocks at accept even for a proposal that never passed
|
|
299
|
+
* reflect's own creation-time check (#952).
|
|
386
300
|
*/
|
|
387
301
|
const reflectTruncationMarkerValidator = {
|
|
388
302
|
name: "reflect-truncation-marker",
|
|
@@ -435,19 +349,9 @@ const reflectPromptScaffoldingValidator = {
|
|
|
435
349
|
},
|
|
436
350
|
};
|
|
437
351
|
/**
|
|
438
|
-
*
|
|
439
|
-
*
|
|
440
|
-
*
|
|
441
|
-
* heading, an odd backtick count, a body that grew more than the reflect
|
|
442
|
-
* ratio allows. They used to BLOCK `proposal accept`, which a human types
|
|
443
|
-
* after reading the diff, and the error told that human to "fix the proposal
|
|
444
|
-
* payload and try again" — but there is no `akm proposal edit` and `accept`
|
|
445
|
-
* takes no `--force`, so the only way out was hand-editing the proposals
|
|
446
|
-
* database. A blocking check whose remedy does not exist is not a check.
|
|
447
|
-
*
|
|
448
|
-
* Structural findings stay blocking: an empty body, an unparseable ref,
|
|
449
|
-
* malformed frontmatter and a broken workflow shape genuinely cannot be
|
|
450
|
-
* written, and they live in {@link defaultProposalValidators}.
|
|
352
|
+
* Prose-quality findings only advise: a human accepting after reading the diff
|
|
353
|
+
* has no way to edit the proposal, so blocking on them left no remedy.
|
|
354
|
+
* Structural findings (in the default validators) still block.
|
|
451
355
|
*/
|
|
452
356
|
function advisory(validator) {
|
|
453
357
|
return {
|
|
@@ -455,14 +359,7 @@ function advisory(validator) {
|
|
|
455
359
|
validate: (proposal, ctx) => validator.validate(proposal, ctx).map((finding) => ({ ...finding, severity: "warn" })),
|
|
456
360
|
};
|
|
457
361
|
}
|
|
458
|
-
/**
|
|
459
|
-
* Full set of quality validators in registration order. Appended onto
|
|
460
|
-
* {@link defaultProposalValidators} so they run inside `validateProposal` on
|
|
461
|
-
* `proposal accept` automatically. All prose-quality checks report without
|
|
462
|
-
* blocking (see {@link advisory}). The truncation-marker, redacted-content,
|
|
463
|
-
* and reflected-prompt-scaffolding validators block because they protect
|
|
464
|
-
* durable content rather than judging prose quality.
|
|
465
|
-
*/
|
|
362
|
+
/** The quality validators `validateProposal` runs; the last three protect durable content and block. */
|
|
466
363
|
export const defaultProposalQualityValidators = [
|
|
467
364
|
...[
|
|
468
365
|
descriptionQualityValidator,
|
|
@@ -6,7 +6,7 @@ import { parseRefInput } from "../../../core/asset/resolve-ref.js";
|
|
|
6
6
|
import { proposalContent } from "../../../core/file-change.js";
|
|
7
7
|
import { lintLessonContent } from "../../../core/lesson-lint.js";
|
|
8
8
|
import { parseTaskSource } from "../../../tasks/source/parse-task-source.js";
|
|
9
|
-
import { compileWorkflowSource } from "../../../workflows/
|
|
9
|
+
import { compileWorkflowSource } from "../../../workflows/compile.js";
|
|
10
10
|
import { defaultProposalQualityValidators } from "./proposal-quality-validators.js";
|
|
11
11
|
const genericProposalValidator = {
|
|
12
12
|
name: "generic-proposal-validator",
|
|
@@ -1,105 +1,29 @@
|
|
|
1
1
|
// This Source Code Form is subject to the terms of the Mozilla Public
|
|
2
2
|
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
3
3
|
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
|
4
|
-
/**
|
|
5
|
-
* Proposal validation and content repair.
|
|
6
|
-
*
|
|
7
|
-
* The proposal repository and domain service moved to `../repository.ts` (#578
|
|
8
|
-
* storage consolidation). This module keeps only the two proposal *validators* — {@link validateProposal} and
|
|
9
|
-
* {@link repairProposalContent}.
|
|
10
|
-
*/
|
|
4
|
+
/** Proposal validation and the one content repair applied before promotion. */
|
|
11
5
|
import { repairTruncatedDescription } from "../../../core/text-truncation.js";
|
|
6
|
+
import { splitFrontmatter } from "../../improve/reflect-noise.js";
|
|
12
7
|
import { runProposalValidators } from "./proposal-validators.js";
|
|
13
8
|
/**
|
|
14
|
-
* Validate a proposal
|
|
15
|
-
*
|
|
16
|
-
* fail-closed canonical registry run their existing parser or validator; types
|
|
17
|
-
* without one remain on generic validation.
|
|
9
|
+
* Validate a proposal before promotion: it must parse and carry a body, and a
|
|
10
|
+
* type with a canonical validator runs it.
|
|
18
11
|
*/
|
|
19
12
|
export function validateProposal(proposal) {
|
|
20
13
|
return runProposalValidators(proposal);
|
|
21
14
|
}
|
|
22
|
-
// ── Content repair ──────────────────────────────────────────────────────────
|
|
23
15
|
/**
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
*
|
|
29
|
-
* Repairs performed:
|
|
30
|
-
* 1. Apply {@link repairTruncatedDescription} to a truncated/hanging
|
|
31
|
-
* `description` field in the frontmatter.
|
|
32
|
-
*
|
|
33
|
-
* It deliberately does NOT delete body lines. Two earlier repairs dropped
|
|
34
|
-
* every body line that restated a frontmatter key and every `---` in a body
|
|
35
|
-
* with frontmatter. Both fired inside fenced code blocks, so any asset
|
|
36
|
-
* documenting frontmatter — a note about akm, Claude Code skills, Jekyll,
|
|
37
|
-
* Hugo — was silently gutted on `proposal accept`, and the repaired bytes
|
|
38
|
-
* were written back over the original in the proposals database. A repair
|
|
39
|
-
* that can destroy content is not a repair.
|
|
40
|
-
*
|
|
41
|
-
* Returns the repaired content string. When no repairs apply the input is
|
|
42
|
-
* returned byte-identical so callers can use strict equality to detect
|
|
43
|
-
* whether a repair actually happened.
|
|
44
|
-
*
|
|
45
|
-
* CRITICAL: This function is CONTENT-PRESERVING. Callers MUST re-validate the
|
|
46
|
-
* repaired output via {@link validateProposal} / {@link runProposalValidators}
|
|
47
|
-
* before promotion — a repair that makes things *worse* (or is simply
|
|
48
|
-
* insufficient) must be caught by the existing gate.
|
|
16
|
+
* Normalize line endings and complete a truncated frontmatter `description`
|
|
17
|
+
* (`repairTruncatedDescription`, with the body as context). Nothing else: an
|
|
18
|
+
* earlier repair that deleted body lines gutted any asset documenting
|
|
19
|
+
* frontmatter. Callers re-validate the result.
|
|
49
20
|
*/
|
|
50
21
|
export function repairProposalContent(content) {
|
|
51
22
|
if (typeof content !== "string" || content.trim() === "")
|
|
52
23
|
return content;
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
// Track whether we are inside the opening frontmatter block so we can
|
|
59
|
-
// leave it untouched and only repair the body.
|
|
60
|
-
let inFrontmatter = false;
|
|
61
|
-
// Frontmatter fence index tracking: first fence opens FM, second closes it.
|
|
62
|
-
let fmOpenSeen = false;
|
|
63
|
-
let fmCloseSeen = false;
|
|
64
|
-
const repairedLines = [];
|
|
65
|
-
for (const line of lines) {
|
|
66
|
-
const isFence = /^---\s*$/.test(line);
|
|
67
|
-
// Track frontmatter fences (first two `---` fences delimit the FM block).
|
|
68
|
-
if (isFence && !fmCloseSeen) {
|
|
69
|
-
if (!fmOpenSeen) {
|
|
70
|
-
fmOpenSeen = true;
|
|
71
|
-
inFrontmatter = true;
|
|
72
|
-
repairedLines.push(line);
|
|
73
|
-
continue;
|
|
74
|
-
}
|
|
75
|
-
if (inFrontmatter) {
|
|
76
|
-
fmCloseSeen = true;
|
|
77
|
-
inFrontmatter = false;
|
|
78
|
-
repairedLines.push(line);
|
|
79
|
-
continue;
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
// We are now in the body (past the frontmatter or no frontmatter).
|
|
83
|
-
if (inFrontmatter) {
|
|
84
|
-
// Still inside the frontmatter — keep as-is.
|
|
85
|
-
repairedLines.push(line);
|
|
86
|
-
continue;
|
|
87
|
-
}
|
|
88
|
-
repairedLines.push(line);
|
|
89
|
-
}
|
|
90
|
-
let repaired = repairedLines.join("\n");
|
|
91
|
-
// Repair 3: Apply repairTruncatedDescription to the description field.
|
|
92
|
-
// We operate on the raw text rather than re-parsing YAML to avoid
|
|
93
|
-
// reformatting unrelated frontmatter keys.
|
|
94
|
-
if (hasFrontmatter) {
|
|
95
|
-
// Extract the body text (after the second `---`) so we can pass it to
|
|
96
|
-
// repairTruncatedDescription as context for the swap-in heuristic.
|
|
97
|
-
const bodyMatch = repaired.match(/^---\r?\n[\s\S]*?\r?\n---\r?\n?([\s\S]*)$/);
|
|
98
|
-
const bodyText = bodyMatch?.[1] ?? "";
|
|
99
|
-
repaired = repaired.replace(/^(description:\s*)(.*?)(\r?\n)/m, (_match, prefix, rawDesc, nl) => {
|
|
100
|
-
const fixed = repairTruncatedDescription(rawDesc.trim(), bodyText);
|
|
101
|
-
return `${prefix}${fixed}${nl}`;
|
|
102
|
-
});
|
|
103
|
-
}
|
|
104
|
-
return repaired;
|
|
24
|
+
const repaired = content.replace(/\r\n/g, "\n");
|
|
25
|
+
const { fmText, body } = splitFrontmatter(repaired);
|
|
26
|
+
if (fmText === null)
|
|
27
|
+
return repaired;
|
|
28
|
+
return repaired.replace(/^(description:\s*)(.*?)(\r?\n)/m, (_match, prefix, rawDesc, nl) => `${prefix}${repairTruncatedDescription(rawDesc.trim(), body)}${nl}`);
|
|
105
29
|
}
|