akm-cli 0.9.0-beta.5 → 0.9.0-beta.51
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +711 -0
- package/README.md +12 -4
- package/dist/akm +38 -0
- package/dist/akm-migrate-storage +38 -0
- package/dist/assets/profiles/default.json +9 -4
- package/dist/assets/profiles/frequent.json +1 -1
- package/dist/assets/profiles/memory-focus.json +1 -1
- package/dist/assets/profiles/quick.json +1 -1
- package/dist/assets/profiles/synthesize.json +15 -0
- package/dist/assets/profiles/thorough.json +1 -1
- package/dist/assets/prompts/consolidate-system.md +23 -0
- package/dist/assets/prompts/contradiction-judge.md +33 -0
- package/dist/assets/prompts/distill-knowledge-system.md +22 -0
- package/dist/assets/prompts/distill-lesson-system.md +36 -0
- package/dist/assets/prompts/extract-session.md +6 -2
- package/dist/assets/prompts/graph-extract-system.md +1 -0
- package/dist/assets/prompts/graph-extract-user-prompt.md +1 -1
- package/dist/assets/prompts/memory-infer-system.md +1 -0
- package/dist/assets/prompts/memory-infer-user.md +5 -0
- package/dist/assets/prompts/metadata-enhance-system.md +1 -0
- package/dist/assets/prompts/procedural-system.md +44 -0
- package/dist/assets/prompts/recombine-system.md +40 -0
- package/dist/assets/prompts/staleness-detect-system.md +6 -0
- package/dist/assets/prompts/validate-summary-judge.md +1 -0
- package/dist/assets/stash-skeleton/facts/conventions/assets/agent.md +38 -0
- package/dist/assets/stash-skeleton/facts/conventions/assets/command.md +38 -0
- package/dist/assets/stash-skeleton/facts/conventions/assets/fact.md +39 -0
- package/dist/assets/stash-skeleton/facts/conventions/assets/knowledge.md +40 -0
- package/dist/assets/stash-skeleton/facts/conventions/assets/lesson.md +43 -0
- package/dist/assets/stash-skeleton/facts/conventions/assets/memory.md +38 -0
- package/dist/assets/stash-skeleton/facts/conventions/assets/script.md +43 -0
- package/dist/assets/stash-skeleton/facts/conventions/assets/skill.md +40 -0
- package/dist/assets/stash-skeleton/facts/conventions/assets/workflow.md +43 -0
- package/dist/assets/templates/html/health.html +281 -111
- package/dist/assets/wiki/ingest-workflow-template.md +38 -10
- package/dist/cli/parse-args.js +46 -1
- package/dist/cli/shared.js +28 -0
- package/dist/cli.js +27 -11
- package/dist/commands/agent/agent-dispatch.js +2 -2
- package/dist/commands/agent/agent-support.js +0 -7
- package/dist/commands/agent/contribute-cli.js +17 -4
- package/dist/commands/config-cli.js +18 -2
- package/dist/commands/env/child-env.js +47 -0
- package/dist/commands/env/env-cli.js +33 -26
- package/dist/commands/env/secret-cli.js +36 -22
- package/dist/commands/feedback-cli.js +15 -6
- package/dist/commands/graph/graph-cli.js +5 -13
- package/dist/commands/graph/graph.js +76 -72
- package/dist/commands/health/checks.js +49 -1
- package/dist/commands/health/html-report.js +422 -80
- package/dist/commands/health.js +386 -9
- package/dist/commands/improve/calibration.js +161 -0
- package/dist/commands/improve/consolidate/chunking.js +141 -0
- package/dist/commands/improve/consolidate/eligibility.js +81 -0
- package/dist/commands/improve/consolidate/merge.js +145 -0
- package/dist/commands/improve/consolidate/sanitize.js +231 -0
- package/dist/commands/{lint.js → improve/consolidate/types.js} +1 -1
- package/dist/commands/improve/consolidate.js +635 -660
- package/dist/commands/improve/dedup.js +482 -0
- package/dist/commands/improve/distill.js +159 -69
- package/dist/commands/improve/eligibility.js +434 -0
- package/dist/commands/improve/encoding-salience.js +205 -0
- package/dist/commands/improve/extract-cli.js +124 -2
- package/dist/commands/improve/extract-prompt.js +39 -2
- package/dist/commands/improve/extract-watch.js +140 -0
- package/dist/commands/improve/extract.js +389 -40
- package/dist/commands/improve/feedback-valence.js +54 -0
- package/dist/commands/improve/homeostatic.js +467 -0
- package/dist/commands/improve/improve-auto-accept.js +138 -7
- package/dist/commands/improve/improve-cli.js +36 -61
- package/dist/commands/improve/improve-profiles.js +14 -0
- package/dist/commands/improve/improve-result-file.js +14 -25
- package/dist/commands/improve/improve-session.js +58 -0
- package/dist/commands/improve/improve.js +485 -2498
- package/dist/commands/improve/locks.js +154 -0
- package/dist/commands/improve/loop-stages.js +1083 -0
- package/dist/commands/improve/memory/memory-contradiction-detect.js +23 -28
- package/dist/commands/improve/outcome-loop.js +256 -0
- package/dist/commands/improve/preparation.js +1966 -0
- package/dist/commands/improve/proactive-maintenance.js +115 -0
- package/dist/commands/improve/procedural.js +418 -0
- package/dist/commands/improve/recombine.js +850 -0
- package/dist/commands/improve/reflect-noise.js +0 -0
- package/dist/commands/improve/reflect.js +183 -40
- package/dist/commands/improve/salience.js +438 -0
- package/dist/commands/improve/triage.js +93 -0
- package/dist/commands/lint/agent-linter.js +19 -24
- package/dist/commands/lint/base-linter.js +173 -60
- package/dist/commands/lint/command-linter.js +19 -24
- package/dist/commands/lint/env-key-rules.js +38 -1
- package/dist/commands/lint/fact-linter.js +39 -0
- package/dist/commands/lint/index.js +31 -13
- package/dist/commands/lint/memory-linter.js +1 -1
- package/dist/commands/lint/registry.js +7 -2
- package/dist/commands/lint/task-linter.js +3 -3
- package/dist/commands/lint/workflow-linter.js +26 -1
- package/dist/commands/proposal/drain-policies.js +5 -0
- package/dist/commands/proposal/drain.js +43 -50
- package/dist/commands/proposal/proposal-cli.js +21 -31
- package/dist/commands/proposal/proposal.js +5 -0
- package/dist/commands/proposal/propose.js +7 -2
- package/dist/commands/proposal/validators/proposal-quality-validators.js +9 -8
- package/dist/commands/proposal/validators/proposals.js +189 -63
- package/dist/commands/read/curate.js +414 -94
- package/dist/commands/read/knowledge.js +6 -3
- package/dist/commands/read/search-cli.js +9 -4
- package/dist/commands/read/search.js +10 -6
- package/dist/commands/read/show.js +86 -7
- package/dist/commands/sources/init.js +49 -17
- package/dist/commands/sources/installed-stashes.js +11 -3
- package/dist/commands/sources/schema-repair.js +43 -45
- package/dist/commands/sources/self-update.js +2 -2
- package/dist/commands/sources/source-add.js +7 -3
- package/dist/commands/sources/stash-cli.js +28 -40
- package/dist/commands/sources/stash-skeleton.js +23 -8
- package/dist/commands/tasks/tasks-cli.js +19 -27
- package/dist/commands/tasks/tasks.js +39 -11
- package/dist/commands/wiki-cli.js +21 -35
- package/dist/core/asset/asset-registry.js +3 -1
- package/dist/core/asset/asset-spec.js +18 -2
- package/dist/core/asset/frontmatter.js +166 -167
- package/dist/core/asset/markdown.js +8 -0
- package/dist/core/authoring-rules.js +92 -0
- package/dist/core/common.js +0 -5
- package/dist/core/config/config-migration.js +12 -11
- package/dist/core/config/config-schema.js +340 -56
- package/dist/core/config/config-types.js +3 -3
- package/dist/core/config/config.js +28 -7
- package/dist/core/events.js +3 -7
- package/dist/core/improve-types.js +11 -8
- package/dist/core/logs-db.js +10 -66
- package/dist/core/parse.js +36 -16
- package/dist/core/paths.js +3 -0
- package/dist/core/standards/resolve-standards-context.js +87 -0
- package/dist/core/standards/resolve-stash-standards.js +99 -0
- package/dist/core/standards/resolve-type-conventions.js +66 -0
- package/dist/core/state/migrations.js +714 -0
- package/dist/core/state-db.js +525 -474
- package/dist/indexer/db/db.js +439 -247
- package/dist/indexer/db/graph-db.js +129 -86
- package/dist/indexer/ensure-index.js +152 -17
- package/dist/indexer/graph/graph-boost.js +51 -41
- package/dist/indexer/graph/graph-extraction.js +218 -4
- package/dist/indexer/index-writer-lock.js +99 -0
- package/dist/indexer/indexer.js +123 -221
- package/dist/indexer/passes/dir-staleness.js +114 -0
- package/dist/indexer/passes/memory-inference.js +13 -5
- package/dist/indexer/passes/staleness-detect.js +2 -5
- package/dist/indexer/search/db-search.js +19 -6
- package/dist/indexer/search/ranking-contributors.js +22 -0
- package/dist/indexer/search/ranking.js +4 -0
- package/dist/indexer/search/search-source.js +17 -18
- package/dist/indexer/search/semantic-status.js +4 -0
- package/dist/indexer/walk/matchers.js +9 -0
- package/dist/integrations/agent/config.js +6 -53
- package/dist/integrations/agent/index.js +2 -18
- package/dist/integrations/agent/prompts.js +75 -9
- package/dist/integrations/agent/runner-dispatch.js +59 -0
- package/dist/integrations/harnesses/claude/session-log.js +11 -1
- package/dist/integrations/harnesses/index.js +2 -3
- package/dist/integrations/harnesses/opencode/session-log.js +173 -3
- package/dist/integrations/harnesses/opencode-sdk/index.js +2 -2
- package/dist/integrations/harnesses/opencode-sdk/sdk-runner.js +0 -2
- package/dist/integrations/session-logs/index.js +16 -0
- package/dist/llm/client.js +45 -15
- package/dist/llm/embedder.js +42 -3
- package/dist/llm/embedders/deterministic.js +66 -0
- package/dist/llm/embedders/local.js +66 -2
- package/dist/llm/feature-gate.js +8 -4
- package/dist/llm/graph-extract.js +67 -44
- package/dist/llm/memory-infer-impl.js +138 -0
- package/dist/llm/memory-infer.js +1 -127
- package/dist/llm/metadata-enhance.js +44 -31
- package/dist/llm/structured-call.js +49 -0
- package/dist/migrate-storage-node.mjs +8 -0
- package/dist/output/context.js +5 -5
- package/dist/output/renderers.js +74 -2
- package/dist/output/shapes/curate.js +14 -2
- package/dist/output/shapes/passthrough.js +0 -1
- package/dist/output/text/helpers.js +16 -1
- package/dist/registry/providers/skills-sh.js +21 -147
- package/dist/registry/providers/static-index.js +15 -157
- package/dist/registry/resolve.js +22 -9
- package/dist/runtime.js +25 -1
- package/dist/scripts/migrate-storage.js +2617 -1961
- package/dist/scripts/migrations/import-fs-improve-runs-to-db.js +759 -510
- package/dist/setup/setup.js +29 -8
- package/dist/sources/include.js +6 -2
- package/dist/sources/providers/filesystem.js +0 -1
- package/dist/sources/providers/git-install.js +210 -0
- package/dist/sources/providers/git-provider.js +234 -0
- package/dist/sources/providers/git-stash.js +248 -0
- package/dist/sources/providers/git.js +10 -661
- package/dist/sources/providers/npm.js +2 -6
- package/dist/sources/providers/provider-utils.js +13 -7
- package/dist/sources/providers/sync-from-ref.js +9 -1
- package/dist/sources/providers/tar-utils.js +16 -8
- package/dist/sources/providers/website.js +9 -5
- package/dist/sources/website-ingest.js +187 -29
- package/dist/sources/wiki-fetchers/registry.js +53 -0
- package/dist/sources/wiki-fetchers/youtube.js +239 -0
- package/dist/storage/database.js +45 -10
- package/dist/storage/managed-db.js +82 -0
- package/dist/storage/repositories/registry-cache.js +92 -0
- package/dist/storage/sqlite-pragmas.js +146 -0
- package/dist/tasks/backends/cron.js +1 -1
- package/dist/tasks/backends/launchd.js +1 -1
- package/dist/tasks/backends/schtasks.js +1 -1
- package/dist/tasks/{resolveAkmBin.js → resolve-akm-bin.js} +2 -2
- package/dist/tasks/runner.js +5 -13
- package/dist/text-import-hook.mjs +0 -0
- package/dist/wiki/wiki.js +37 -0
- package/dist/workflows/db.js +3 -4
- package/dist/workflows/runtime/runs.js +1 -117
- package/dist/workflows/runtime/workflow-asset-loader.js +125 -0
- package/dist/workflows/validate-summary.js +2 -7
- package/docs/data-and-telemetry.md +3 -2
- package/docs/migration/release-notes/0.9.0.md +39 -0
- package/package.json +13 -11
- package/dist/commands/db-cli.js +0 -23
- package/dist/indexer/db/db-backup.js +0 -376
|
@@ -50,7 +50,8 @@ import { makeAssetRef, parseAssetRef } from "../../../core/asset/asset-ref.js";
|
|
|
50
50
|
import { resolveAssetPathFromName, TYPE_DIRS } from "../../../core/asset/asset-spec.js";
|
|
51
51
|
import { NotFoundError, UsageError } from "../../../core/errors.js";
|
|
52
52
|
import { appendEvent } from "../../../core/events.js";
|
|
53
|
-
import {
|
|
53
|
+
import { getStateProposal, hasImportedFsProposals, insertProposalIfAbsent, listStateProposalIdsByPrefix, listStateProposals, recordFsProposalsImport, upsertProposal, withImmediateTransaction, withStateDb, } from "../../../core/state-db.js";
|
|
54
|
+
import { repairTruncatedDescription } from "../../../core/text-truncation.js";
|
|
54
55
|
import { warn } from "../../../core/warn.js";
|
|
55
56
|
import { commitWriteTargetBoundary, formatRefForMessage, resolveWriteTarget, writeAssetToSource, } from "../../../core/write-source.js";
|
|
56
57
|
import { runProposalValidators } from "./proposal-validators.js";
|
|
@@ -74,6 +75,8 @@ export const PROPOSAL_SOURCES = [
|
|
|
74
75
|
"consolidate",
|
|
75
76
|
"extract",
|
|
76
77
|
"improve",
|
|
78
|
+
"recombine",
|
|
79
|
+
"procedural",
|
|
77
80
|
// Semi-automated / tool-driven.
|
|
78
81
|
"feedback",
|
|
79
82
|
// Human-initiated / CLI-driven.
|
|
@@ -91,6 +94,8 @@ export const AUTOMATED_PROPOSAL_SOURCES = [
|
|
|
91
94
|
"consolidate",
|
|
92
95
|
"extract",
|
|
93
96
|
"improve",
|
|
97
|
+
"recombine",
|
|
98
|
+
"procedural",
|
|
94
99
|
"schema-repair",
|
|
95
100
|
];
|
|
96
101
|
/**
|
|
@@ -156,14 +161,10 @@ function newId(ctx) {
|
|
|
156
161
|
* guaranteed to have run before any read or write.
|
|
157
162
|
*/
|
|
158
163
|
function withProposalsDb(stashDir, ctx, fn) {
|
|
159
|
-
|
|
160
|
-
try {
|
|
164
|
+
return withStateDb((db) => {
|
|
161
165
|
importLegacyProposalFiles(db, stashDir);
|
|
162
166
|
return fn(db);
|
|
163
|
-
}
|
|
164
|
-
finally {
|
|
165
|
-
db.close();
|
|
166
|
-
}
|
|
167
|
+
}, { path: ctx?.dbPath });
|
|
167
168
|
}
|
|
168
169
|
// ── Legacy filesystem import (#578) ─────────────────────────────────────────
|
|
169
170
|
/** Legacy (pre-0.9.0) proposal directory: `<stashDir>/.akm/proposals[/archive]`. */
|
|
@@ -334,37 +335,42 @@ export function createProposal(stashDir, input, ctx) {
|
|
|
334
335
|
}
|
|
335
336
|
const normalizedRef = makeAssetRef(parsedRef.type, parsedRef.name, parsedRef.origin);
|
|
336
337
|
return withProposalsDb(stashDir, ctx, (db) => {
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
338
|
+
return withImmediateTransaction(db, () => {
|
|
339
|
+
if (!input.force) {
|
|
340
|
+
const skip = checkDedupAndCooldown(db, stashDir, normalizedRef, input, ctx);
|
|
341
|
+
if (skip)
|
|
342
|
+
return skip;
|
|
343
|
+
}
|
|
344
|
+
const created = nowIso(ctx);
|
|
345
|
+
// Phase 6A: validate confidence is a finite number in [0, 1]. Anything else
|
|
346
|
+
// is dropped silently — we never store NaN, Infinity, or out-of-range values.
|
|
347
|
+
// Callers that mis-report confidence should not poison the auto-accept gate.
|
|
348
|
+
const sanitizedConfidence = typeof input.confidence === "number" &&
|
|
349
|
+
Number.isFinite(input.confidence) &&
|
|
350
|
+
input.confidence >= 0 &&
|
|
351
|
+
input.confidence <= 1
|
|
352
|
+
? input.confidence
|
|
353
|
+
: undefined;
|
|
354
|
+
const proposal = {
|
|
355
|
+
id: newId(ctx),
|
|
356
|
+
ref: normalizedRef,
|
|
357
|
+
status: "pending",
|
|
358
|
+
source: input.source,
|
|
359
|
+
...(input.sourceRun !== undefined ? { sourceRun: input.sourceRun } : {}),
|
|
360
|
+
createdAt: created,
|
|
361
|
+
updatedAt: created,
|
|
362
|
+
payload: {
|
|
363
|
+
content: input.payload.content,
|
|
364
|
+
...(input.payload.frontmatter !== undefined ? { frontmatter: input.payload.frontmatter } : {}),
|
|
365
|
+
},
|
|
366
|
+
...(sanitizedConfidence !== undefined ? { confidence: sanitizedConfidence } : {}),
|
|
367
|
+
// Attribution tagging: persist the eligibility lane so it survives to
|
|
368
|
+
// accept/reject/revert time. See EligibilitySource.
|
|
369
|
+
...(input.eligibilitySource !== undefined ? { eligibilitySource: input.eligibilitySource } : {}),
|
|
370
|
+
};
|
|
371
|
+
upsertProposal(db, proposal, stashDir);
|
|
372
|
+
return proposal;
|
|
373
|
+
});
|
|
368
374
|
});
|
|
369
375
|
}
|
|
370
376
|
/**
|
|
@@ -517,19 +523,25 @@ export function resolveProposalId(stashDir, idOrRef, ctx) {
|
|
|
517
523
|
*/
|
|
518
524
|
export function archiveProposal(stashDir, id, status, reason, ctx) {
|
|
519
525
|
return withProposalsDb(stashDir, ctx, (db) => {
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
...
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
526
|
+
return withImmediateTransaction(db, () => {
|
|
527
|
+
const existing = requireProposal(db, stashDir, id);
|
|
528
|
+
if (existing.status !== "pending") {
|
|
529
|
+
throw new UsageError(`Proposal ${id} is not pending (current status: ${existing.status}). Only pending proposals can be ${status}.`, "INVALID_FLAG_VALUE");
|
|
530
|
+
}
|
|
531
|
+
const decidedAt = nowIso(ctx);
|
|
532
|
+
const updated = {
|
|
533
|
+
...existing,
|
|
534
|
+
status,
|
|
535
|
+
updatedAt: decidedAt,
|
|
536
|
+
review: {
|
|
537
|
+
outcome: status,
|
|
538
|
+
...(reason !== undefined ? { reason } : {}),
|
|
539
|
+
decidedAt,
|
|
540
|
+
},
|
|
541
|
+
};
|
|
542
|
+
upsertProposal(db, updated, stashDir);
|
|
543
|
+
return updated;
|
|
544
|
+
});
|
|
533
545
|
});
|
|
534
546
|
}
|
|
535
547
|
/**
|
|
@@ -548,15 +560,17 @@ export function archiveProposal(stashDir, id, status, reason, ctx) {
|
|
|
548
560
|
*/
|
|
549
561
|
export function recordGateDecision(stashDir, id, decision, ctx) {
|
|
550
562
|
return withProposalsDb(stashDir, ctx, (db) => {
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
563
|
+
return withImmediateTransaction(db, () => {
|
|
564
|
+
const existing = getStateProposal(db, id, stashDir);
|
|
565
|
+
if (!existing || existing.status !== "pending")
|
|
566
|
+
return undefined;
|
|
567
|
+
const updated = {
|
|
568
|
+
...existing,
|
|
569
|
+
gateDecision: { ...decision, decidedAt: decision.decidedAt ?? nowIso(ctx) },
|
|
570
|
+
};
|
|
571
|
+
upsertProposal(db, updated, stashDir);
|
|
572
|
+
return updated;
|
|
573
|
+
});
|
|
560
574
|
});
|
|
561
575
|
}
|
|
562
576
|
/**
|
|
@@ -699,6 +713,99 @@ export function expireStaleProposals(stashDir, config, ctx) {
|
|
|
699
713
|
export function validateProposal(proposal) {
|
|
700
714
|
return runProposalValidators(proposal);
|
|
701
715
|
}
|
|
716
|
+
// ── Content repair ──────────────────────────────────────────────────────────
|
|
717
|
+
/**
|
|
718
|
+
* Attempt bounded, deterministic repair of mechanically-fixable defects in a
|
|
719
|
+
* proposal's markdown content. NEVER fabricates text — only strips known-bad
|
|
720
|
+
* structure and applies {@link repairTruncatedDescription} to a truncated
|
|
721
|
+
* description when one is detected.
|
|
722
|
+
*
|
|
723
|
+
* Repairs performed (in order):
|
|
724
|
+
* 1. Strip body lines that restate frontmatter fields as pseudo-frontmatter
|
|
725
|
+
* (e.g. `**description**: …` or `when_to_use: …` in the body).
|
|
726
|
+
* 2. Remove stray body `---` horizontal-rule lines (leaving exactly the two
|
|
727
|
+
* frontmatter fences when the content has a valid frontmatter block).
|
|
728
|
+
* 3. Apply {@link repairTruncatedDescription} to a truncated/hanging
|
|
729
|
+
* `description` field in the frontmatter.
|
|
730
|
+
*
|
|
731
|
+
* Returns the repaired content string. When no repairs apply the input is
|
|
732
|
+
* returned byte-identical so callers can use strict equality to detect
|
|
733
|
+
* whether a repair actually happened.
|
|
734
|
+
*
|
|
735
|
+
* CRITICAL: This function is CONTENT-PRESERVING. Callers MUST re-validate the
|
|
736
|
+
* repaired output via {@link validateProposal} / {@link runProposalValidators}
|
|
737
|
+
* before promotion — a repair that makes things *worse* (or is simply
|
|
738
|
+
* insufficient) must be caught by the existing gate.
|
|
739
|
+
*/
|
|
740
|
+
export function repairProposalContent(content) {
|
|
741
|
+
if (typeof content !== "string" || content.trim() === "")
|
|
742
|
+
return content;
|
|
743
|
+
// Determine whether the content has a frontmatter block so we know how
|
|
744
|
+
// many `---` fence lines are expected.
|
|
745
|
+
const hasFrontmatter = /^---\r?\n[\s\S]*?\r?\n---/.test(content);
|
|
746
|
+
// Split into lines for structural repairs.
|
|
747
|
+
const lines = content.split(/\r?\n/);
|
|
748
|
+
// Track whether we are inside the opening frontmatter block so we can
|
|
749
|
+
// leave it untouched and only repair the body.
|
|
750
|
+
let inFrontmatter = false;
|
|
751
|
+
// Frontmatter fence index tracking: first fence opens FM, second closes it.
|
|
752
|
+
let fmOpenSeen = false;
|
|
753
|
+
let fmCloseSeen = false;
|
|
754
|
+
const repairedLines = [];
|
|
755
|
+
for (const line of lines) {
|
|
756
|
+
const isFence = /^---\s*$/.test(line);
|
|
757
|
+
// Track frontmatter fences (first two `---` fences delimit the FM block).
|
|
758
|
+
if (isFence && !fmCloseSeen) {
|
|
759
|
+
if (!fmOpenSeen) {
|
|
760
|
+
fmOpenSeen = true;
|
|
761
|
+
inFrontmatter = true;
|
|
762
|
+
repairedLines.push(line);
|
|
763
|
+
continue;
|
|
764
|
+
}
|
|
765
|
+
if (inFrontmatter) {
|
|
766
|
+
fmCloseSeen = true;
|
|
767
|
+
inFrontmatter = false;
|
|
768
|
+
repairedLines.push(line);
|
|
769
|
+
continue;
|
|
770
|
+
}
|
|
771
|
+
}
|
|
772
|
+
// We are now in the body (past the frontmatter or no frontmatter).
|
|
773
|
+
if (inFrontmatter) {
|
|
774
|
+
// Still inside the frontmatter — keep as-is.
|
|
775
|
+
repairedLines.push(line);
|
|
776
|
+
continue;
|
|
777
|
+
}
|
|
778
|
+
// Repair 1: Strip pseudo-frontmatter restatements in the body.
|
|
779
|
+
// Matches lines like `**description**: …` or `when_to_use: …`.
|
|
780
|
+
if (/^\s*(\*\*|__)?\s*(description|when_to_use)\s*(\*\*|__)?\s*:/i.test(line)) {
|
|
781
|
+
// Drop the line — it is a structural defect, not user content.
|
|
782
|
+
continue;
|
|
783
|
+
}
|
|
784
|
+
// Repair 2: Remove stray `---` horizontal-rule lines in the body.
|
|
785
|
+
// We keep these only when the content has NO frontmatter (in that case
|
|
786
|
+
// `---` is a legitimate thematic break in plain-body content).
|
|
787
|
+
if (isFence && hasFrontmatter) {
|
|
788
|
+
// Drop: these are extra `---` fences beyond the two frontmatter delimiters.
|
|
789
|
+
continue;
|
|
790
|
+
}
|
|
791
|
+
repairedLines.push(line);
|
|
792
|
+
}
|
|
793
|
+
let repaired = repairedLines.join("\n");
|
|
794
|
+
// Repair 3: Apply repairTruncatedDescription to the description field.
|
|
795
|
+
// We operate on the raw text rather than re-parsing YAML to avoid
|
|
796
|
+
// reformatting unrelated frontmatter keys.
|
|
797
|
+
if (hasFrontmatter) {
|
|
798
|
+
// Extract the body text (after the second `---`) so we can pass it to
|
|
799
|
+
// repairTruncatedDescription as context for the swap-in heuristic.
|
|
800
|
+
const bodyMatch = repaired.match(/^---\r?\n[\s\S]*?\r?\n---\r?\n?([\s\S]*)$/);
|
|
801
|
+
const bodyText = bodyMatch?.[1] ?? "";
|
|
802
|
+
repaired = repaired.replace(/^(description:\s*)(.*?)(\r?\n)/m, (_match, prefix, rawDesc, nl) => {
|
|
803
|
+
const fixed = repairTruncatedDescription(rawDesc.trim(), bodyText);
|
|
804
|
+
return `${prefix}${fixed}${nl}`;
|
|
805
|
+
});
|
|
806
|
+
}
|
|
807
|
+
return repaired;
|
|
808
|
+
}
|
|
702
809
|
/**
|
|
703
810
|
* Validate a proposal, then promote it through the canonical
|
|
704
811
|
* {@link writeAssetToSource} dispatch (the single place that branches on
|
|
@@ -716,12 +823,31 @@ export async function promoteProposal(stashDir, config, id, options = {}, ctx) {
|
|
|
716
823
|
if (proposal.status !== "pending") {
|
|
717
824
|
throw new UsageError(`Proposal ${id} is not pending (current status: ${proposal.status}). Only pending proposals can be accepted.`, "INVALID_FLAG_VALUE");
|
|
718
825
|
}
|
|
719
|
-
|
|
826
|
+
// Attempt bounded auto-repair of mechanically-fixable structural defects
|
|
827
|
+
// (pseudo-frontmatter-in-body, stray `---` fences, truncated description)
|
|
828
|
+
// BEFORE running validation. If the repair produces valid content, we
|
|
829
|
+
// promote the repaired version; if validation still fails, the original
|
|
830
|
+
// error path throws as before. The repair is content-preserving and
|
|
831
|
+
// deterministic — it never invents text.
|
|
832
|
+
const repairedContent = repairProposalContent(proposal.payload.content);
|
|
833
|
+
const proposalToValidate = repairedContent !== proposal.payload.content
|
|
834
|
+
? { ...proposal, payload: { ...proposal.payload, content: repairedContent } }
|
|
835
|
+
: proposal;
|
|
836
|
+
const report = validateProposal(proposalToValidate);
|
|
720
837
|
if (!report.ok) {
|
|
721
838
|
const message = report.findings.map((f) => `[${f.kind}] ${f.message}`).join("\n");
|
|
722
839
|
throw new UsageError(`Proposal ${id} failed validation:\n${message}`, "MISSING_REQUIRED_ARGUMENT", "Fix the proposal payload (frontmatter / content) and try again, or reject the proposal with a reason.");
|
|
723
840
|
}
|
|
724
|
-
|
|
841
|
+
// Use the (possibly repaired) payload for the promotion write. Persist the
|
|
842
|
+
// repaired content back onto the DB row so the audit trail reflects the
|
|
843
|
+
// final promoted payload (not the defective original).
|
|
844
|
+
if (repairedContent !== proposal.payload.content) {
|
|
845
|
+
withProposalsDb(stashDir, ctx, (db) => {
|
|
846
|
+
const updated = { ...proposal, payload: { ...proposal.payload, content: repairedContent } };
|
|
847
|
+
upsertProposal(db, updated, stashDir);
|
|
848
|
+
});
|
|
849
|
+
}
|
|
850
|
+
const ref = parseAssetRef(proposalToValidate.ref);
|
|
725
851
|
if (!TYPE_DIRS[ref.type]) {
|
|
726
852
|
throw new UsageError(`Proposal ${id} targets unknown asset type "${ref.type}".`, "INVALID_FLAG_VALUE");
|
|
727
853
|
}
|
|
@@ -743,7 +869,7 @@ export async function promoteProposal(stashDir, config, id, options = {}, ctx) {
|
|
|
743
869
|
// missing-revert path is visible.
|
|
744
870
|
warn(`[proposals] promoteProposal: failed to capture backup for ${id}: ${err instanceof Error ? err.message : String(err)}`);
|
|
745
871
|
}
|
|
746
|
-
const written = await writeAssetToSource(target.source, target.config, ref,
|
|
872
|
+
const written = await writeAssetToSource(target.source, target.config, ref, repairedContent);
|
|
747
873
|
// 0.9.0 (issue #507): single batch commit at the write boundary for git
|
|
748
874
|
// targets. No-op for filesystem/primary-stash targets.
|
|
749
875
|
commitWriteTargetBoundary(target, `Update ${formatRefForMessage(ref)}`);
|