akm-cli 0.9.16 → 0.9.17-alpha.10
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 +2101 -0
- package/STABILITY.md +11 -10
- package/dist/akm +124 -193
- package/dist/akm-migrate +38 -19
- package/dist/assets/hints/cli-hints-full.md +6 -7
- package/dist/assets/improve-strategies/catchup.json +0 -3
- package/dist/assets/improve-strategies/consolidate.json +0 -1
- package/dist/assets/improve-strategies/default.json +1 -2
- package/dist/assets/improve-strategies/proactive-maintenance.json +1 -2
- package/dist/assets/improve-strategies/quick.json +1 -2
- package/dist/assets/improve-strategies/reflect-distill.json +1 -2
- package/dist/assets/improve-strategies/thorough.json +0 -3
- package/dist/assets/prompts/consolidate-pair.md +20 -0
- package/dist/assets/prompts/consolidate-system.md +4 -11
- package/dist/assets/prompts/retrieval-relevance-judge.md +6 -0
- package/dist/assets/stash-skeleton/facts/conventions/backlinks.md +20 -20
- package/dist/assets/stash-skeleton/facts/conventions/domains.md +2 -2
- package/dist/assets/templates/html/health.html +3 -5
- package/dist/cli/retired-commands.js +1 -1
- package/dist/cli/shared.js +6 -2
- package/dist/cli/unknown-flags.js +24 -1
- package/dist/cli.js +68 -10
- 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 +6 -0
- package/dist/commands/health/archive-usage.js +92 -0
- package/dist/commands/health/checks.js +83 -74
- package/dist/commands/health/config-skew.js +38 -0
- package/dist/commands/health/data-dir-usage.js +25 -13
- package/dist/commands/health/egress.js +54 -0
- package/dist/commands/health/html-report.js +1 -42
- package/dist/commands/health/improve-metrics.js +136 -591
- package/dist/commands/health/md-report.js +1 -6
- 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 +14 -120
- package/dist/commands/health/types-improve.js +4 -19
- package/dist/commands/health/windows.js +64 -74
- package/dist/commands/health.js +145 -143
- package/dist/commands/improve/consolidate/chunking.js +26 -117
- package/dist/commands/improve/consolidate/continuity-check.js +137 -0
- package/dist/commands/improve/consolidate/pair-pass.js +791 -0
- package/dist/commands/improve/consolidate/sanitize.js +54 -149
- package/dist/commands/improve/consolidate.js +589 -1127
- 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 -1041
- package/dist/commands/improve/eligibility.js +126 -390
- package/dist/commands/improve/execution.js +8 -10
- package/dist/commands/improve/extract-prompt.js +1 -2
- package/dist/commands/improve/extract.js +487 -1046
- package/dist/commands/improve/feedback-valence.js +0 -25
- package/dist/commands/improve/improve-cli.js +75 -169
- package/dist/commands/improve/improve-result-file.js +10 -66
- package/dist/commands/improve/improve-strategies.js +52 -4
- package/dist/commands/improve/improve-usage-report.js +18 -64
- package/dist/commands/improve/improve.js +480 -1074
- package/dist/commands/improve/ledger.js +119 -0
- package/dist/commands/improve/locks.js +2 -8
- package/dist/commands/improve/loop-stages.js +415 -1073
- package/dist/commands/improve/memory/derived-ref.js +12 -77
- package/dist/commands/improve/memory/memory-belief.js +16 -118
- package/dist/commands/improve/memory/memory-improve.js +266 -14
- package/dist/commands/improve/outcome-loop.js +28 -156
- package/dist/commands/improve/planner.js +5 -15
- package/dist/commands/improve/preparation.js +779 -2319
- package/dist/commands/improve/proactive-maintenance.js +34 -101
- package/dist/commands/improve/reflect-noise.js +104 -280
- package/dist/commands/improve/reflect.js +642 -1353
- package/dist/commands/improve/retrieval-gate.js +127 -0
- package/dist/commands/improve/retrieval-scope.js +92 -0
- package/dist/commands/improve/salience.js +41 -240
- package/dist/commands/improve/session-asset.js +19 -100
- package/dist/commands/improve/stage.js +322 -0
- package/dist/commands/lint/base-linter.js +37 -15
- package/dist/commands/proposal/drain.js +261 -578
- package/dist/commands/proposal/proposal-cli.js +19 -20
- package/dist/commands/proposal/proposal-types.js +31 -24
- package/dist/commands/proposal/proposal.js +38 -8
- package/dist/commands/proposal/propose.js +134 -160
- package/dist/commands/proposal/repository.js +1097 -1394
- 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 +22 -89
- package/dist/commands/read/curate.js +105 -462
- package/dist/commands/read/knowledge.js +3 -2
- package/dist/commands/read/search-cli.js +16 -33
- package/dist/commands/read/search.js +17 -23
- package/dist/commands/read/show.js +57 -108
- package/dist/commands/sources/bundle-cli.js +25 -2
- package/dist/commands/sources/bundle-config-ops.js +4 -0
- package/dist/commands/sources/dangerous-env-audit.js +1 -2
- package/dist/commands/sources/info.js +127 -29
- 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/source-manage.js +9 -2
- package/dist/commands/sources/stash-cli.js +24 -4
- package/dist/commands/tasks/explain.js +10 -13
- package/dist/commands/tasks/tasks-cli.js +12 -13
- package/dist/commands/tasks/tasks.js +350 -936
- package/dist/commands/tasks/validate.js +26 -24
- package/dist/commands/workflow/plan.js +22 -29
- package/dist/commands/workflow-cli.js +4 -4
- package/dist/core/adapter/adapters/akm-adapter.js +2 -1
- package/dist/core/adapter/adapters/akm-lint.js +2 -3
- package/dist/core/adapter/adapters/akm-metadata.js +42 -12
- package/dist/core/adapter/adapters/akm-task-adapter.js +29 -8
- package/dist/core/adapter/adapters/akm-workflow-adapter.js +1 -1
- package/dist/core/adapter/execution-source.js +17 -29
- package/dist/core/asset/asset-placement.js +4 -13
- package/dist/core/asset/frontmatter.js +106 -1
- package/dist/core/asset/resolve-ref.js +1 -1
- package/dist/core/bundle-id.js +42 -5
- package/dist/core/bundle-rename.js +285 -0
- package/dist/core/config/config-io.js +1 -2
- package/dist/core/config/config-schema.js +9 -34
- package/dist/core/config/config-walker.js +1 -1
- package/dist/core/config/config.js +184 -111
- package/dist/core/config/engine-semantics.js +0 -2
- 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 +54 -125
- package/dist/core/config/schema/improve.js +4 -42
- package/dist/core/config/schema/index-config.js +9 -48
- 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-change.js +13 -5
- package/dist/core/file-lock.js +76 -173
- package/dist/core/improve-result.js +35 -7
- package/dist/core/improve-types.js +0 -1
- package/dist/core/logs-db.js +2 -2
- package/dist/core/loopback.js +7 -12
- package/dist/core/non-task-input.js +20 -0
- package/dist/core/parse.js +13 -16
- package/dist/core/paths.js +0 -24
- 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 +123 -61
- package/dist/core/state-db-scope.js +2 -4
- package/dist/core/state-db.js +126 -692
- package/dist/core/time.js +0 -20
- package/dist/core/type-presentation.js +1 -9
- package/dist/core/write-source.js +294 -1005
- 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/llm-cache.js +2 -2
- package/dist/indexer/ensure-index.js +77 -73
- 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 +141 -154
- package/dist/indexer/indexer.js +400 -1124
- package/dist/indexer/links/declared-links.js +90 -0
- package/dist/indexer/materialize-embeddings.js +60 -397
- package/dist/indexer/passes/memory-inference.js +96 -90
- package/dist/indexer/passes/metadata.js +132 -219
- package/dist/indexer/read-preflight.js +0 -7
- package/dist/indexer/scan/doc-to-entry.js +2 -3
- package/dist/indexer/scan/drain-dir.js +1 -1
- package/dist/indexer/search/db-search.js +190 -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 +36 -7
- package/dist/indexer/walk/walker.js +3 -4
- 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/client.js +18 -6
- package/dist/llm/embedders/deterministic.js +2 -3
- package/dist/llm/embedders/profile.js +71 -0
- package/dist/llm/embedders/remote.js +11 -17
- package/dist/llm/feature-gate.js +0 -8
- package/dist/llm/index-passes.js +3 -5
- package/dist/llm/memory-infer.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 +25 -38
- package/dist/output/shapes/passthrough.js +1 -9
- package/dist/{indexer/graph/graph-types.js → output/text/bundle-rename.js} +4 -1
- package/dist/output/text/command-format.js +69 -31
- package/dist/output/text/helpers.js +1 -1
- package/dist/output/text/migrate.js +5 -14
- package/dist/output/text/proposal-format.js +48 -3
- package/dist/output/text/show-format.js +13 -17
- 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 +42948 -92369
- package/dist/scripts/akm-migrate.js +42935 -92354
- 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/connection.js +5 -6
- package/dist/setup/steps/platforms.js +2 -2
- 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 +83 -21
- 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 +209 -0
- package/dist/storage/repositories/index-connection.js +39 -72
- package/dist/storage/repositories/index-entries-repository.js +131 -129
- package/dist/storage/repositories/index-entry-mapper.js +1 -2
- package/dist/storage/repositories/index-entry-schema.js +101 -268
- package/dist/storage/repositories/index-fts-repository.js +86 -256
- package/dist/storage/repositories/index-links-repository.js +143 -0
- package/dist/storage/repositories/index-llm-cache-repository.js +7 -9
- package/dist/storage/repositories/index-meta-repository.js +6 -4
- package/dist/storage/repositories/index-schema.js +257 -325
- 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 +104 -1
- package/dist/storage/repositories/registry-index-cache-repository.js +100 -0
- package/dist/storage/repositories/salience-repository.js +1 -19
- 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 +130 -0
- package/dist/tasks/activation-config.js +134 -62
- package/dist/tasks/backends/cron.js +191 -302
- package/dist/tasks/backends/exec-utils.js +2 -5
- package/dist/tasks/backends/launchd.js +141 -748
- package/dist/tasks/backends/schtasks.js +119 -623
- 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/run/load-task.js +1 -1
- package/dist/tasks/scheduler-binding.js +20 -238
- package/dist/tasks/scheduler-invocation.js +136 -244
- package/dist/tasks/scheduler-lock.js +53 -0
- package/dist/tasks/scheduler-sync.js +368 -679
- package/dist/tasks/source/parse-task-source.js +55 -9
- package/dist/tasks/source/task-source-v3-frozen.js +3 -4
- package/dist/tasks/source/task-to-v4.js +464 -88
- 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 +43 -0
- package/docs/migration/v0.9.1-to-v0.9.2.md +23 -7
- package/docs/reference/cli.md +232 -135
- package/docs/reference/configuration.md +71 -57
- package/docs/reference/data-and-telemetry.md +20 -21
- package/docs/reference/tasks.md +105 -39
- 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 +115 -738
- package/schemas/akm-workflow.json +1 -0
- package/dist/assets/improve-strategies/graph-refresh.json +0 -15
- package/dist/assets/prompts/contradiction-judge.md +0 -33
- package/dist/assets/prompts/graph-extract-system.md +0 -1
- package/dist/assets/prompts/graph-extract-user-prompt.md +0 -35
- package/dist/assets/prompts/metadata-enhance-system.md +0 -1
- package/dist/assets/tasks/improve/akm-graph-refresh-weekly.yml +0 -4
- 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 -149
- package/dist/commands/improve/distill/promote-memory.js +0 -291
- package/dist/commands/improve/distill/quality-gate.js +0 -337
- package/dist/commands/improve/eval-cases.js +0 -52
- 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 -31
- 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/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/db/graph-db.js +0 -444
- package/dist/indexer/graph/graph-boost.js +0 -427
- package/dist/indexer/graph/graph-dedup.js +0 -95
- package/dist/indexer/graph/graph-extraction.js +0 -1108
- package/dist/indexer/search/name-match.js +0 -35
- package/dist/indexer/search/ranking-contributors.js +0 -515
- package/dist/indexer/search/ranking-types.js +0 -4
- 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/llm/graph-extract.js +0 -728
- package/dist/llm/metadata-enhance.js +0 -96
- 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/tasks/source/task-to-v3.js +0 -507
- 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 -77
- 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,288 +2,141 @@
|
|
|
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
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
* review-queue slot. This module classifies a candidate edit against the
|
|
13
|
-
* current asset BEFORE `createProposal()` so reflect can short-circuit.
|
|
14
|
-
*
|
|
15
|
-
* Classification is pure text comparison — no LLM involved:
|
|
16
|
-
*
|
|
17
|
-
* - `"noop"` — byte-identical after trailing-whitespace normalization.
|
|
18
|
-
* - `"cosmetic"` — identical after a conservative cosmetic normalization
|
|
19
|
-
* of both sides (frontmatter compared as parsed YAML so
|
|
20
|
-
* folded/literal scalar re-folding cancels out; markdown
|
|
21
|
-
* body compared with hard-wrapped prose unwrapped,
|
|
22
|
-
* whitespace runs collapsed, and code-fence language
|
|
23
|
-
* hints stripped).
|
|
24
|
-
* - `"substantive"` — everything else, including every case the
|
|
25
|
-
* normalizer is unsure about.
|
|
26
|
-
*
|
|
27
|
-
* The normalizer is deliberately conservative: a false NEGATIVE (letting a
|
|
28
|
-
* cosmetic proposal through) costs one review-queue slot; a false POSITIVE
|
|
29
|
-
* (suppressing a real fix) loses work. When in doubt, classify as
|
|
30
|
-
* substantive. Concretely:
|
|
31
|
-
*
|
|
32
|
-
* - Fenced code block contents are compared verbatim (whitespace inside
|
|
33
|
-
* code is significant).
|
|
34
|
-
* - Indented (4-space / tab) code lines are compared verbatim.
|
|
35
|
-
* - Headings, tables, and thematic breaks never absorb the following
|
|
36
|
-
* line, so `# Title\nprose` vs `# Title prose` stays substantive.
|
|
37
|
-
* - Prose only unwraps into the previous line when that line is itself
|
|
38
|
-
* plain prose or a list/blockquote item (CommonMark lazy continuation).
|
|
5
|
+
* The reflect noise gate (#580): classify a candidate edit against the current
|
|
6
|
+
* asset before it becomes a proposal, by text comparison alone. Empty and
|
|
7
|
+
* cosmetic-only edits (YAML re-folding, fence language hints, re-wrapped prose)
|
|
8
|
+
* each cost an LLM call and a review slot. The gate is conservative — letting a
|
|
9
|
+
* cosmetic edit through costs a review, suppressing a real fix loses work — so
|
|
10
|
+
* anything uncertain is `substantive`: code (fenced or indented) compares
|
|
11
|
+
* verbatim, and headings, tables and breaks never absorb the next line.
|
|
39
12
|
*/
|
|
40
13
|
import { parse as yamlParse } from "yaml";
|
|
41
14
|
/**
|
|
42
|
-
*
|
|
43
|
-
*
|
|
44
|
-
*
|
|
45
|
-
*
|
|
46
|
-
*
|
|
47
|
-
* `"low-value"` is a tier between `"cosmetic"` and `"substantive"` (#639):
|
|
48
|
-
* a small prose rewrite that changes few content-words outside fenced code,
|
|
49
|
-
* without introducing new headings, list items, or code lines. The caller
|
|
50
|
-
* decides whether to act on it (config-gated, DEFAULT OFF).
|
|
15
|
+
* `noop` (identical up to trailing whitespace) and `cosmetic` (identical after
|
|
16
|
+
* normalizing frontmatter as parsed YAML and prose as unwrapped text) never
|
|
17
|
+
* become proposals. `low-value` (#639) is a small prose rewrite — few changed
|
|
18
|
+
* words, nothing structural; the caller decides (off by default).
|
|
51
19
|
*/
|
|
52
20
|
export function classifyReflectChange(sourceContent, candidateContent) {
|
|
53
|
-
if (normalizeTrailingWhitespace(sourceContent) === normalizeTrailingWhitespace(candidateContent))
|
|
21
|
+
if (normalizeTrailingWhitespace(sourceContent) === normalizeTrailingWhitespace(candidateContent))
|
|
54
22
|
return "noop";
|
|
55
|
-
}
|
|
56
23
|
try {
|
|
57
|
-
if (cosmeticNormalForm(sourceContent) === cosmeticNormalForm(candidateContent))
|
|
24
|
+
if (cosmeticNormalForm(sourceContent) === cosmeticNormalForm(candidateContent))
|
|
58
25
|
return "cosmetic";
|
|
59
|
-
}
|
|
60
26
|
}
|
|
61
27
|
catch {
|
|
62
|
-
//
|
|
63
|
-
// cosmetic — let the proposal through.
|
|
28
|
+
// unprovable → substantive
|
|
64
29
|
}
|
|
65
|
-
// Low-value tier (#639): deterministic semantic-value-floor check.
|
|
66
30
|
try {
|
|
67
|
-
if (isLowValueChange(sourceContent, candidateContent))
|
|
31
|
+
if (isLowValueChange(sourceContent, candidateContent))
|
|
68
32
|
return "low-value";
|
|
69
|
-
}
|
|
70
33
|
}
|
|
71
34
|
catch {
|
|
72
|
-
//
|
|
35
|
+
// unprovable → substantive
|
|
73
36
|
}
|
|
74
37
|
return "substantive";
|
|
75
38
|
}
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
* minimise false positives (substantive changes suppressed).
|
|
83
|
-
*/
|
|
39
|
+
/** A `---` frontmatter block and the rest (`fmText: null` when there is none). */
|
|
40
|
+
export function splitFrontmatter(raw) {
|
|
41
|
+
const m = raw.match(/^---\r?\n([\s\S]*?)\r?\n---\r?\n?([\s\S]*)$/);
|
|
42
|
+
return m ? { fmText: m[1] ?? "", body: m[2] ?? "" } : { fmText: null, body: raw };
|
|
43
|
+
}
|
|
44
|
+
/** At most this many changed words (and at least 2 — one word may be a flag flip) is low-value. */
|
|
84
45
|
const LOW_VALUE_TOKEN_THRESHOLD = 4;
|
|
85
|
-
/**
|
|
86
|
-
* Negation / flag-correction words that make a line exempt from low-value
|
|
87
|
-
* classification even when the token-change count is below the threshold.
|
|
88
|
-
* A line that introduces or removes one of these words carries enough semantic
|
|
89
|
-
* weight to be treated as substantive.
|
|
90
|
-
*/
|
|
46
|
+
/** A change to any of these is never low-value. */
|
|
91
47
|
const NEGATION_WORDS = new Set(["never", "not", "no", "don't", "avoid", "cannot", "can't"]);
|
|
92
|
-
/**
|
|
93
|
-
* Pattern for lines that contain decision / outcome markers. Changed tokens on
|
|
94
|
-
* these lines are EXEMPT from the low-value token count (they are semantically
|
|
95
|
-
* significant regardless of token volume).
|
|
96
|
-
*/
|
|
48
|
+
/** Lines recording a decision or outcome are always significant; their words are not counted. */
|
|
97
49
|
const DECISION_MARKER_RE = /\b(decision|outcome)\b/i;
|
|
98
|
-
/**
|
|
99
|
-
* Pattern for lines that start a new structural element (ATX heading, list
|
|
100
|
-
* item, thematic break). A candidate that ADDS such lines is not low-value.
|
|
101
|
-
*/
|
|
102
50
|
const STRUCTURAL_LINE_RE = /^\s*(#{1,6}\s|[-*+]\s|\d{1,9}[.)]\s|---+|===+|\|\s)/;
|
|
103
|
-
/**
|
|
104
|
-
* Determine whether the change from `source` to `candidate` qualifies as
|
|
105
|
-
* `"low-value"`: a small prose rewrite with few changed content-words outside
|
|
106
|
-
* fenced code / frontmatter / decision markers, without adding structural lines.
|
|
107
|
-
*
|
|
108
|
-
* Conservative / false-negative-biased: returns `false` (= substantive) when
|
|
109
|
-
* any exemption condition is met or when the parser is unsure.
|
|
110
|
-
*/
|
|
111
51
|
function isLowValueChange(source, candidate) {
|
|
112
|
-
const
|
|
113
|
-
const
|
|
114
|
-
//
|
|
115
|
-
if (
|
|
116
|
-
return false;
|
|
117
|
-
}
|
|
118
|
-
// Any change in frontmatter → substantive (description / when_to_use are semantic).
|
|
119
|
-
if (srcParsed.frontmatter !== cndParsed.frontmatter) {
|
|
52
|
+
const src = parseLowValueSections(source);
|
|
53
|
+
const cnd = parseLowValueSections(candidate);
|
|
54
|
+
// Code and frontmatter changes are always substantive.
|
|
55
|
+
if (src.codeFences.join("\n") !== cnd.codeFences.join("\n") || src.frontmatter !== cnd.frontmatter)
|
|
120
56
|
return false;
|
|
121
|
-
|
|
122
|
-
// Check prose body lines for low-value criteria.
|
|
123
|
-
const srcLines = srcParsed.proseLines;
|
|
124
|
-
const cndLines = cndParsed.proseLines;
|
|
125
|
-
// Build a word-diff over prose lines. Align by line index (conservative: if
|
|
126
|
-
// line counts differ significantly, call it substantive).
|
|
127
|
-
if (Math.abs(srcLines.length - cndLines.length) > 2) {
|
|
57
|
+
if (Math.abs(src.proseLines.length - cnd.proseLines.length) > 2)
|
|
128
58
|
return false;
|
|
129
|
-
}
|
|
130
|
-
const maxLen = Math.max(srcLines.length, cndLines.length);
|
|
131
59
|
let changedTokens = 0;
|
|
132
|
-
let
|
|
133
|
-
|
|
134
|
-
const
|
|
135
|
-
|
|
136
|
-
if (srcLine === cndLine)
|
|
137
|
-
continue;
|
|
138
|
-
// Lines with decision/outcome markers are exempt — skip their token diff.
|
|
139
|
-
if (DECISION_MARKER_RE.test(srcLine) || DECISION_MARKER_RE.test(cndLine)) {
|
|
60
|
+
for (let i = 0; i < Math.max(src.proseLines.length, cnd.proseLines.length); i++) {
|
|
61
|
+
const srcLine = src.proseLines[i] ?? "";
|
|
62
|
+
const cndLine = cnd.proseLines[i] ?? "";
|
|
63
|
+
if (srcLine === cndLine || DECISION_MARKER_RE.test(srcLine) || DECISION_MARKER_RE.test(cndLine))
|
|
140
64
|
continue;
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
// low-value. Structural changes affect document organization and are
|
|
144
|
-
// inherently significant regardless of token count.
|
|
145
|
-
if (STRUCTURAL_LINE_RE.test(cndLine) || STRUCTURAL_LINE_RE.test(srcLine)) {
|
|
146
|
-
hasNewStructuralLine = true;
|
|
147
|
-
break;
|
|
148
|
-
}
|
|
149
|
-
// Tokenise both lines into lowercase words.
|
|
65
|
+
if (STRUCTURAL_LINE_RE.test(cndLine) || STRUCTURAL_LINE_RE.test(srcLine))
|
|
66
|
+
return false;
|
|
150
67
|
const srcTokens = tokenize(srcLine);
|
|
151
68
|
const cndTokens = tokenize(cndLine);
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
const cndNegSet = new Set(cndTokens.filter((t) => NEGATION_WORDS.has(t)));
|
|
156
|
-
const negSetsEqual = srcNegSet.size === cndNegSet.size && [...srcNegSet].every((t) => cndNegSet.has(t));
|
|
157
|
-
if (!negSetsEqual) {
|
|
69
|
+
const srcNeg = new Set(srcTokens.filter((t) => NEGATION_WORDS.has(t)));
|
|
70
|
+
const cndNeg = new Set(cndTokens.filter((t) => NEGATION_WORDS.has(t)));
|
|
71
|
+
if (srcNeg.size !== cndNeg.size || [...srcNeg].some((t) => !cndNeg.has(t)))
|
|
158
72
|
return false;
|
|
159
|
-
|
|
160
|
-
// Count changed tokens: words unique to each side (symmetric difference).
|
|
161
|
-
// Counting max(added, removed) approximates "number of substitutions" so
|
|
162
|
-
// that "is→runs" + "default→defaults" counts as 2, not 4.
|
|
73
|
+
// max(added, removed) approximates substitutions: "is→runs" counts once.
|
|
163
74
|
const srcSet = new Set(srcTokens);
|
|
164
75
|
const cndSet = new Set(cndTokens);
|
|
165
|
-
|
|
166
|
-
const removedTokens = srcTokens.filter((t) => !cndSet.has(t));
|
|
167
|
-
changedTokens += Math.max(addedTokens.length, removedTokens.length);
|
|
76
|
+
changedTokens += Math.max(cndTokens.filter((t) => !srcSet.has(t)).length, srcTokens.filter((t) => !cndSet.has(t)).length);
|
|
168
77
|
}
|
|
169
|
-
if (hasNewStructuralLine)
|
|
170
|
-
return false;
|
|
171
|
-
// Must have at least 2 changed tokens to qualify as low-value:
|
|
172
|
-
// - 0 changed tokens → noop/cosmetic (handled upstream, never reaches here)
|
|
173
|
-
// - 1 changed token → single-word substitution; too small to be reliably
|
|
174
|
-
// "low-value" vs a semantically significant flag flip → treat as substantive
|
|
175
|
-
// - 2..threshold-1 → genuine prose micro-rewrite → low-value
|
|
176
78
|
return changedTokens >= 2 && changedTokens < LOW_VALUE_TOKEN_THRESHOLD;
|
|
177
79
|
}
|
|
178
|
-
/**
|
|
80
|
+
/** Frontmatter text, fenced code blocks, and prose lines (an unclosed fence counts as prose). */
|
|
179
81
|
function parseLowValueSections(text) {
|
|
180
|
-
const
|
|
181
|
-
// Split off frontmatter.
|
|
182
|
-
const fmMatch = normalized.match(/^---\n([\s\S]*?)\n---\n?([\s\S]*)$/);
|
|
183
|
-
const frontmatter = fmMatch ? (fmMatch[1] ?? "") : "";
|
|
184
|
-
const body = fmMatch ? (fmMatch[2] ?? normalized) : normalized;
|
|
185
|
-
const lines = body.split("\n");
|
|
82
|
+
const { fmText, body } = splitFrontmatter(normalizeTrailingWhitespace(text));
|
|
186
83
|
const codeFences = [];
|
|
187
84
|
const proseLines = [];
|
|
188
|
-
let
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
inFence = false;
|
|
196
|
-
codeFences.push(currentFence.join("\n"));
|
|
197
|
-
currentFence = [];
|
|
198
|
-
fenceMarker = "";
|
|
199
|
-
}
|
|
200
|
-
else {
|
|
201
|
-
currentFence.push(line);
|
|
202
|
-
}
|
|
203
|
-
}
|
|
204
|
-
else {
|
|
205
|
-
const openMatch = line.match(/^(\s{0,3})(`{3,}|~{3,})/);
|
|
206
|
-
if (openMatch) {
|
|
207
|
-
inFence = true;
|
|
208
|
-
fenceMarker = openMatch[2] ?? "```";
|
|
209
|
-
currentFence = [];
|
|
85
|
+
let fence;
|
|
86
|
+
for (const line of body.split("\n")) {
|
|
87
|
+
if (fence) {
|
|
88
|
+
const close = line.match(/^(\s{0,3})(`{3,}|~{3,})\s*$/);
|
|
89
|
+
if (close?.[2]?.startsWith(fence.marker)) {
|
|
90
|
+
codeFences.push(fence.lines.join("\n"));
|
|
91
|
+
fence = undefined;
|
|
210
92
|
}
|
|
211
93
|
else {
|
|
212
|
-
|
|
94
|
+
fence.lines.push(line);
|
|
213
95
|
}
|
|
96
|
+
continue;
|
|
214
97
|
}
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
98
|
+
const open = line.match(/^(\s{0,3})(`{3,}|~{3,})/);
|
|
99
|
+
if (open)
|
|
100
|
+
fence = { marker: open[2] ?? "```", lines: [] };
|
|
101
|
+
else
|
|
102
|
+
proseLines.push(line);
|
|
103
|
+
}
|
|
104
|
+
if (fence)
|
|
105
|
+
proseLines.push(...fence.lines);
|
|
106
|
+
return { frontmatter: fmText ?? "", codeFences, proseLines };
|
|
221
107
|
}
|
|
222
|
-
/** Split a prose line into lowercase word tokens, filtering punctuation. */
|
|
223
108
|
function tokenize(line) {
|
|
224
|
-
return
|
|
109
|
+
return line
|
|
225
110
|
.toLowerCase()
|
|
226
|
-
|
|
227
|
-
// like "don't"/"can't" match regardless of which glyph the content uses.
|
|
228
|
-
.replace(/’/g, "'")
|
|
111
|
+
.replace(/’/g, "'") // curly apostrophes still match "don't"
|
|
229
112
|
.replace(/[^a-z0-9'-]/g, " ")
|
|
230
113
|
.split(/\s+/)
|
|
231
|
-
.filter((t) => t.length > 0)
|
|
114
|
+
.filter((t) => t.length > 0);
|
|
232
115
|
}
|
|
233
|
-
/**
|
|
234
|
-
* Trailing-whitespace normalization used for the `"noop"` check: CRLF → LF,
|
|
235
|
-
* strip trailing spaces/tabs per line, drop trailing newlines.
|
|
236
|
-
*/
|
|
116
|
+
/** CRLF → LF, no trailing spaces per line, no trailing newlines. */
|
|
237
117
|
export function normalizeTrailingWhitespace(text) {
|
|
238
118
|
return text
|
|
239
119
|
.replace(/\r\n/g, "\n")
|
|
240
120
|
.replace(/[ \t]+$/gm, "")
|
|
241
121
|
.replace(/\n+$/, "");
|
|
242
122
|
}
|
|
243
|
-
/**
|
|
244
|
-
* Compute the cosmetic normal form of an asset: canonicalized frontmatter
|
|
245
|
-
* (parsed YAML, keys sorted) + normalized markdown body, joined with a
|
|
246
|
-
* separator that cannot occur in either part.
|
|
247
|
-
*
|
|
248
|
-
* Two assets with equal normal forms differ only cosmetically.
|
|
249
|
-
*/
|
|
123
|
+
/** Canonical frontmatter (parsed YAML, keys sorted) plus the normalized body; equal forms differ only cosmetically. */
|
|
250
124
|
export function cosmeticNormalForm(text) {
|
|
251
|
-
const { fmText, body } =
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
*/
|
|
261
|
-
function splitFrontmatterText(raw) {
|
|
262
|
-
const m = raw.match(/^---\n([\s\S]*?)\n---\n?([\s\S]*)$/);
|
|
263
|
-
if (!m)
|
|
264
|
-
return { fmText: null, body: raw };
|
|
265
|
-
return { fmText: m[1] ?? "", body: m[2] ?? "" };
|
|
266
|
-
}
|
|
267
|
-
/**
|
|
268
|
-
* Canonicalize a YAML frontmatter block: parse and re-serialize as JSON with
|
|
269
|
-
* recursively sorted object keys. Folded (`>`) and literal (`|`) scalars,
|
|
270
|
-
* quoting style, and key order all cancel out; the parsed VALUES are what we
|
|
271
|
-
* compare. Unparsable YAML falls back to the raw text (trailing-stripped) so
|
|
272
|
-
* any real edit still registers as a difference.
|
|
273
|
-
*/
|
|
274
|
-
function canonicalizeYamlText(fmText) {
|
|
275
|
-
try {
|
|
276
|
-
return stableStringify(yamlParse(fmText));
|
|
277
|
-
}
|
|
278
|
-
catch {
|
|
279
|
-
return fmText;
|
|
125
|
+
const { fmText, body } = splitFrontmatter(normalizeTrailingWhitespace(text));
|
|
126
|
+
let fmCanonical = "";
|
|
127
|
+
if (fmText !== null) {
|
|
128
|
+
try {
|
|
129
|
+
fmCanonical = stableStringify(yamlParse(fmText));
|
|
130
|
+
}
|
|
131
|
+
catch {
|
|
132
|
+
fmCanonical = fmText; // unparsable: any real edit still registers
|
|
133
|
+
}
|
|
280
134
|
}
|
|
135
|
+
return `${fmCanonical}\u0000${normalizeMarkdownBody(body)}`;
|
|
281
136
|
}
|
|
282
|
-
/** JSON.stringify with recursively sorted object keys (arrays keep order). */
|
|
283
137
|
function stableStringify(value) {
|
|
284
|
-
if (Array.isArray(value))
|
|
138
|
+
if (Array.isArray(value))
|
|
285
139
|
return `[${value.map(stableStringify).join(",")}]`;
|
|
286
|
-
}
|
|
287
140
|
if (value !== null && typeof value === "object") {
|
|
288
141
|
const entries = Object.entries(value)
|
|
289
142
|
.sort(([a], [b]) => (a < b ? -1 : a > b ? 1 : 0))
|
|
@@ -292,100 +145,71 @@ function stableStringify(value) {
|
|
|
292
145
|
}
|
|
293
146
|
return JSON.stringify(value);
|
|
294
147
|
}
|
|
295
|
-
/** Opening or closing fence: optional indent + ``` / ~~~ + optional info string. */
|
|
296
148
|
const FENCE_LINE = /^(\s{0,3})(`{3,}|~{3,})(.*)$/;
|
|
297
|
-
/**
|
|
298
|
-
* Lines that terminate a logical line and never absorb a following prose
|
|
299
|
-
* line: ATX headings, setext underlines, thematic breaks, table rows.
|
|
300
|
-
*/
|
|
149
|
+
/** Headings, setext underlines, thematic breaks and table rows never absorb the next line. */
|
|
301
150
|
const TERMINAL_LINE = /^\s{0,3}(#{1,6}(\s|$)|=+\s*$|(-\s*){3,}$|(\*\s*){3,}$|(_\s*){3,}$|\|)/;
|
|
302
|
-
/** List items and blockquotes
|
|
151
|
+
/** List items and blockquotes allow lazy continuation. */
|
|
303
152
|
const CONTINUABLE_LINE = /^\s*([-*+]\s|\d{1,9}[.)]\s|>)/;
|
|
304
|
-
/** Indented code block line (outside any list context — treated verbatim). */
|
|
305
153
|
const INDENTED_CODE_LINE = /^(\t| {4})/;
|
|
306
154
|
/**
|
|
307
|
-
*
|
|
308
|
-
*
|
|
309
|
-
*
|
|
310
|
-
*
|
|
311
|
-
* - indented code lines kept verbatim
|
|
312
|
-
* - hard-wrapped prose unwrapped into the preceding prose / list /
|
|
313
|
-
* blockquote line (single space join)
|
|
314
|
-
* - runs of spaces/tabs inside prose collapsed to one space
|
|
315
|
-
* - runs of blank lines collapsed to one; leading/trailing blanks dropped
|
|
155
|
+
* A markdown body for cosmetic comparison: fence language hints dropped (code
|
|
156
|
+
* kept verbatim), indented code verbatim, hard-wrapped prose joined into the
|
|
157
|
+
* preceding prose/list/quote line, inner whitespace runs collapsed, blank runs
|
|
158
|
+
* collapsed and trimmed.
|
|
316
159
|
*/
|
|
317
160
|
export function normalizeMarkdownBody(body) {
|
|
318
|
-
const lines = normalizeTrailingWhitespace(body).split("\n");
|
|
319
161
|
const logical = [];
|
|
320
162
|
let lastKind = "blank";
|
|
321
163
|
let fenceMarker = null;
|
|
322
|
-
|
|
164
|
+
const push = (line, kind) => {
|
|
165
|
+
logical.push(line);
|
|
166
|
+
lastKind = kind;
|
|
167
|
+
};
|
|
168
|
+
for (const line of normalizeTrailingWhitespace(body).split("\n")) {
|
|
323
169
|
if (fenceMarker !== null) {
|
|
324
170
|
const close = line.match(FENCE_LINE);
|
|
325
171
|
if (close?.[2]?.startsWith(fenceMarker) && close[3]?.trim() === "") {
|
|
326
172
|
fenceMarker = null;
|
|
327
|
-
|
|
328
|
-
lastKind = "terminal";
|
|
173
|
+
push(line.trim(), "terminal");
|
|
329
174
|
}
|
|
330
175
|
else {
|
|
331
|
-
|
|
332
|
-
lastKind = "verbatim";
|
|
176
|
+
push(line, "verbatim");
|
|
333
177
|
}
|
|
334
178
|
continue;
|
|
335
179
|
}
|
|
336
180
|
const fence = line.match(FENCE_LINE);
|
|
337
181
|
if (fence) {
|
|
338
182
|
fenceMarker = fence[2] ?? "```";
|
|
339
|
-
|
|
340
|
-
logical.push(fenceMarker);
|
|
341
|
-
lastKind = "terminal";
|
|
342
|
-
continue;
|
|
183
|
+
push(fenceMarker, "terminal");
|
|
343
184
|
}
|
|
344
|
-
if (line.trim() === "") {
|
|
345
|
-
if (lastKind !== "blank")
|
|
346
|
-
|
|
347
|
-
lastKind = "blank";
|
|
348
|
-
}
|
|
349
|
-
continue;
|
|
185
|
+
else if (line.trim() === "") {
|
|
186
|
+
if (lastKind !== "blank")
|
|
187
|
+
push("", "blank");
|
|
350
188
|
}
|
|
351
|
-
if (INDENTED_CODE_LINE.test(line)) {
|
|
352
|
-
|
|
353
|
-
lastKind = "verbatim";
|
|
354
|
-
continue;
|
|
189
|
+
else if (INDENTED_CODE_LINE.test(line)) {
|
|
190
|
+
push(line, "verbatim");
|
|
355
191
|
}
|
|
356
|
-
if (TERMINAL_LINE.test(line)) {
|
|
357
|
-
|
|
358
|
-
lastKind = "terminal";
|
|
359
|
-
continue;
|
|
192
|
+
else if (TERMINAL_LINE.test(line)) {
|
|
193
|
+
push(collapseInnerWhitespace(line), "terminal");
|
|
360
194
|
}
|
|
361
|
-
if (CONTINUABLE_LINE.test(line)) {
|
|
362
|
-
|
|
363
|
-
lastKind = "continuable";
|
|
364
|
-
continue;
|
|
195
|
+
else if (CONTINUABLE_LINE.test(line)) {
|
|
196
|
+
push(collapseInnerWhitespace(line), "continuable");
|
|
365
197
|
}
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
const collapsed = collapseInnerWhitespace(line.trim());
|
|
369
|
-
if (lastKind === "prose" || lastKind === "continuable") {
|
|
370
|
-
// lastKind stays joinable (prose continuation keeps absorbing).
|
|
371
|
-
logical[logical.length - 1] = `${logical[logical.length - 1]} ${collapsed}`;
|
|
198
|
+
else if (lastKind === "prose" || lastKind === "continuable") {
|
|
199
|
+
logical[logical.length - 1] = `${logical[logical.length - 1]} ${collapseInnerWhitespace(line.trim())}`;
|
|
372
200
|
}
|
|
373
201
|
else {
|
|
374
|
-
|
|
375
|
-
lastKind = "prose";
|
|
202
|
+
push(collapseInnerWhitespace(line.trim()), "prose");
|
|
376
203
|
}
|
|
377
204
|
}
|
|
378
|
-
// Drop leading/trailing blank logical lines.
|
|
379
205
|
while (logical[0] === "")
|
|
380
206
|
logical.shift();
|
|
381
207
|
while (logical[logical.length - 1] === "")
|
|
382
208
|
logical.pop();
|
|
383
209
|
return logical.join("\n");
|
|
384
210
|
}
|
|
385
|
-
/** Collapse
|
|
211
|
+
/** Collapse inner space/tab runs, keeping the leading indent. */
|
|
386
212
|
function collapseInnerWhitespace(line) {
|
|
387
213
|
const m = line.match(/^([ \t]*)([\s\S]*)$/);
|
|
388
|
-
|
|
389
|
-
const rest = m?.[2] ?? "";
|
|
390
|
-
return indent + rest.replace(/[ \t]+/g, " ");
|
|
214
|
+
return (m?.[1] ?? "") + (m?.[2] ?? "").replace(/[ \t]+/g, " ");
|
|
391
215
|
}
|