mdkg 0.3.3 → 0.3.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 +39 -0
- package/CLI_COMMAND_MATRIX.md +20 -9
- package/README.md +8 -4
- package/dist/cli.js +52 -8
- package/dist/command-contract.json +237 -9
- package/dist/commands/fix.js +468 -16
- package/dist/init/CLI_COMMAND_MATRIX.md +9 -3
- package/dist/init/README.md +7 -2
- package/dist/init/init-manifest.json +3 -3
- package/package.json +3 -2
package/CHANGELOG.md
CHANGED
|
@@ -12,6 +12,45 @@ mdkg is pre-v1 public alpha software. Command, graph, cache, bundle, and DAL con
|
|
|
12
12
|
|
|
13
13
|
- No changes yet.
|
|
14
14
|
|
|
15
|
+
## 0.3.4 - 2026-06-17
|
|
16
|
+
|
|
17
|
+
### Added
|
|
18
|
+
|
|
19
|
+
- Added IDs-family repair apply support with `mdkg fix apply --family ids`
|
|
20
|
+
and the focused `mdkg fix ids [--apply]` convenience command.
|
|
21
|
+
- Added `--base-ref` support for duplicate-ID repair planning so mainline IDs
|
|
22
|
+
can be preserved while incoming duplicate nodes receive the next unused
|
|
23
|
+
canonical numeric ID.
|
|
24
|
+
- Added unresolved Git add/add conflict-stage repair for mdkg Markdown files:
|
|
25
|
+
stage 2 remains at the conflicted path, stage 3 is rewritten to a new
|
|
26
|
+
canonical ID/path, and the Git index conflict stages are resolved with a
|
|
27
|
+
receipt.
|
|
28
|
+
- Added packed `smoke:id-repair` coverage that installs mdkg from a tarball in
|
|
29
|
+
a temp prefix, validates clean duplicate repair, base-ref link preservation,
|
|
30
|
+
unresolved Git conflict-stage repair, and final graph validation.
|
|
31
|
+
|
|
32
|
+
### Changed
|
|
33
|
+
|
|
34
|
+
- Updated `mdkg fix plan` receipts so duplicate-ID findings advertise
|
|
35
|
+
`apply_supported: true` with an explicit `apply_kind`, while index/cache and
|
|
36
|
+
graph-reference findings remain review-only.
|
|
37
|
+
- Updated command help, README, init assets, command matrix, generated command
|
|
38
|
+
contract metadata, and publish-readiness assertions to document the new
|
|
39
|
+
IDs-only apply boundary.
|
|
40
|
+
- Updated branch-conflict and fix-plan smokes to distinguish non-mutating plan
|
|
41
|
+
behavior from the newly apply-capable duplicate-ID repair family.
|
|
42
|
+
|
|
43
|
+
### Security
|
|
44
|
+
|
|
45
|
+
- `fix apply` refuses unsupported families, blocked plans, and non-IDs repair
|
|
46
|
+
findings instead of silently applying partial graph/reference/index repairs.
|
|
47
|
+
- Duplicate-ID apply writes Markdown atomically under the mdkg mutation lock,
|
|
48
|
+
rebuilds derived indexes, and emits receipt evidence with touched paths,
|
|
49
|
+
source plan hash, and ambiguous reference notes.
|
|
50
|
+
- Base-ref link rewriting is conservative: references in files absent from the
|
|
51
|
+
base ref can be rewritten to the repaired incoming ID, while base-existing
|
|
52
|
+
references remain on the mainline ID and ambiguous references are reported.
|
|
53
|
+
|
|
15
54
|
## 0.3.3 - 2026-06-16
|
|
16
55
|
|
|
17
56
|
### Added
|
package/CLI_COMMAND_MATRIX.md
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# CLI Command Matrix
|
|
2
2
|
|
|
3
3
|
as_of: 2026-06-06
|
|
4
|
-
package_version_in_source: 0.3.
|
|
4
|
+
package_version_in_source: 0.3.4
|
|
5
5
|
source: live help from `src/cli.ts`, runtime command handlers, and `dec-15`..`dec-18`
|
|
6
6
|
status: canonical single-source command and flag reference for mdkg
|
|
7
7
|
|
|
@@ -61,7 +61,7 @@ Recursive long-running objective contracts are accessed through `mdkg goal ...`.
|
|
|
61
61
|
Fresh init workspaces default to the SQLite access cache backend; existing migrated configs stay on JSON until opted in.
|
|
62
62
|
Project application database foundation commands are accessed through `mdkg db ...`; `mdkg index` remains the compatibility shortcut for graph index rebuilds.
|
|
63
63
|
Operator health summaries are accessed through read-only `mdkg status ...`; deeper diagnostics remain under `mdkg doctor ...`.
|
|
64
|
-
Repair planning is accessed through read-only `mdkg fix plan ...`; apply
|
|
64
|
+
Repair planning is accessed through read-only `mdkg fix plan ...`; duplicate-ID graph repairs can be applied through `mdkg fix apply --family ids ...` or the convenience `mdkg fix ids --apply ...`. Index/cache and graph-reference findings remain plan/manual-review only.
|
|
65
65
|
|
|
66
66
|
## Global usage
|
|
67
67
|
|
|
@@ -1049,27 +1049,38 @@ JSON receipt shape:
|
|
|
1049
1049
|
### `mdkg fix`
|
|
1050
1050
|
|
|
1051
1051
|
When to use:
|
|
1052
|
-
- plan reviewable graph/index repairs before
|
|
1052
|
+
- plan reviewable graph/index repairs before applying supported duplicate-ID rewrites
|
|
1053
1053
|
- get a receipt-shaped JSON plan for automation and agent review
|
|
1054
|
+
- repair branch-merge duplicate IDs while preserving main/base IDs where possible
|
|
1054
1055
|
|
|
1055
1056
|
Usage:
|
|
1056
|
-
- `mdkg fix plan [--family index|refs|ids|all] [--target <id-or-qid>] [--json]`
|
|
1057
|
+
- `mdkg fix plan [--family index|refs|ids|all] [--target <id-or-qid>] [--base-ref <ref>] [--json]`
|
|
1058
|
+
- `mdkg fix apply [--family ids] [--target <id-or-qid>] [--base-ref <ref>] [--json]`
|
|
1059
|
+
- `mdkg fix ids [--target <id-or-qid>] [--base-ref <ref>] [--apply] [--json]`
|
|
1057
1060
|
|
|
1058
1061
|
Flags:
|
|
1059
1062
|
- `--family <family>`
|
|
1060
1063
|
- `--target <id-or-qid>`
|
|
1064
|
+
- `--base-ref <ref>`
|
|
1065
|
+
- `--apply`
|
|
1061
1066
|
- `--json`
|
|
1062
1067
|
|
|
1063
1068
|
Boundaries:
|
|
1064
|
-
- dry-run only and writes nothing
|
|
1065
|
-
-
|
|
1066
|
-
- `fix apply` is
|
|
1069
|
+
- `fix plan` is dry-run only and writes nothing
|
|
1070
|
+
- `fix apply` currently supports only IDs-family duplicate-ID graph rewrites
|
|
1071
|
+
- `fix ids` without `--apply` is equivalent to `fix plan --family ids`
|
|
1072
|
+
- `fix ids --apply` is equivalent to `fix apply --family ids`
|
|
1073
|
+
- apply rewrites graph Markdown atomically, rebuilds derived indexes, and emits a receipt
|
|
1074
|
+
- unresolved Git add/add conflict stages are handled by keeping stage 2 at the conflicted path and writing stage 3 to a new canonical ID/path
|
|
1075
|
+
- graph-reference and index/cache findings remain review-only guidance
|
|
1067
1076
|
- initial families are index/cache, graph refs, and duplicate ids
|
|
1068
1077
|
|
|
1069
1078
|
JSON receipt:
|
|
1070
1079
|
- `{ action: "fix.plan", ok, schema_version, plan_id, plan_hash, generated_at, root, family, target, dirty, families, risk_counts, proposed_changes, blocked_changes, summary }`
|
|
1071
|
-
- each proposed change includes family, risk, status, reason, paths, refs, optional before/after values, command hint, and `apply_supported
|
|
1072
|
-
-
|
|
1080
|
+
- each proposed change includes family, risk, status, reason, paths, refs, optional before/after values, command hint, and `apply_supported`
|
|
1081
|
+
- duplicate-ID changes include candidate ID/path details and `apply_kind`
|
|
1082
|
+
- `{ action: "fix.apply", ok, schema_version, receipt_hash, root, family, target, base_ref, plan_id, plan_hash, applied_changes, touched_paths, ambiguous_reference_rewrites, index, summary }`
|
|
1083
|
+
- `summary.apply_deferred` remains true when the selected plan includes index/cache, graph-ref, blocked, or otherwise unsupported findings
|
|
1073
1084
|
|
|
1074
1085
|
### `mdkg doctor`
|
|
1075
1086
|
|
package/README.md
CHANGED
|
@@ -14,7 +14,7 @@ mdkg stays deliberately boring:
|
|
|
14
14
|
- first-class rebuildable SQLite cache through built-in `node:sqlite`
|
|
15
15
|
- no daemon, hosted index, or vector DB
|
|
16
16
|
|
|
17
|
-
Current package version in source: `0.3.
|
|
17
|
+
Current package version in source: `0.3.4`
|
|
18
18
|
|
|
19
19
|
mdkg is still pre-v1 public alpha software. The public package is usable, but graph, cache, bundle, and DAL contracts may continue to change quickly while the project converges on a stable v1 surface.
|
|
20
20
|
|
|
@@ -341,9 +341,13 @@ warnings unless their underlying check fails.
|
|
|
341
341
|
Use `mdkg fix plan --json` when you want repair guidance without mutation. It
|
|
342
342
|
emits a receipt-shaped plan for generated index/cache repair, missing graph
|
|
343
343
|
references, and duplicate local ids. Planned changes include affected paths,
|
|
344
|
-
risk, reason codes, command hints, and `apply_supported
|
|
345
|
-
|
|
346
|
-
|
|
344
|
+
risk, reason codes, command hints, and per-change `apply_supported` metadata.
|
|
345
|
+
Duplicate-ID graph repairs can be applied with
|
|
346
|
+
`mdkg fix apply --family ids --json` or `mdkg fix ids --apply --json`; use
|
|
347
|
+
`--base-ref main` when mainline IDs should win. Index/cache and graph-reference
|
|
348
|
+
findings remain review-only. For unresolved Git add/add conflicts, `fix ids`
|
|
349
|
+
keeps stage 2 at the conflicted path, rewrites stage 3 to the next unused
|
|
350
|
+
canonical ID/path, and records a receipt.
|
|
347
351
|
|
|
348
352
|
## Skills
|
|
349
353
|
|
package/dist/cli.js
CHANGED
|
@@ -886,24 +886,58 @@ function printFixHelp(log, subcommand) {
|
|
|
886
886
|
switch ((subcommand ?? "").toLowerCase()) {
|
|
887
887
|
case "plan":
|
|
888
888
|
log("Usage:");
|
|
889
|
-
log(" mdkg fix plan [--family index|refs|ids|all] [--target <id-or-qid>] [--json]");
|
|
889
|
+
log(" mdkg fix plan [--family index|refs|ids|all] [--target <id-or-qid>] [--base-ref <ref>] [--json]");
|
|
890
890
|
log("\nBoundaries:");
|
|
891
891
|
log(" - read-only repair planning; writes no files and does not rebuild indexes");
|
|
892
892
|
log(" - emits a deterministic receipt-shaped JSON plan with paths, risks, and reason codes");
|
|
893
893
|
log(" - initial families are index/cache, graph refs, and duplicate ids");
|
|
894
|
-
log(" -
|
|
894
|
+
log(" - ids-family duplicate-id repairs can be applied with `mdkg fix apply --family ids`");
|
|
895
|
+
log(" - index/cache and graph-ref findings remain review-only guidance");
|
|
895
896
|
log("\nOptions:");
|
|
896
897
|
log(" --family <family> Select index, refs, ids, or all (default all)");
|
|
897
898
|
log(" --target <id-or-qid> Optional node target for family planners");
|
|
899
|
+
log(" --base-ref <ref> Prefer IDs that already exist at a Git base ref");
|
|
900
|
+
log(" --json Emit machine-readable JSON output");
|
|
901
|
+
printGlobalOptions(log);
|
|
902
|
+
return;
|
|
903
|
+
case "apply":
|
|
904
|
+
log("Usage:");
|
|
905
|
+
log(" mdkg fix apply [--family ids] [--target <id-or-qid>] [--base-ref <ref>] [--json]");
|
|
906
|
+
log("\nBoundaries:");
|
|
907
|
+
log(" - applies only supported ids-family duplicate-ID rewrites");
|
|
908
|
+
log(" - refuses index/cache, graph-ref, all-family, blocked, and unsupported repairs");
|
|
909
|
+
log(" - writes graph Markdown atomically and rebuilds derived indexes");
|
|
910
|
+
log(" - emits a receipt with plan hash, touched paths, and manual-review reference notes");
|
|
911
|
+
log("\nOptions:");
|
|
912
|
+
log(" --family ids Explicit apply family; ids is the only supported apply family");
|
|
913
|
+
log(" --target <id-or-qid> Optional duplicate ID target");
|
|
914
|
+
log(" --base-ref <ref> Prefer IDs that already exist at a Git base ref");
|
|
915
|
+
log(" --json Emit machine-readable JSON output");
|
|
916
|
+
printGlobalOptions(log);
|
|
917
|
+
return;
|
|
918
|
+
case "ids":
|
|
919
|
+
log("Usage:");
|
|
920
|
+
log(" mdkg fix ids [--target <id-or-qid>] [--base-ref <ref>] [--apply] [--json]");
|
|
921
|
+
log("\nBoundaries:");
|
|
922
|
+
log(" - convenience command for duplicate-ID planning and application");
|
|
923
|
+
log(" - without --apply it is equivalent to `mdkg fix plan --family ids`");
|
|
924
|
+
log(" - with --apply it is equivalent to `mdkg fix apply --family ids`");
|
|
925
|
+
log("\nOptions:");
|
|
926
|
+
log(" --target <id-or-qid> Optional duplicate ID target");
|
|
927
|
+
log(" --base-ref <ref> Prefer IDs that already exist at a Git base ref");
|
|
928
|
+
log(" --apply Apply supported duplicate-ID rewrites");
|
|
898
929
|
log(" --json Emit machine-readable JSON output");
|
|
899
930
|
printGlobalOptions(log);
|
|
900
931
|
return;
|
|
901
932
|
default:
|
|
902
933
|
log("Usage:");
|
|
903
|
-
log(" mdkg fix plan [--family index|refs|ids|all] [--target <id-or-qid>] [--json]");
|
|
934
|
+
log(" mdkg fix plan [--family index|refs|ids|all] [--target <id-or-qid>] [--base-ref <ref>] [--json]");
|
|
935
|
+
log(" mdkg fix apply [--family ids] [--target <id-or-qid>] [--base-ref <ref>] [--json]");
|
|
936
|
+
log(" mdkg fix ids [--target <id-or-qid>] [--base-ref <ref>] [--apply] [--json]");
|
|
904
937
|
log("\nNotes:");
|
|
905
|
-
log(" - fix
|
|
906
|
-
log(" - apply
|
|
938
|
+
log(" - fix plan is dry-run only and writes nothing");
|
|
939
|
+
log(" - fix apply is limited to duplicate-ID graph repairs with receipt evidence");
|
|
940
|
+
log(" - index/cache and graph-ref repairs remain plan/manual-review only");
|
|
907
941
|
printGlobalOptions(log);
|
|
908
942
|
}
|
|
909
943
|
}
|
|
@@ -2670,16 +2704,26 @@ function runCommand(parsed, root, runtime) {
|
|
|
2670
2704
|
if (!sub) {
|
|
2671
2705
|
throw new errors_1.UsageError("fix requires a subcommand");
|
|
2672
2706
|
}
|
|
2673
|
-
if (
|
|
2707
|
+
if (!["plan", "apply", "ids"].includes(sub)) {
|
|
2674
2708
|
throw new errors_1.UsageError(`unknown fix subcommand: ${sub}`);
|
|
2675
2709
|
}
|
|
2676
2710
|
if (parsed.positionals.length > 2) {
|
|
2677
|
-
throw new errors_1.UsageError(
|
|
2711
|
+
throw new errors_1.UsageError(`fix ${sub} does not accept positional arguments`);
|
|
2678
2712
|
}
|
|
2679
2713
|
const family = requireFlagValue("--family", parsed.flags["--family"]);
|
|
2680
2714
|
const target = requireFlagValue("--target", parsed.flags["--target"]);
|
|
2715
|
+
const baseRef = requireFlagValue("--base-ref", parsed.flags["--base-ref"]);
|
|
2681
2716
|
const json = parseBooleanFlag("--json", parsed.flags["--json"]);
|
|
2682
|
-
|
|
2717
|
+
if (sub === "plan") {
|
|
2718
|
+
(0, fix_1.runFixPlanCommand)({ root, family, target, baseRef, json });
|
|
2719
|
+
return 0;
|
|
2720
|
+
}
|
|
2721
|
+
if (sub === "apply") {
|
|
2722
|
+
(0, fix_1.runFixApplyCommand)({ root, family, target, baseRef, json });
|
|
2723
|
+
return 0;
|
|
2724
|
+
}
|
|
2725
|
+
const apply = parseBooleanFlag("--apply", parsed.flags["--apply"]);
|
|
2726
|
+
(0, fix_1.runFixIdsCommand)({ root, target, baseRef, json, apply });
|
|
2683
2727
|
return 0;
|
|
2684
2728
|
}
|
|
2685
2729
|
case "format":
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"schema_version": 1,
|
|
3
3
|
"tool": "mdkg",
|
|
4
|
-
"package_version": "0.3.
|
|
4
|
+
"package_version": "0.3.4",
|
|
5
5
|
"source": {
|
|
6
6
|
"help_targets": "scripts/cli_help_targets.js",
|
|
7
7
|
"command_matrix": "CLI_COMMAND_MATRIX.md"
|
|
@@ -2050,7 +2050,9 @@
|
|
|
2050
2050
|
"visibility": "public",
|
|
2051
2051
|
"summary": "mdkg fix command",
|
|
2052
2052
|
"usage": [
|
|
2053
|
-
" mdkg fix plan [--family index|refs|ids|all] [--target <id-or-qid>] [--json]"
|
|
2053
|
+
" mdkg fix plan [--family index|refs|ids|all] [--target <id-or-qid>] [--base-ref <ref>] [--json]",
|
|
2054
|
+
" mdkg fix apply [--family ids] [--target <id-or-qid>] [--base-ref <ref>] [--json]",
|
|
2055
|
+
" mdkg fix ids [--target <id-or-qid>] [--base-ref <ref>] [--apply] [--json]"
|
|
2054
2056
|
],
|
|
2055
2057
|
"args": [],
|
|
2056
2058
|
"flags": [
|
|
@@ -2104,13 +2106,232 @@
|
|
|
2104
2106
|
}
|
|
2105
2107
|
],
|
|
2106
2108
|
"examples": [
|
|
2107
|
-
" mdkg fix
|
|
2109
|
+
" mdkg fix apply [--family ids] [--target <id-or-qid>] [--base-ref <ref>] [--json]",
|
|
2110
|
+
" mdkg fix ids [--target <id-or-qid>] [--base-ref <ref>] [--apply] [--json]",
|
|
2111
|
+
" mdkg fix plan [--family index|refs|ids|all] [--target <id-or-qid>] [--base-ref <ref>] [--json]"
|
|
2108
2112
|
],
|
|
2109
2113
|
"docs": {
|
|
2110
2114
|
"help_target": "mdkg help fix",
|
|
2111
2115
|
"command_matrix": "CLI_COMMAND_MATRIX.md"
|
|
2112
2116
|
}
|
|
2113
2117
|
},
|
|
2118
|
+
{
|
|
2119
|
+
"key": "fix apply",
|
|
2120
|
+
"path": [
|
|
2121
|
+
"fix",
|
|
2122
|
+
"apply"
|
|
2123
|
+
],
|
|
2124
|
+
"category": "fix",
|
|
2125
|
+
"status": "stable",
|
|
2126
|
+
"visibility": "public",
|
|
2127
|
+
"summary": "mdkg fix apply command",
|
|
2128
|
+
"usage": [
|
|
2129
|
+
" mdkg fix apply [--family ids] [--target <id-or-qid>] [--base-ref <ref>] [--json]"
|
|
2130
|
+
],
|
|
2131
|
+
"args": [],
|
|
2132
|
+
"flags": [
|
|
2133
|
+
{
|
|
2134
|
+
"name": "--base-ref",
|
|
2135
|
+
"value": "<ref>",
|
|
2136
|
+
"required": false,
|
|
2137
|
+
"description": "--base-ref <ref> Prefer IDs that already exist at a Git base ref"
|
|
2138
|
+
},
|
|
2139
|
+
{
|
|
2140
|
+
"name": "--family",
|
|
2141
|
+
"value": "ids",
|
|
2142
|
+
"required": false,
|
|
2143
|
+
"description": "--family ids Explicit apply family; ids is the only supported apply family"
|
|
2144
|
+
},
|
|
2145
|
+
{
|
|
2146
|
+
"name": "--help",
|
|
2147
|
+
"value": null,
|
|
2148
|
+
"required": false,
|
|
2149
|
+
"description": "--help, -h Show help"
|
|
2150
|
+
},
|
|
2151
|
+
{
|
|
2152
|
+
"name": "--json",
|
|
2153
|
+
"value": null,
|
|
2154
|
+
"required": false,
|
|
2155
|
+
"description": "--json Emit machine-readable JSON output"
|
|
2156
|
+
},
|
|
2157
|
+
{
|
|
2158
|
+
"name": "--root",
|
|
2159
|
+
"value": null,
|
|
2160
|
+
"required": false,
|
|
2161
|
+
"description": "--root, -r <path> Run against a specific repo root"
|
|
2162
|
+
},
|
|
2163
|
+
{
|
|
2164
|
+
"name": "--target",
|
|
2165
|
+
"value": "<id-or-qid>",
|
|
2166
|
+
"required": false,
|
|
2167
|
+
"description": "--target <id-or-qid> Optional duplicate ID target"
|
|
2168
|
+
},
|
|
2169
|
+
{
|
|
2170
|
+
"name": "--version",
|
|
2171
|
+
"value": null,
|
|
2172
|
+
"required": false,
|
|
2173
|
+
"description": "--version, -V Show version"
|
|
2174
|
+
}
|
|
2175
|
+
],
|
|
2176
|
+
"output_formats": [
|
|
2177
|
+
"text",
|
|
2178
|
+
"json"
|
|
2179
|
+
],
|
|
2180
|
+
"json_schema_ref": "mdkg.fix_apply.v1",
|
|
2181
|
+
"side_effects": [
|
|
2182
|
+
"rebuild-derived-indexes",
|
|
2183
|
+
"rewrite-duplicate-node-ids"
|
|
2184
|
+
],
|
|
2185
|
+
"read_paths": [
|
|
2186
|
+
".mdkg/**"
|
|
2187
|
+
],
|
|
2188
|
+
"write_paths": [
|
|
2189
|
+
".mdkg/**/*.md",
|
|
2190
|
+
".mdkg/index/**"
|
|
2191
|
+
],
|
|
2192
|
+
"dry_run": {
|
|
2193
|
+
"supported": false,
|
|
2194
|
+
"apply_supported": true,
|
|
2195
|
+
"apply_family": "ids"
|
|
2196
|
+
},
|
|
2197
|
+
"lock_policy": "mutation-lock-required",
|
|
2198
|
+
"atomic_write_policy": "atomic-file-writes",
|
|
2199
|
+
"receipts": [
|
|
2200
|
+
"fix-apply-receipt"
|
|
2201
|
+
],
|
|
2202
|
+
"danger_level": "high",
|
|
2203
|
+
"aliases": [],
|
|
2204
|
+
"exit_codes": [
|
|
2205
|
+
{
|
|
2206
|
+
"code": 0,
|
|
2207
|
+
"meaning": "success"
|
|
2208
|
+
},
|
|
2209
|
+
{
|
|
2210
|
+
"code": 1,
|
|
2211
|
+
"meaning": "validation-or-runtime-error"
|
|
2212
|
+
}
|
|
2213
|
+
],
|
|
2214
|
+
"examples": [
|
|
2215
|
+
" mdkg fix apply [--family ids] [--target <id-or-qid>] [--base-ref <ref>] [--json]"
|
|
2216
|
+
],
|
|
2217
|
+
"docs": {
|
|
2218
|
+
"help_target": "mdkg help fix apply",
|
|
2219
|
+
"command_matrix": "CLI_COMMAND_MATRIX.md"
|
|
2220
|
+
}
|
|
2221
|
+
},
|
|
2222
|
+
{
|
|
2223
|
+
"key": "fix ids",
|
|
2224
|
+
"path": [
|
|
2225
|
+
"fix",
|
|
2226
|
+
"ids"
|
|
2227
|
+
],
|
|
2228
|
+
"category": "fix",
|
|
2229
|
+
"status": "stable",
|
|
2230
|
+
"visibility": "public",
|
|
2231
|
+
"summary": "mdkg fix ids command",
|
|
2232
|
+
"usage": [
|
|
2233
|
+
" mdkg fix ids [--target <id-or-qid>] [--base-ref <ref>] [--apply] [--json]"
|
|
2234
|
+
],
|
|
2235
|
+
"args": [],
|
|
2236
|
+
"flags": [
|
|
2237
|
+
{
|
|
2238
|
+
"name": "--apply",
|
|
2239
|
+
"value": "it",
|
|
2240
|
+
"required": false,
|
|
2241
|
+
"description": "- without --apply it is equivalent to `mdkg fix plan --family ids`"
|
|
2242
|
+
},
|
|
2243
|
+
{
|
|
2244
|
+
"name": "--base-ref",
|
|
2245
|
+
"value": "<ref>",
|
|
2246
|
+
"required": false,
|
|
2247
|
+
"description": "--base-ref <ref> Prefer IDs that already exist at a Git base ref"
|
|
2248
|
+
},
|
|
2249
|
+
{
|
|
2250
|
+
"name": "--family",
|
|
2251
|
+
"value": "ids`",
|
|
2252
|
+
"required": false,
|
|
2253
|
+
"description": "- without --apply it is equivalent to `mdkg fix plan --family ids`"
|
|
2254
|
+
},
|
|
2255
|
+
{
|
|
2256
|
+
"name": "--help",
|
|
2257
|
+
"value": null,
|
|
2258
|
+
"required": false,
|
|
2259
|
+
"description": "--help, -h Show help"
|
|
2260
|
+
},
|
|
2261
|
+
{
|
|
2262
|
+
"name": "--json",
|
|
2263
|
+
"value": null,
|
|
2264
|
+
"required": false,
|
|
2265
|
+
"description": "--json Emit machine-readable JSON output"
|
|
2266
|
+
},
|
|
2267
|
+
{
|
|
2268
|
+
"name": "--root",
|
|
2269
|
+
"value": null,
|
|
2270
|
+
"required": false,
|
|
2271
|
+
"description": "--root, -r <path> Run against a specific repo root"
|
|
2272
|
+
},
|
|
2273
|
+
{
|
|
2274
|
+
"name": "--target",
|
|
2275
|
+
"value": "<id-or-qid>",
|
|
2276
|
+
"required": false,
|
|
2277
|
+
"description": "--target <id-or-qid> Optional duplicate ID target"
|
|
2278
|
+
},
|
|
2279
|
+
{
|
|
2280
|
+
"name": "--version",
|
|
2281
|
+
"value": null,
|
|
2282
|
+
"required": false,
|
|
2283
|
+
"description": "--version, -V Show version"
|
|
2284
|
+
}
|
|
2285
|
+
],
|
|
2286
|
+
"output_formats": [
|
|
2287
|
+
"text",
|
|
2288
|
+
"json"
|
|
2289
|
+
],
|
|
2290
|
+
"json_schema_ref": "mdkg.fix_ids.v1",
|
|
2291
|
+
"side_effects": [
|
|
2292
|
+
"plan-or-rewrite-duplicate-node-ids",
|
|
2293
|
+
"rebuild-derived-indexes-when-apply"
|
|
2294
|
+
],
|
|
2295
|
+
"read_paths": [
|
|
2296
|
+
".mdkg/**"
|
|
2297
|
+
],
|
|
2298
|
+
"write_paths": [
|
|
2299
|
+
".mdkg/**/*.md",
|
|
2300
|
+
".mdkg/index/**"
|
|
2301
|
+
],
|
|
2302
|
+
"dry_run": {
|
|
2303
|
+
"supported": true,
|
|
2304
|
+
"default": true,
|
|
2305
|
+
"apply_flag": "--apply",
|
|
2306
|
+
"apply_supported": true,
|
|
2307
|
+
"apply_family": "ids"
|
|
2308
|
+
},
|
|
2309
|
+
"lock_policy": "mutation-lock-required-when-apply",
|
|
2310
|
+
"atomic_write_policy": "atomic-file-writes-when-apply",
|
|
2311
|
+
"receipts": [
|
|
2312
|
+
"fix-apply-receipt",
|
|
2313
|
+
"fix-plan-receipt"
|
|
2314
|
+
],
|
|
2315
|
+
"danger_level": "high",
|
|
2316
|
+
"aliases": [],
|
|
2317
|
+
"exit_codes": [
|
|
2318
|
+
{
|
|
2319
|
+
"code": 0,
|
|
2320
|
+
"meaning": "success"
|
|
2321
|
+
},
|
|
2322
|
+
{
|
|
2323
|
+
"code": 1,
|
|
2324
|
+
"meaning": "validation-or-runtime-error"
|
|
2325
|
+
}
|
|
2326
|
+
],
|
|
2327
|
+
"examples": [
|
|
2328
|
+
" mdkg fix ids [--target <id-or-qid>] [--base-ref <ref>] [--apply] [--json]"
|
|
2329
|
+
],
|
|
2330
|
+
"docs": {
|
|
2331
|
+
"help_target": "mdkg help fix ids",
|
|
2332
|
+
"command_matrix": "CLI_COMMAND_MATRIX.md"
|
|
2333
|
+
}
|
|
2334
|
+
},
|
|
2114
2335
|
{
|
|
2115
2336
|
"key": "fix plan",
|
|
2116
2337
|
"path": [
|
|
@@ -2122,15 +2343,21 @@
|
|
|
2122
2343
|
"visibility": "public",
|
|
2123
2344
|
"summary": "mdkg fix plan command",
|
|
2124
2345
|
"usage": [
|
|
2125
|
-
" mdkg fix plan [--family index|refs|ids|all] [--target <id-or-qid>] [--json]"
|
|
2346
|
+
" mdkg fix plan [--family index|refs|ids|all] [--target <id-or-qid>] [--base-ref <ref>] [--json]"
|
|
2126
2347
|
],
|
|
2127
2348
|
"args": [],
|
|
2128
2349
|
"flags": [
|
|
2350
|
+
{
|
|
2351
|
+
"name": "--base-ref",
|
|
2352
|
+
"value": "<ref>",
|
|
2353
|
+
"required": false,
|
|
2354
|
+
"description": "--base-ref <ref> Prefer IDs that already exist at a Git base ref"
|
|
2355
|
+
},
|
|
2129
2356
|
{
|
|
2130
2357
|
"name": "--family",
|
|
2131
|
-
"value": "
|
|
2358
|
+
"value": "ids`",
|
|
2132
2359
|
"required": false,
|
|
2133
|
-
"description": "
|
|
2360
|
+
"description": "- ids-family duplicate-id repairs can be applied with `mdkg fix apply --family ids`"
|
|
2134
2361
|
},
|
|
2135
2362
|
{
|
|
2136
2363
|
"name": "--help",
|
|
@@ -2178,7 +2405,8 @@
|
|
|
2178
2405
|
"dry_run": {
|
|
2179
2406
|
"supported": true,
|
|
2180
2407
|
"default": true,
|
|
2181
|
-
"apply_supported":
|
|
2408
|
+
"apply_supported": true,
|
|
2409
|
+
"apply_family": "ids"
|
|
2182
2410
|
},
|
|
2183
2411
|
"lock_policy": "none-read-only",
|
|
2184
2412
|
"atomic_write_policy": "none-read-only",
|
|
@@ -2198,7 +2426,7 @@
|
|
|
2198
2426
|
}
|
|
2199
2427
|
],
|
|
2200
2428
|
"examples": [
|
|
2201
|
-
" mdkg fix plan [--family index|refs|ids|all] [--target <id-or-qid>] [--json]"
|
|
2429
|
+
" mdkg fix plan [--family index|refs|ids|all] [--target <id-or-qid>] [--base-ref <ref>] [--json]"
|
|
2202
2430
|
],
|
|
2203
2431
|
"docs": {
|
|
2204
2432
|
"help_target": "mdkg help fix plan",
|
|
@@ -7636,5 +7864,5 @@
|
|
|
7636
7864
|
}
|
|
7637
7865
|
}
|
|
7638
7866
|
],
|
|
7639
|
-
"contract_hash": "
|
|
7867
|
+
"contract_hash": "a2e027cdfdb590f3882cb824b9c8b1cf153e39ba63738ab0fad2d92277794325"
|
|
7640
7868
|
}
|
package/dist/commands/fix.js
CHANGED
|
@@ -4,7 +4,10 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
6
|
exports.collectFixPlan = collectFixPlan;
|
|
7
|
+
exports.collectFixApply = collectFixApply;
|
|
7
8
|
exports.runFixPlanCommand = runFixPlanCommand;
|
|
9
|
+
exports.runFixApplyCommand = runFixApplyCommand;
|
|
10
|
+
exports.runFixIdsCommand = runFixIdsCommand;
|
|
8
11
|
const crypto_1 = __importDefault(require("crypto"));
|
|
9
12
|
const child_process_1 = require("child_process");
|
|
10
13
|
const fs_1 = __importDefault(require("fs"));
|
|
@@ -23,6 +26,9 @@ const template_schema_1 = require("../graph/template_schema");
|
|
|
23
26
|
const workspace_files_1 = require("../graph/workspace_files");
|
|
24
27
|
const errors_1 = require("../util/errors");
|
|
25
28
|
const refs_1 = require("../util/refs");
|
|
29
|
+
const atomic_1 = require("../util/atomic");
|
|
30
|
+
const lock_1 = require("../util/lock");
|
|
31
|
+
const index_1 = require("./index");
|
|
26
32
|
const FAMILY_VALUES = new Set(["index", "refs", "ids", "all"]);
|
|
27
33
|
const CONCRETE_FAMILIES = ["index", "refs", "ids"];
|
|
28
34
|
function stableValue(value) {
|
|
@@ -668,14 +674,143 @@ function planRefRepairs(root, target) {
|
|
|
668
674
|
return { proposed: changes, blocked: [] };
|
|
669
675
|
}
|
|
670
676
|
function candidateDuplicateId(baseId, used) {
|
|
671
|
-
|
|
672
|
-
|
|
677
|
+
const match = /^([a-z]+)-([0-9]+)$/.exec(baseId);
|
|
678
|
+
if (!match) {
|
|
679
|
+
throw new errors_1.UsageError(`duplicate id ${baseId} cannot be repaired automatically because it is not a canonical numeric id`);
|
|
680
|
+
}
|
|
681
|
+
const prefix = match[1];
|
|
682
|
+
const start = Number.parseInt(match[2], 10) + 1;
|
|
683
|
+
for (let index = start;; index += 1) {
|
|
684
|
+
const candidate = `${prefix}-${index}`;
|
|
673
685
|
if (!used.has(candidate)) {
|
|
674
686
|
used.add(candidate);
|
|
675
687
|
return candidate;
|
|
676
688
|
}
|
|
677
689
|
}
|
|
678
690
|
}
|
|
691
|
+
function gitShow(root, refPath) {
|
|
692
|
+
const result = (0, child_process_1.spawnSync)("git", ["show", refPath], { cwd: root, encoding: "utf8" });
|
|
693
|
+
if (result.status !== 0) {
|
|
694
|
+
return undefined;
|
|
695
|
+
}
|
|
696
|
+
return result.stdout;
|
|
697
|
+
}
|
|
698
|
+
function runGitStrict(root, args) {
|
|
699
|
+
const result = (0, child_process_1.spawnSync)("git", args, { cwd: root, encoding: "utf8" });
|
|
700
|
+
if (result.status !== 0) {
|
|
701
|
+
throw new errors_1.UsageError(`git ${args.join(" ")} failed: ${(result.stderr || result.stdout).trim() || "unknown error"}`);
|
|
702
|
+
}
|
|
703
|
+
}
|
|
704
|
+
function baseRefIdPaths(root, baseRef, files, config) {
|
|
705
|
+
const pathsById = new Map();
|
|
706
|
+
if (!baseRef) {
|
|
707
|
+
return pathsById;
|
|
708
|
+
}
|
|
709
|
+
const templateSchemas = (0, template_schema_1.loadTemplateSchemas)(root, config, node_1.ALLOWED_TYPES);
|
|
710
|
+
for (const absPath of files) {
|
|
711
|
+
const relativePath = rel(root, absPath);
|
|
712
|
+
const content = gitShow(root, `${baseRef}:${relativePath}`);
|
|
713
|
+
if (content === undefined) {
|
|
714
|
+
continue;
|
|
715
|
+
}
|
|
716
|
+
try {
|
|
717
|
+
const node = (0, node_1.parseNode)(content, absPath, {
|
|
718
|
+
workStatusEnum: config.work.status_enum,
|
|
719
|
+
priorityMin: config.work.priority_min,
|
|
720
|
+
priorityMax: config.work.priority_max,
|
|
721
|
+
templateSchemas,
|
|
722
|
+
});
|
|
723
|
+
if (!pathsById.has(node.id)) {
|
|
724
|
+
pathsById.set(node.id, new Set());
|
|
725
|
+
}
|
|
726
|
+
pathsById.get(node.id)?.add(relativePath);
|
|
727
|
+
}
|
|
728
|
+
catch {
|
|
729
|
+
continue;
|
|
730
|
+
}
|
|
731
|
+
}
|
|
732
|
+
return pathsById;
|
|
733
|
+
}
|
|
734
|
+
function rewriteIdInNodeContent(content, fromId, toId) {
|
|
735
|
+
const lines = content.split(/\n/);
|
|
736
|
+
let inFrontmatter = false;
|
|
737
|
+
let frontmatterClosed = false;
|
|
738
|
+
let idRewritten = false;
|
|
739
|
+
const rewritten = lines.map((line, index) => {
|
|
740
|
+
if (index === 0 && line.trim() === "---") {
|
|
741
|
+
inFrontmatter = true;
|
|
742
|
+
return line;
|
|
743
|
+
}
|
|
744
|
+
if (inFrontmatter && !frontmatterClosed && line.trim() === "---") {
|
|
745
|
+
frontmatterClosed = true;
|
|
746
|
+
inFrontmatter = false;
|
|
747
|
+
return line;
|
|
748
|
+
}
|
|
749
|
+
if (inFrontmatter && !idRewritten && line === `id: ${fromId}`) {
|
|
750
|
+
idRewritten = true;
|
|
751
|
+
return `id: ${toId}`;
|
|
752
|
+
}
|
|
753
|
+
return line.split(fromId).join(toId);
|
|
754
|
+
});
|
|
755
|
+
if (!idRewritten) {
|
|
756
|
+
throw new errors_1.UsageError(`unable to rewrite id ${fromId}; frontmatter id line not found`);
|
|
757
|
+
}
|
|
758
|
+
return rewritten.join("\n");
|
|
759
|
+
}
|
|
760
|
+
function isInsideRoot(root, filePath) {
|
|
761
|
+
const realRoot = `${relativeRoot(root)}${path_1.default.sep}`;
|
|
762
|
+
return path_1.default.resolve(filePath).startsWith(realRoot);
|
|
763
|
+
}
|
|
764
|
+
function replaceIdInRelativePath(relativePath, fromId, toId, usedPaths) {
|
|
765
|
+
const parsed = path_1.default.posix.parse(relativePath);
|
|
766
|
+
const baseName = parsed.base.includes(fromId)
|
|
767
|
+
? parsed.base.replace(fromId, toId)
|
|
768
|
+
: `${toId}-${parsed.base}`;
|
|
769
|
+
let candidate = path_1.default.posix.join(parsed.dir, baseName);
|
|
770
|
+
if (!usedPaths.has(candidate)) {
|
|
771
|
+
usedPaths.add(candidate);
|
|
772
|
+
return candidate;
|
|
773
|
+
}
|
|
774
|
+
for (let index = 2;; index += 1) {
|
|
775
|
+
const suffixed = path_1.default.posix.join(parsed.dir, `${parsed.name}-${toId}-${index}${parsed.ext}`);
|
|
776
|
+
if (!usedPaths.has(suffixed)) {
|
|
777
|
+
usedPaths.add(suffixed);
|
|
778
|
+
return suffixed;
|
|
779
|
+
}
|
|
780
|
+
}
|
|
781
|
+
}
|
|
782
|
+
function gitConflictStages(root) {
|
|
783
|
+
const output = runGit(root, ["ls-files", "-u", "--", ".mdkg"]) ?? "";
|
|
784
|
+
const groups = new Map();
|
|
785
|
+
for (const line of output.split(/\r?\n/).filter(Boolean)) {
|
|
786
|
+
const match = /^([0-7]+) ([0-9a-f]+) ([123])\t(.+)$/.exec(line);
|
|
787
|
+
if (!match || !match[4].endsWith(".md")) {
|
|
788
|
+
continue;
|
|
789
|
+
}
|
|
790
|
+
const entry = {
|
|
791
|
+
mode: match[1],
|
|
792
|
+
object: match[2],
|
|
793
|
+
stage: Number.parseInt(match[3], 10),
|
|
794
|
+
path: match[4],
|
|
795
|
+
};
|
|
796
|
+
groups.set(entry.path, [...(groups.get(entry.path) ?? []), entry]);
|
|
797
|
+
}
|
|
798
|
+
return groups;
|
|
799
|
+
}
|
|
800
|
+
function workspaceAliasForPath(root, config, relativePath) {
|
|
801
|
+
const absPath = path_1.default.resolve(root, relativePath);
|
|
802
|
+
for (const alias of Object.keys(config.workspaces).sort()) {
|
|
803
|
+
const entry = config.workspaces[alias];
|
|
804
|
+
if (!entry.enabled) {
|
|
805
|
+
continue;
|
|
806
|
+
}
|
|
807
|
+
const wsRoot = path_1.default.resolve(root, entry.path, entry.mdkg_dir);
|
|
808
|
+
if (absPath === wsRoot || absPath.startsWith(`${wsRoot}${path_1.default.sep}`)) {
|
|
809
|
+
return alias;
|
|
810
|
+
}
|
|
811
|
+
}
|
|
812
|
+
return undefined;
|
|
813
|
+
}
|
|
679
814
|
function filesContaining(root, files, needle) {
|
|
680
815
|
return files
|
|
681
816
|
.filter((filePath) => {
|
|
@@ -728,18 +863,118 @@ function referenceRewriteItems(root, files, from, to) {
|
|
|
728
863
|
.filter((item) => Boolean(item))
|
|
729
864
|
.sort((a, b) => a.path.localeCompare(b.path));
|
|
730
865
|
}
|
|
731
|
-
function
|
|
866
|
+
function planGitStageDuplicateIdRepairs(root, target, config, usedIdsByAlias, usedPaths, startIndex) {
|
|
867
|
+
const templateSchemas = (0, template_schema_1.loadTemplateSchemas)(root, config, node_1.ALLOWED_TYPES);
|
|
868
|
+
const proposed = [];
|
|
869
|
+
let matchedTarget = false;
|
|
870
|
+
for (const [relativePath, stages] of [...gitConflictStages(root).entries()].sort(([a], [b]) => a.localeCompare(b))) {
|
|
871
|
+
const ours = stages.find((entry) => entry.stage === 2);
|
|
872
|
+
const theirs = stages.find((entry) => entry.stage === 3);
|
|
873
|
+
if (!ours || !theirs) {
|
|
874
|
+
continue;
|
|
875
|
+
}
|
|
876
|
+
const alias = workspaceAliasForPath(root, config, relativePath);
|
|
877
|
+
if (!alias) {
|
|
878
|
+
continue;
|
|
879
|
+
}
|
|
880
|
+
const oursContent = gitShow(root, `:2:${relativePath}`);
|
|
881
|
+
const theirsContent = gitShow(root, `:3:${relativePath}`);
|
|
882
|
+
if (oursContent === undefined || theirsContent === undefined) {
|
|
883
|
+
continue;
|
|
884
|
+
}
|
|
885
|
+
try {
|
|
886
|
+
const absPath = path_1.default.resolve(root, relativePath);
|
|
887
|
+
const oursNode = (0, node_1.parseNode)(oursContent, absPath, {
|
|
888
|
+
workStatusEnum: config.work.status_enum,
|
|
889
|
+
priorityMin: config.work.priority_min,
|
|
890
|
+
priorityMax: config.work.priority_max,
|
|
891
|
+
templateSchemas,
|
|
892
|
+
});
|
|
893
|
+
const theirsNode = (0, node_1.parseNode)(theirsContent, absPath, {
|
|
894
|
+
workStatusEnum: config.work.status_enum,
|
|
895
|
+
priorityMin: config.work.priority_min,
|
|
896
|
+
priorityMax: config.work.priority_max,
|
|
897
|
+
templateSchemas,
|
|
898
|
+
});
|
|
899
|
+
if (oursNode.id !== theirsNode.id) {
|
|
900
|
+
continue;
|
|
901
|
+
}
|
|
902
|
+
const duplicateId = oursNode.id;
|
|
903
|
+
const qid = `${alias}:${duplicateId}`;
|
|
904
|
+
const targetMatches = !target || target.toLowerCase() === duplicateId || target.toLowerCase() === qid || target === relativePath;
|
|
905
|
+
if (!targetMatches) {
|
|
906
|
+
continue;
|
|
907
|
+
}
|
|
908
|
+
matchedTarget = true;
|
|
909
|
+
const usedIds = usedIdsByAlias.get(alias) ?? new Set();
|
|
910
|
+
usedIds.add(oursNode.id);
|
|
911
|
+
usedIds.add(theirsNode.id);
|
|
912
|
+
usedIdsByAlias.set(alias, usedIds);
|
|
913
|
+
const candidate = candidateDuplicateId(duplicateId, usedIds);
|
|
914
|
+
const candidatePath = replaceIdInRelativePath(relativePath, duplicateId, candidate, usedPaths);
|
|
915
|
+
proposed.push({
|
|
916
|
+
id: `ids.${String(startIndex + proposed.length + 1).padStart(3, "0")}`,
|
|
917
|
+
family: "ids",
|
|
918
|
+
risk: "high",
|
|
919
|
+
status: "planned",
|
|
920
|
+
reason: "git_stage_duplicate_id",
|
|
921
|
+
paths: [relativePath, candidatePath],
|
|
922
|
+
refs: [qid, `${alias}:${candidate}`].sort(),
|
|
923
|
+
evidence: {
|
|
924
|
+
conflict_kind: "git_index_unresolved_duplicate_id",
|
|
925
|
+
workspace: alias,
|
|
926
|
+
duplicate_id: duplicateId,
|
|
927
|
+
conflict_path: relativePath,
|
|
928
|
+
canonical_stage: 2,
|
|
929
|
+
duplicate_stage: 3,
|
|
930
|
+
current_blob: ours.object,
|
|
931
|
+
incoming_blob: theirs.object,
|
|
932
|
+
deterministic_rule: "keep stage 2 at the conflicted path, rewrite stage 3 to the next unused canonical numeric id and path, then git add both files",
|
|
933
|
+
},
|
|
934
|
+
before: {
|
|
935
|
+
duplicate_id: duplicateId,
|
|
936
|
+
workspace: alias,
|
|
937
|
+
conflict_path: relativePath,
|
|
938
|
+
canonical_stage: 2,
|
|
939
|
+
duplicate_stage: 3,
|
|
940
|
+
current_blob: ours.object,
|
|
941
|
+
incoming_blob: theirs.object,
|
|
942
|
+
},
|
|
943
|
+
after: {
|
|
944
|
+
candidate_id: candidate,
|
|
945
|
+
candidate_qid: `${alias}:${candidate}`,
|
|
946
|
+
candidate_path: candidatePath,
|
|
947
|
+
canonical_path: relativePath,
|
|
948
|
+
collision_free: true,
|
|
949
|
+
deterministic_rule: "keep stage 2 at the conflicted path, rewrite stage 3 to the next unused canonical numeric id and path, then git add both files",
|
|
950
|
+
},
|
|
951
|
+
command_hint: `mdkg fix ids --target ${duplicateId} --apply --json`,
|
|
952
|
+
apply_supported: true,
|
|
953
|
+
apply_kind: "git_stage_duplicate_id_rewrite",
|
|
954
|
+
});
|
|
955
|
+
}
|
|
956
|
+
catch {
|
|
957
|
+
continue;
|
|
958
|
+
}
|
|
959
|
+
}
|
|
960
|
+
return { proposed, matchedTarget };
|
|
961
|
+
}
|
|
962
|
+
function planDuplicateIdRepairs(root, target, baseRef) {
|
|
732
963
|
const config = (0, config_1.loadConfig)(root);
|
|
733
964
|
const templateSchemas = (0, template_schema_1.loadTemplateSchemas)(root, config, node_1.ALLOWED_TYPES);
|
|
734
965
|
const docsByAlias = (0, workspace_files_1.listWorkspaceDocFilesByAlias)(root, config);
|
|
735
966
|
const proposed = [];
|
|
736
967
|
const blocked = [];
|
|
968
|
+
const usedIdsByAlias = new Map();
|
|
969
|
+
const usedPaths = new Set();
|
|
737
970
|
let matchedTarget = !target;
|
|
738
971
|
for (const alias of Object.keys(docsByAlias).sort()) {
|
|
739
972
|
const records = [];
|
|
740
973
|
const usedIds = new Set();
|
|
741
974
|
const files = docsByAlias[alias].sort();
|
|
975
|
+
const basePathsById = baseRefIdPaths(root, baseRef, files, config);
|
|
742
976
|
for (const filePath of files) {
|
|
977
|
+
usedPaths.add(rel(root, filePath));
|
|
743
978
|
if (path_1.default.basename(filePath) === "core.md" && path_1.default.basename(path_1.default.dirname(filePath)) === "core") {
|
|
744
979
|
continue;
|
|
745
980
|
}
|
|
@@ -762,6 +997,7 @@ function planDuplicateIdRepairs(root, target) {
|
|
|
762
997
|
continue;
|
|
763
998
|
}
|
|
764
999
|
}
|
|
1000
|
+
usedIdsByAlias.set(alias, usedIds);
|
|
765
1001
|
const groups = new Map();
|
|
766
1002
|
for (const record of records) {
|
|
767
1003
|
groups.set(record.id, [...(groups.get(record.id) ?? []), record]);
|
|
@@ -778,18 +1014,29 @@ function planDuplicateIdRepairs(root, target) {
|
|
|
778
1014
|
continue;
|
|
779
1015
|
}
|
|
780
1016
|
matchedTarget = true;
|
|
781
|
-
const
|
|
1017
|
+
const basePaths = basePathsById.get(id);
|
|
1018
|
+
const baseCanonical = basePaths ? group.find((record) => basePaths.has(record.path)) : undefined;
|
|
1019
|
+
const canonical = baseCanonical ?? group[0];
|
|
782
1020
|
const referencePaths = filesContaining(root, files, id);
|
|
783
|
-
const duplicateRecords = group.
|
|
1021
|
+
const duplicateRecords = group.filter((record) => record.path !== canonical.path);
|
|
784
1022
|
const groupPaths = group.map((record) => record.path).sort();
|
|
785
|
-
const deterministicRule =
|
|
1023
|
+
const deterministicRule = baseCanonical
|
|
1024
|
+
? "keep the path that already existed at --base-ref unchanged; propose the next unused canonical numeric id for each other path"
|
|
1025
|
+
: "keep the lexicographically first path unchanged; propose the next unused canonical numeric id for each later path";
|
|
786
1026
|
for (const duplicate of duplicateRecords) {
|
|
787
1027
|
const candidate = candidateDuplicateId(id, usedIds);
|
|
1028
|
+
const selfReferenceRewrites = referenceRewriteItems(root, [duplicate.absPath], id, candidate);
|
|
1029
|
+
const externalReferenceRewrites = referenceRewriteItems(root, files.filter((filePath) => filePath !== duplicate.absPath), id, candidate);
|
|
1030
|
+
const safeReferenceRewrites = baseRef
|
|
1031
|
+
? externalReferenceRewrites.filter((item) => gitShow(root, `${baseRef}:${item.path}`) === undefined)
|
|
1032
|
+
: [];
|
|
1033
|
+
const safeReferencePaths = new Set(safeReferenceRewrites.map((item) => item.path));
|
|
1034
|
+
const ambiguousReferenceRewrites = externalReferenceRewrites.filter((item) => !safeReferencePaths.has(item.path));
|
|
788
1035
|
proposed.push({
|
|
789
1036
|
id: `ids.${String(proposed.length + 1).padStart(3, "0")}`,
|
|
790
1037
|
family: "ids",
|
|
791
1038
|
risk: "high",
|
|
792
|
-
status: "
|
|
1039
|
+
status: "planned",
|
|
793
1040
|
reason: "duplicate_id",
|
|
794
1041
|
paths: [duplicate.path],
|
|
795
1042
|
refs: Array.from(new Set([canonical.qid, duplicate.qid])).sort(),
|
|
@@ -797,6 +1044,8 @@ function planDuplicateIdRepairs(root, target) {
|
|
|
797
1044
|
conflict_kind: "duplicate_local_id",
|
|
798
1045
|
branch_merge_suspected: true,
|
|
799
1046
|
workspace: alias,
|
|
1047
|
+
base_ref: baseRef ?? null,
|
|
1048
|
+
base_ref_canonical: Boolean(baseCanonical),
|
|
800
1049
|
duplicate_id: id,
|
|
801
1050
|
group_size: group.length,
|
|
802
1051
|
group_paths: groupPaths,
|
|
@@ -813,6 +1062,7 @@ function planDuplicateIdRepairs(root, target) {
|
|
|
813
1062
|
before: {
|
|
814
1063
|
duplicate_id: id,
|
|
815
1064
|
workspace: alias,
|
|
1065
|
+
base_ref: baseRef ?? null,
|
|
816
1066
|
canonical_path: canonical.path,
|
|
817
1067
|
duplicate_path: duplicate.path,
|
|
818
1068
|
duplicate_group: {
|
|
@@ -827,14 +1077,23 @@ function planDuplicateIdRepairs(root, target) {
|
|
|
827
1077
|
collision_free: true,
|
|
828
1078
|
deterministic_rule: deterministicRule,
|
|
829
1079
|
reference_paths: referencePaths,
|
|
830
|
-
|
|
1080
|
+
self_reference_rewrites: selfReferenceRewrites,
|
|
1081
|
+
safe_reference_rewrites: safeReferenceRewrites,
|
|
1082
|
+
ambiguous_reference_rewrites: ambiguousReferenceRewrites,
|
|
1083
|
+
reference_rewrite_plan: [...selfReferenceRewrites, ...safeReferenceRewrites, ...ambiguousReferenceRewrites].sort((a, b) => a.path.localeCompare(b.path)),
|
|
831
1084
|
},
|
|
832
|
-
command_hint: `
|
|
833
|
-
apply_supported:
|
|
1085
|
+
command_hint: `mdkg fix apply --family ids --target ${id}${baseRef ? ` --base-ref ${baseRef}` : ""} --json`,
|
|
1086
|
+
apply_supported: true,
|
|
1087
|
+
apply_kind: "duplicate_id_rewrite",
|
|
834
1088
|
});
|
|
835
1089
|
}
|
|
836
1090
|
}
|
|
837
1091
|
}
|
|
1092
|
+
const stageRepairs = planGitStageDuplicateIdRepairs(root, target, config, usedIdsByAlias, usedPaths, proposed.length);
|
|
1093
|
+
proposed.push(...stageRepairs.proposed);
|
|
1094
|
+
if (stageRepairs.matchedTarget) {
|
|
1095
|
+
matchedTarget = true;
|
|
1096
|
+
}
|
|
838
1097
|
if (!matchedTarget && target) {
|
|
839
1098
|
blocked.push({
|
|
840
1099
|
id: "ids.target.001",
|
|
@@ -881,15 +1140,20 @@ function collectFixPlan(options) {
|
|
|
881
1140
|
const root = relativeRoot(options.root);
|
|
882
1141
|
const indexRepairs = selected.includes("index") ? planIndexRepairs(root) : { proposed: [], blocked: [] };
|
|
883
1142
|
const refRepairs = selected.includes("refs") ? planRefRepairs(root, options.target) : { proposed: [], blocked: [] };
|
|
884
|
-
const idRepairs = selected.includes("ids")
|
|
1143
|
+
const idRepairs = selected.includes("ids")
|
|
1144
|
+
? planDuplicateIdRepairs(root, options.target, options.baseRef)
|
|
1145
|
+
: { proposed: [], blocked: [] };
|
|
885
1146
|
const proposedChanges = sortChanges([...indexRepairs.proposed, ...refRepairs.proposed, ...idRepairs.proposed]);
|
|
886
1147
|
const blockedChanges = sortChanges([...indexRepairs.blocked, ...refRepairs.blocked, ...idRepairs.blocked]);
|
|
1148
|
+
const supportedApplyCount = proposedChanges.filter((change) => change.apply_supported).length;
|
|
1149
|
+
const unsupportedApplyCount = proposedChanges.filter((change) => !change.apply_supported).length;
|
|
887
1150
|
const body = {
|
|
888
1151
|
action: "fix.plan",
|
|
889
1152
|
schema_version: 1,
|
|
890
1153
|
root,
|
|
891
1154
|
family,
|
|
892
1155
|
target: options.target ?? null,
|
|
1156
|
+
base_ref: options.baseRef ?? null,
|
|
893
1157
|
dirty: collectDirtyState(root),
|
|
894
1158
|
families: emptyFamilySummaries(selected).map((entry) => ({
|
|
895
1159
|
...entry,
|
|
@@ -903,9 +1167,13 @@ function collectFixPlan(options) {
|
|
|
903
1167
|
selected_families: selected,
|
|
904
1168
|
proposed_count: proposedChanges.length,
|
|
905
1169
|
blocked_count: blockedChanges.length,
|
|
906
|
-
apply_supported:
|
|
907
|
-
apply_deferred:
|
|
908
|
-
|
|
1170
|
+
apply_supported: supportedApplyCount > 0,
|
|
1171
|
+
apply_deferred: unsupportedApplyCount > 0 || blockedChanges.length > 0,
|
|
1172
|
+
supported_apply_count: supportedApplyCount,
|
|
1173
|
+
unsupported_apply_count: unsupportedApplyCount,
|
|
1174
|
+
message: supportedApplyCount > 0
|
|
1175
|
+
? "ids-family duplicate-id repairs can be applied with mdkg fix apply --family ids or mdkg fix ids --apply"
|
|
1176
|
+
: "this command is review-only for the selected findings and writes no files",
|
|
909
1177
|
},
|
|
910
1178
|
};
|
|
911
1179
|
const planHash = sha256(body);
|
|
@@ -917,6 +1185,171 @@ function collectFixPlan(options) {
|
|
|
917
1185
|
plan_id: `fix-plan-${planHash.slice("sha256:".length, "sha256:".length + 16)}`,
|
|
918
1186
|
};
|
|
919
1187
|
}
|
|
1188
|
+
function normalizeApplyFamily(value) {
|
|
1189
|
+
const family = normalizeFamily(value ?? "ids");
|
|
1190
|
+
if (family !== "ids") {
|
|
1191
|
+
throw new errors_1.UsageError("fix apply currently supports only --family ids");
|
|
1192
|
+
}
|
|
1193
|
+
return "ids";
|
|
1194
|
+
}
|
|
1195
|
+
function applyDuplicateIdChange(root, change) {
|
|
1196
|
+
if (change.family !== "ids" || change.reason !== "duplicate_id" || change.apply_kind !== "duplicate_id_rewrite") {
|
|
1197
|
+
throw new errors_1.UsageError(`unsupported fix apply change ${change.id}`);
|
|
1198
|
+
}
|
|
1199
|
+
const relativePath = change.paths[0];
|
|
1200
|
+
if (!relativePath) {
|
|
1201
|
+
throw new errors_1.UsageError(`fix apply change ${change.id} is missing a path`);
|
|
1202
|
+
}
|
|
1203
|
+
const absPath = path_1.default.resolve(root, relativePath);
|
|
1204
|
+
if (!isInsideRoot(root, absPath)) {
|
|
1205
|
+
throw new errors_1.UsageError(`fix apply refused path outside repo: ${relativePath}`);
|
|
1206
|
+
}
|
|
1207
|
+
const before = change.before;
|
|
1208
|
+
const after = change.after;
|
|
1209
|
+
const fromId = typeof before.duplicate_id === "string" ? before.duplicate_id : undefined;
|
|
1210
|
+
const toId = typeof after.candidate_id === "string" ? after.candidate_id : undefined;
|
|
1211
|
+
if (!fromId || !toId) {
|
|
1212
|
+
throw new errors_1.UsageError(`fix apply change ${change.id} is missing duplicate id rewrite details`);
|
|
1213
|
+
}
|
|
1214
|
+
const current = fs_1.default.readFileSync(absPath, "utf8");
|
|
1215
|
+
const rewritten = rewriteIdInNodeContent(current, fromId, toId);
|
|
1216
|
+
if (rewritten === current) {
|
|
1217
|
+
throw new errors_1.UsageError(`fix apply change ${change.id} produced no file changes`);
|
|
1218
|
+
}
|
|
1219
|
+
(0, atomic_1.atomicWriteFile)(absPath, rewritten);
|
|
1220
|
+
const afterDetails = change.after;
|
|
1221
|
+
const safeReferenceRewrites = Array.isArray(afterDetails.safe_reference_rewrites)
|
|
1222
|
+
? afterDetails.safe_reference_rewrites
|
|
1223
|
+
: [];
|
|
1224
|
+
const touchedPaths = new Set([relativePath]);
|
|
1225
|
+
for (const rewrite of safeReferenceRewrites) {
|
|
1226
|
+
if (typeof rewrite.path !== "string" || typeof rewrite.from !== "string" || typeof rewrite.to !== "string") {
|
|
1227
|
+
continue;
|
|
1228
|
+
}
|
|
1229
|
+
const rewriteAbs = path_1.default.resolve(root, rewrite.path);
|
|
1230
|
+
if (!isInsideRoot(root, rewriteAbs) || !fs_1.default.existsSync(rewriteAbs)) {
|
|
1231
|
+
continue;
|
|
1232
|
+
}
|
|
1233
|
+
const rewriteCurrent = fs_1.default.readFileSync(rewriteAbs, "utf8");
|
|
1234
|
+
const rewriteNext = rewriteCurrent.split(rewrite.from).join(rewrite.to);
|
|
1235
|
+
if (rewriteNext !== rewriteCurrent) {
|
|
1236
|
+
(0, atomic_1.atomicWriteFile)(rewriteAbs, rewriteNext);
|
|
1237
|
+
touchedPaths.add(rewrite.path);
|
|
1238
|
+
}
|
|
1239
|
+
}
|
|
1240
|
+
return {
|
|
1241
|
+
id: change.id,
|
|
1242
|
+
family: "ids",
|
|
1243
|
+
reason: change.reason,
|
|
1244
|
+
apply_kind: "duplicate_id_rewrite",
|
|
1245
|
+
path: relativePath,
|
|
1246
|
+
touched_paths: Array.from(touchedPaths).sort(),
|
|
1247
|
+
refs: change.refs,
|
|
1248
|
+
before: change.before,
|
|
1249
|
+
after: change.after,
|
|
1250
|
+
};
|
|
1251
|
+
}
|
|
1252
|
+
function applyGitStageDuplicateIdChange(root, change) {
|
|
1253
|
+
if (change.family !== "ids" || change.reason !== "git_stage_duplicate_id" || change.apply_kind !== "git_stage_duplicate_id_rewrite") {
|
|
1254
|
+
throw new errors_1.UsageError(`unsupported git-stage fix apply change ${change.id}`);
|
|
1255
|
+
}
|
|
1256
|
+
const before = change.before;
|
|
1257
|
+
const after = change.after;
|
|
1258
|
+
const fromId = typeof before.duplicate_id === "string" ? before.duplicate_id : undefined;
|
|
1259
|
+
const conflictPath = typeof before.conflict_path === "string" ? before.conflict_path : undefined;
|
|
1260
|
+
const candidateId = typeof after.candidate_id === "string" ? after.candidate_id : undefined;
|
|
1261
|
+
const candidatePath = typeof after.candidate_path === "string" ? after.candidate_path : undefined;
|
|
1262
|
+
if (!fromId || !conflictPath || !candidateId || !candidatePath) {
|
|
1263
|
+
throw new errors_1.UsageError(`fix apply change ${change.id} is missing git-stage rewrite details`);
|
|
1264
|
+
}
|
|
1265
|
+
const canonicalContent = gitShow(root, `:2:${conflictPath}`);
|
|
1266
|
+
const duplicateContent = gitShow(root, `:3:${conflictPath}`);
|
|
1267
|
+
if (canonicalContent === undefined || duplicateContent === undefined) {
|
|
1268
|
+
throw new errors_1.UsageError(`fix apply change ${change.id} could not read Git conflict stages for ${conflictPath}`);
|
|
1269
|
+
}
|
|
1270
|
+
const canonicalAbs = path_1.default.resolve(root, conflictPath);
|
|
1271
|
+
const candidateAbs = path_1.default.resolve(root, candidatePath);
|
|
1272
|
+
if (!isInsideRoot(root, canonicalAbs) || !isInsideRoot(root, candidateAbs)) {
|
|
1273
|
+
throw new errors_1.UsageError(`fix apply refused path outside repo while resolving ${conflictPath}`);
|
|
1274
|
+
}
|
|
1275
|
+
const rewrittenDuplicate = rewriteIdInNodeContent(duplicateContent, fromId, candidateId);
|
|
1276
|
+
(0, atomic_1.atomicWriteFile)(canonicalAbs, canonicalContent);
|
|
1277
|
+
(0, atomic_1.atomicWriteFile)(candidateAbs, rewrittenDuplicate);
|
|
1278
|
+
runGitStrict(root, ["add", "--", conflictPath, candidatePath]);
|
|
1279
|
+
return {
|
|
1280
|
+
id: change.id,
|
|
1281
|
+
family: "ids",
|
|
1282
|
+
reason: change.reason,
|
|
1283
|
+
apply_kind: "git_stage_duplicate_id_rewrite",
|
|
1284
|
+
path: conflictPath,
|
|
1285
|
+
touched_paths: [conflictPath, candidatePath],
|
|
1286
|
+
refs: change.refs,
|
|
1287
|
+
before: change.before,
|
|
1288
|
+
after: change.after,
|
|
1289
|
+
};
|
|
1290
|
+
}
|
|
1291
|
+
function collectFixApply(options) {
|
|
1292
|
+
const family = normalizeApplyFamily(options.family);
|
|
1293
|
+
const root = relativeRoot(options.root);
|
|
1294
|
+
const config = (0, config_1.loadConfig)(root);
|
|
1295
|
+
return (0, lock_1.withMutationLock)(root, config.index.lock_timeout_ms, () => {
|
|
1296
|
+
const plan = collectFixPlan({ ...options, root, family });
|
|
1297
|
+
const applicable = plan.proposed_changes.filter((change) => change.apply_supported);
|
|
1298
|
+
const unsupported = plan.proposed_changes.filter((change) => !change.apply_supported);
|
|
1299
|
+
if (plan.blocked_changes.length > 0) {
|
|
1300
|
+
throw new errors_1.UsageError("fix apply refused because the plan contains blocked changes");
|
|
1301
|
+
}
|
|
1302
|
+
if (applicable.length === 0) {
|
|
1303
|
+
throw new errors_1.UsageError("fix apply found no supported ids-family changes to apply");
|
|
1304
|
+
}
|
|
1305
|
+
const appliedChanges = applicable.map((change) => change.apply_kind === "git_stage_duplicate_id_rewrite"
|
|
1306
|
+
? applyGitStageDuplicateIdChange(root, change)
|
|
1307
|
+
: applyDuplicateIdChange(root, change));
|
|
1308
|
+
const indexReceipt = (0, index_1.rebuildDerivedIndexCaches)({ root, tolerant: true });
|
|
1309
|
+
const body = {
|
|
1310
|
+
action: "fix.apply",
|
|
1311
|
+
ok: true,
|
|
1312
|
+
schema_version: 1,
|
|
1313
|
+
root,
|
|
1314
|
+
family,
|
|
1315
|
+
target: options.target ?? null,
|
|
1316
|
+
base_ref: options.baseRef ?? null,
|
|
1317
|
+
plan_id: plan.plan_id,
|
|
1318
|
+
plan_hash: plan.plan_hash,
|
|
1319
|
+
applied_changes: appliedChanges,
|
|
1320
|
+
blocked_changes: plan.blocked_changes,
|
|
1321
|
+
unsupported_changes: unsupported,
|
|
1322
|
+
touched_paths: Array.from(new Set(appliedChanges.flatMap((change) => change.touched_paths))).sort(),
|
|
1323
|
+
ambiguous_reference_rewrites: applicable.flatMap((change) => {
|
|
1324
|
+
const after = change.after;
|
|
1325
|
+
return Array.isArray(after.ambiguous_reference_rewrites) ? after.ambiguous_reference_rewrites : [];
|
|
1326
|
+
}),
|
|
1327
|
+
index: {
|
|
1328
|
+
rebuilt: true,
|
|
1329
|
+
paths: {
|
|
1330
|
+
nodes: rel(root, indexReceipt.paths.nodes),
|
|
1331
|
+
skills: rel(root, indexReceipt.paths.skills),
|
|
1332
|
+
capabilities: rel(root, indexReceipt.paths.capabilities),
|
|
1333
|
+
subgraphs: rel(root, indexReceipt.paths.subgraphs),
|
|
1334
|
+
sqlite: indexReceipt.paths.sqlite ? rel(root, indexReceipt.paths.sqlite) : null,
|
|
1335
|
+
},
|
|
1336
|
+
},
|
|
1337
|
+
summary: {
|
|
1338
|
+
applied_count: appliedChanges.length,
|
|
1339
|
+
unsupported_count: unsupported.length,
|
|
1340
|
+
blocked_count: plan.blocked_changes.length,
|
|
1341
|
+
message: unsupported.length > 0
|
|
1342
|
+
? "applied supported ids-family changes; unsupported findings remain review-only"
|
|
1343
|
+
: "applied supported ids-family duplicate-id repairs",
|
|
1344
|
+
},
|
|
1345
|
+
};
|
|
1346
|
+
return {
|
|
1347
|
+
...body,
|
|
1348
|
+
generated_at: new Date().toISOString(),
|
|
1349
|
+
receipt_hash: sha256(body),
|
|
1350
|
+
};
|
|
1351
|
+
});
|
|
1352
|
+
}
|
|
920
1353
|
function runFixPlanCommand(options) {
|
|
921
1354
|
const payload = collectFixPlan(options);
|
|
922
1355
|
if (options.json) {
|
|
@@ -929,6 +1362,25 @@ function runFixPlanCommand(options) {
|
|
|
929
1362
|
console.log(`family: ${payload.family}`);
|
|
930
1363
|
console.log(`proposed_changes: ${payload.proposed_changes.length}`);
|
|
931
1364
|
console.log(`blocked_changes: ${payload.blocked_changes.length}`);
|
|
932
|
-
console.log(
|
|
933
|
-
console.log("note:
|
|
1365
|
+
console.log(`apply_supported: ${payload.summary.apply_supported}`);
|
|
1366
|
+
console.log("note: use --json for the machine-readable receipt");
|
|
1367
|
+
}
|
|
1368
|
+
function runFixApplyCommand(options) {
|
|
1369
|
+
const payload = collectFixApply(options);
|
|
1370
|
+
if (options.json) {
|
|
1371
|
+
console.log(JSON.stringify(payload, null, 2));
|
|
1372
|
+
return;
|
|
1373
|
+
}
|
|
1374
|
+
console.log("fix apply");
|
|
1375
|
+
console.log(`receipt_hash: ${payload.receipt_hash}`);
|
|
1376
|
+
console.log(`family: ${payload.family}`);
|
|
1377
|
+
console.log(`applied_changes: ${payload.applied_changes.length}`);
|
|
1378
|
+
console.log(`touched_paths: ${payload.touched_paths.join(", ")}`);
|
|
1379
|
+
}
|
|
1380
|
+
function runFixIdsCommand(options) {
|
|
1381
|
+
if (options.apply) {
|
|
1382
|
+
runFixApplyCommand({ ...options, family: "ids" });
|
|
1383
|
+
return;
|
|
1384
|
+
}
|
|
1385
|
+
runFixPlanCommand({ ...options, family: "ids" });
|
|
934
1386
|
}
|
|
@@ -27,14 +27,20 @@ Primary commands:
|
|
|
27
27
|
- `mdkg task`
|
|
28
28
|
- `mdkg validate`
|
|
29
29
|
- `mdkg status [--json]`
|
|
30
|
-
- `mdkg fix plan [--family index|refs|ids|all] [--target <id-or-qid>] [--json]`
|
|
30
|
+
- `mdkg fix plan [--family index|refs|ids|all] [--target <id-or-qid>] [--base-ref <ref>] [--json]`
|
|
31
|
+
- `mdkg fix apply [--family ids] [--target <id-or-qid>] [--base-ref <ref>] [--json]`
|
|
32
|
+
- `mdkg fix ids [--target <id-or-qid>] [--base-ref <ref>] [--apply] [--json]`
|
|
31
33
|
|
|
32
34
|
Operator health:
|
|
33
35
|
- `mdkg status [--json]` is a read-only summary for scripts and agents
|
|
34
36
|
- reports mdkg version/config, git state, graph/index freshness, selected-goal state, project DB verification summary, and generated cache status
|
|
35
37
|
- does not rebuild indexes, run migrations, repair files, mutate graph nodes, or change selected-goal state
|
|
36
|
-
- `mdkg fix plan ...` is dry-run repair planning only; it writes nothing
|
|
37
|
-
-
|
|
38
|
+
- `mdkg fix plan ...` is dry-run repair planning only; it writes nothing
|
|
39
|
+
- duplicate-ID graph repairs can be applied with `mdkg fix apply --family ids` or `mdkg fix ids --apply`
|
|
40
|
+
- use `--base-ref main` when mainline IDs should win branch-merge repair
|
|
41
|
+
- unresolved Git add/add conflict stages are split by keeping stage 2 at the conflicted path and writing stage 3 to a new canonical ID/path
|
|
42
|
+
- graph-reference and index/cache findings remain review-only guidance
|
|
43
|
+
- `fix plan --json` returns a receipt-shaped plan with selected families, risk counts, paths, reason codes, and per-change `apply_supported` metadata
|
|
38
44
|
|
|
39
45
|
Index backend:
|
|
40
46
|
- fresh mdkg workspaces default to `index.backend: sqlite`
|
package/dist/init/README.md
CHANGED
|
@@ -45,8 +45,13 @@ fresh init, run `mdkg index` first so strict doctor can load generated caches.
|
|
|
45
45
|
|
|
46
46
|
Use `mdkg fix plan --json` for dry-run repair guidance. It reports generated
|
|
47
47
|
index/cache repair hints, missing graph references, and duplicate local ids as
|
|
48
|
-
receipt-shaped planned changes with risk levels and
|
|
49
|
-
`
|
|
48
|
+
receipt-shaped planned changes with risk levels and per-change
|
|
49
|
+
`apply_supported` metadata. Duplicate-ID graph repairs can be applied with
|
|
50
|
+
`mdkg fix apply --family ids --json` or `mdkg fix ids --apply --json`; use
|
|
51
|
+
`--base-ref main` when mainline IDs should win. Index/cache and graph-reference
|
|
52
|
+
findings remain review-only. For unresolved Git add/add conflicts, `fix ids`
|
|
53
|
+
keeps stage 2 at the conflicted path, rewrites stage 3 to the next unused
|
|
54
|
+
canonical ID/path, and records a receipt.
|
|
50
55
|
|
|
51
56
|
Use research spikes for investigation and planning work that should produce a
|
|
52
57
|
reviewable recommendation before implementation:
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"schema_version": 1,
|
|
3
3
|
"tool": "mdkg",
|
|
4
|
-
"mdkg_version": "0.3.
|
|
4
|
+
"mdkg_version": "0.3.4",
|
|
5
5
|
"files": [
|
|
6
6
|
{
|
|
7
7
|
"path": ".mdkg/config.json",
|
|
@@ -61,7 +61,7 @@
|
|
|
61
61
|
{
|
|
62
62
|
"path": ".mdkg/README.md",
|
|
63
63
|
"category": "mdkg_doc",
|
|
64
|
-
"sha256": "
|
|
64
|
+
"sha256": "5962993e8dffd53ccbceaaedd0b8f2a868421a3a1cc16d7002b661b39b7ebf79"
|
|
65
65
|
},
|
|
66
66
|
{
|
|
67
67
|
"path": ".mdkg/skills/build-pack-and-execute-task/SKILL.md",
|
|
@@ -261,7 +261,7 @@
|
|
|
261
261
|
{
|
|
262
262
|
"path": "CLI_COMMAND_MATRIX.md",
|
|
263
263
|
"category": "startup_doc",
|
|
264
|
-
"sha256": "
|
|
264
|
+
"sha256": "7322e6a2c47261f87dacd15ba0d93eba0d1e32eec0900a1e9f447ad13aaae860"
|
|
265
265
|
},
|
|
266
266
|
{
|
|
267
267
|
"path": "llms.txt",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "mdkg",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.4",
|
|
4
4
|
"description": "Markdown Knowledge Graph",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"bin": {
|
|
@@ -28,6 +28,7 @@
|
|
|
28
28
|
"smoke:operator-health": "npm run build && node scripts/smoke-operator-health.js",
|
|
29
29
|
"smoke:fix-plan": "npm run build && node scripts/smoke-fix-plan.js",
|
|
30
30
|
"smoke:branch-conflicts": "npm run build && node scripts/smoke-branch-conflicts.js",
|
|
31
|
+
"smoke:id-repair": "npm run build && node scripts/smoke-id-repair.js",
|
|
31
32
|
"smoke:command-docs": "npm run build && node scripts/smoke-command-docs.js",
|
|
32
33
|
"smoke:spike": "npm run build && node scripts/smoke-spike.js",
|
|
33
34
|
"smoke:goal-lifecycle": "npm run build && node scripts/smoke-goal-lifecycle.js",
|
|
@@ -41,7 +42,7 @@
|
|
|
41
42
|
"cli:check": "npm run build && node scripts/cli_help_snapshot.js --check",
|
|
42
43
|
"cli:contract": "npm run build && node scripts/generate-command-contract.js --check",
|
|
43
44
|
"prepack": "npm run build && node scripts/assert-publish-ready.js",
|
|
44
|
-
"prepublishOnly": "npm run test && npm run cli:check && npm run cli:contract && node dist/cli.js validate && npm run smoke:consumer && npm run smoke:matrix && npm run smoke:upgrade && npm run smoke:init && npm run smoke:capabilities && npm run smoke:db && npm run smoke:db-queue && npm run smoke:db-queue-cli && npm run smoke:db-events && npm run smoke:db-materializer && npm run smoke:db-snapshot && npm run smoke:archive-work && npm run smoke:work-invocation && npm run smoke:cli-ux-polish && npm run smoke:operator-health && npm run smoke:fix-plan && npm run smoke:branch-conflicts && npm run smoke:command-docs && npm run smoke:spike && npm run smoke:goal-lifecycle && npm run smoke:bundle && npm run smoke:subgraph && npm run smoke:visibility && npm run smoke:sqlite && npm run smoke:parallel && npm run smoke:goal && node scripts/assert-publish-ready.js",
|
|
45
|
+
"prepublishOnly": "npm run test && npm run cli:check && npm run cli:contract && node dist/cli.js validate && npm run smoke:consumer && npm run smoke:matrix && npm run smoke:upgrade && npm run smoke:init && npm run smoke:capabilities && npm run smoke:db && npm run smoke:db-queue && npm run smoke:db-queue-cli && npm run smoke:db-events && npm run smoke:db-materializer && npm run smoke:db-snapshot && npm run smoke:archive-work && npm run smoke:work-invocation && npm run smoke:cli-ux-polish && npm run smoke:operator-health && npm run smoke:fix-plan && npm run smoke:branch-conflicts && npm run smoke:id-repair && npm run smoke:command-docs && npm run smoke:spike && npm run smoke:goal-lifecycle && npm run smoke:bundle && npm run smoke:subgraph && npm run smoke:visibility && npm run smoke:sqlite && npm run smoke:parallel && npm run smoke:goal && node scripts/assert-publish-ready.js",
|
|
45
46
|
"postinstall": "node scripts/postinstall.js",
|
|
46
47
|
"smoke:subgraph": "npm run build && node scripts/smoke-subgraph.js"
|
|
47
48
|
},
|