@milehimikey/em 1.6.0 → 1.7.0
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/.claude/skills/event-modeling/SKILL.md +60 -12
- package/.claude/skills/event-modeling/reference/conform.md +30 -8
- package/.claude/skills/event-modeling/reference/em-dsl.md +145 -23
- package/.claude/skills/event-modeling/reference/extract.md +2 -1
- package/.claude/skills/event-modeling/reference/implement.md +172 -0
- package/.claude/skills/event-modeling/reference/methodology.md +5 -0
- package/.claude/skills/event-modeling/templates/conformance-report.md +15 -9
- package/.claude/skills/event-modeling/templates/model-readme.md +3 -3
- package/.claude/skills/event-modeling/templates/slice.md +70 -4
- package/.claude/skills/event-modeling/templates/state.md +6 -5
- package/README.md +1 -0
- package/dist/catalog/build.d.ts +5 -0
- package/dist/catalog/build.js +1 -3
- package/dist/catalog/build.js.map +1 -1
- package/dist/catalog/docJoin.d.ts +39 -0
- package/dist/catalog/docJoin.js +86 -0
- package/dist/catalog/docJoin.js.map +1 -0
- package/dist/catalog/driftSignal.d.ts +16 -0
- package/dist/catalog/driftSignal.js +24 -0
- package/dist/catalog/driftSignal.js.map +1 -0
- package/dist/catalog/frontmatterCoherenceValidate.d.ts +8 -0
- package/dist/catalog/frontmatterCoherenceValidate.js +52 -0
- package/dist/catalog/frontmatterCoherenceValidate.js.map +1 -0
- package/dist/catalog/lineageValidate.d.ts +9 -0
- package/dist/catalog/lineageValidate.js +161 -0
- package/dist/catalog/lineageValidate.js.map +1 -0
- package/dist/catalog/readSliceDoc.d.ts +2 -0
- package/dist/catalog/readSliceDoc.js +21 -0
- package/dist/catalog/readSliceDoc.js.map +1 -0
- package/dist/catalog/sliceDoc.d.ts +94 -3
- package/dist/catalog/sliceDoc.js +157 -7
- package/dist/catalog/sliceDoc.js.map +1 -1
- package/dist/catalog/sliceReadyValidate.d.ts +11 -0
- package/dist/catalog/sliceReadyValidate.js +85 -0
- package/dist/catalog/sliceReadyValidate.js.map +1 -0
- package/dist/cli/diff-inputs.d.ts +20 -0
- package/dist/cli/diff-inputs.js +75 -18
- package/dist/cli/diff-inputs.js.map +1 -1
- package/dist/cli/ledgerCheck.d.ts +38 -0
- package/dist/cli/ledgerCheck.js +116 -0
- package/dist/cli/ledgerCheck.js.map +1 -0
- package/dist/cli/skillCheck.d.ts +18 -0
- package/dist/cli/skillCheck.js +63 -0
- package/dist/cli/skillCheck.js.map +1 -0
- package/dist/cli/skillSync.d.ts +22 -0
- package/dist/cli/skillSync.js +69 -0
- package/dist/cli/skillSync.js.map +1 -0
- package/dist/cli/skillVersionCheck.d.ts +32 -0
- package/dist/cli/skillVersionCheck.js +145 -0
- package/dist/cli/skillVersionCheck.js.map +1 -0
- package/dist/cli.d.ts +3 -1
- package/dist/cli.js +199 -32
- package/dist/cli.js.map +1 -1
- package/dist/emit/diffJson.d.ts +1 -1
- package/dist/emit/diffJson.js +18 -14
- package/dist/emit/diffJson.js.map +1 -1
- package/dist/emit/json.d.ts +10 -25
- package/dist/emit/json.js +70 -103
- package/dist/emit/json.js.map +1 -1
- package/dist/emit/ledgerJson.d.ts +5 -0
- package/dist/emit/ledgerJson.js +25 -0
- package/dist/emit/ledgerJson.js.map +1 -0
- package/dist/emit/skillCheckJson.d.ts +5 -0
- package/dist/emit/skillCheckJson.js +32 -0
- package/dist/emit/skillCheckJson.js.map +1 -0
- package/dist/model/diff.d.ts +44 -1
- package/dist/model/diff.js +51 -9
- package/dist/model/diff.js.map +1 -1
- package/dist/model/model.d.ts +1 -1
- package/dist/model/refs.d.ts +23 -0
- package/dist/model/refs.js +75 -0
- package/dist/model/refs.js.map +1 -0
- package/dist/model/rules.d.ts +252 -0
- package/dist/model/rules.js +253 -0
- package/dist/model/rules.js.map +1 -0
- package/dist/model/validate.d.ts +23 -1
- package/dist/model/validate.js +184 -49
- package/dist/model/validate.js.map +1 -1
- package/dist/parser/ast.d.ts +3 -2
- package/dist/parser/parser.js +36 -14
- package/dist/parser/parser.js.map +1 -1
- package/dist/pipeline.d.ts +9 -0
- package/dist/pipeline.js +4 -2
- package/dist/pipeline.js.map +1 -1
- package/dist/render/sliceStatus.js +1 -1
- package/dist/render/sliceStatus.js.map +1 -1
- package/dist/util/isMainModule.d.ts +14 -0
- package/dist/util/isMainModule.js +33 -0
- package/dist/util/isMainModule.js.map +1 -0
- package/dist/util/walkDir.d.ts +4 -0
- package/dist/util/walkDir.js +28 -0
- package/dist/util/walkDir.js.map +1 -0
- package/package.json +5 -2
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
// SPDX-License-Identifier: MIT
|
|
2
|
+
// `em validate --slice-ready <key>` (MIL-87): the handoff gate without the bridge. Gives em
|
|
3
|
+
// itself a native version of the readiness check that used to live only in em-sdd-bridge's
|
|
4
|
+
// `assertReadyToImplement` — status: ready-to-implement AND every Open Question checked —
|
|
5
|
+
// so any toolchain reading slice docs directly (not just ones routed through the bridge) has
|
|
6
|
+
// a gate to enforce.
|
|
7
|
+
//
|
|
8
|
+
// Deliberately single-slice, opt-in — unlike catalog/lineageValidate.ts (MIL-84) and
|
|
9
|
+
// catalog/frontmatterCoherenceValidate.ts (MIL-85), which run unconditionally on every
|
|
10
|
+
// `em validate` because they only fire on genuine anomalies. "Not yet ready-to-implement" /
|
|
11
|
+
// "has unchecked Open Questions" is the NORMAL state of most slices most of the time (drafts,
|
|
12
|
+
// reviewed-but-not-ready-yet, ...) — splicing this into the unconditional diagnostic set would
|
|
13
|
+
// make plain `em validate` permanently noisy on any healthy in-progress model. So this only
|
|
14
|
+
// ever runs for the one slice named by `--slice-ready <key>`.
|
|
15
|
+
//
|
|
16
|
+
// "Note binding" (the ticket's own phrase) means reusing docJoin.ts's resolveSliceDocJoin — the
|
|
17
|
+
// same note-gated resolution `em export` (MIL-91) uses — rather than the filename-only
|
|
18
|
+
// readSliceDoc/existsSync convention `em catalog`/`em render` deliberately use instead (that
|
|
19
|
+
// convention has a tested invariant of ignoring `note` entirely; this module doesn't touch it).
|
|
20
|
+
// A slice with no `note "slices/<key>.md"` element bound to it isn't ready by definition: there's
|
|
21
|
+
// nothing ratified to check status/Open Questions against.
|
|
22
|
+
//
|
|
23
|
+
// version/status/link coherence (MIL-85) is folded in for free at the CLI layer (src/cli.ts):
|
|
24
|
+
// validateFrontmatterCoherence already runs unconditionally and already tags its diagnostics
|
|
25
|
+
// with `refs: [sliceKey]`, so the `--slice-ready` branch filters the full combined diagnostic
|
|
26
|
+
// set by that ref instead of this module re-deriving classifyImplementationDrift.
|
|
27
|
+
import { makeDiag, pushDiag } from "../model/rules.js";
|
|
28
|
+
import { resolveSliceDocJoin } from "./docJoin.js";
|
|
29
|
+
import { readSliceDoc } from "./readSliceDoc.js";
|
|
30
|
+
/**
|
|
31
|
+
* Readiness gate for one slice, named by its export key (`refs.sliceKeys`). `baseDir` is the
|
|
32
|
+
* `.em` file's directory, same convention every other doc/note path in em uses. Always warning-
|
|
33
|
+
* severity (fits validate's existing warning-producer shape) except for a bad `sliceKey` itself,
|
|
34
|
+
* which is an error — a CLI-argument mistake, not a model-quality finding. The `--slice-ready`
|
|
35
|
+
* flag in src/cli.ts is what decides whether these warnings gate the exit code.
|
|
36
|
+
*/
|
|
37
|
+
export function validateSliceReady(model, refs, baseDir, sliceKey) {
|
|
38
|
+
const sliceIndex = refs.sliceKeys.indexOf(sliceKey);
|
|
39
|
+
if (sliceIndex === -1) {
|
|
40
|
+
return [
|
|
41
|
+
makeDiag("slice-ready-unknown-slice", {
|
|
42
|
+
message: `no slice with export key "${sliceKey}" in this model`,
|
|
43
|
+
refs: [sliceKey],
|
|
44
|
+
}),
|
|
45
|
+
];
|
|
46
|
+
}
|
|
47
|
+
const slice = model.slices[sliceIndex];
|
|
48
|
+
const { doc, diagnostics: joinDiagnostics } = resolveSliceDocJoin(slice, sliceKey, baseDir, (id) => refs.refById.get(id));
|
|
49
|
+
if (doc.reason === "no-doc-bound") {
|
|
50
|
+
return [
|
|
51
|
+
makeDiag("slice-ready-no-doc-bound", {
|
|
52
|
+
message: `slice "${sliceKey}" has no doc bound via \`note "slices/${sliceKey}.md"\` — bind a slice doc before it can be ready-to-implement`,
|
|
53
|
+
line: slice.line,
|
|
54
|
+
refs: [sliceKey],
|
|
55
|
+
}),
|
|
56
|
+
];
|
|
57
|
+
}
|
|
58
|
+
if (doc.reason === "binding-missing-file" || doc.reason === "frontmatter-invalid") {
|
|
59
|
+
// Reuse docJoin's own diagnostics verbatim — don't re-code binding-missing-file/
|
|
60
|
+
// frontmatter-invalid a second time under a slice-ready-specific code.
|
|
61
|
+
return joinDiagnostics;
|
|
62
|
+
}
|
|
63
|
+
// doc.reason === null: found, usable frontmatter.
|
|
64
|
+
const diags = [];
|
|
65
|
+
if (doc.status !== "ready-to-implement") {
|
|
66
|
+
pushDiag(diags, "slice-ready-status-not-ready", {
|
|
67
|
+
message: `slice "${sliceKey}" is status: ${doc.status ?? "(none)"}, not ready-to-implement`,
|
|
68
|
+
line: slice.line,
|
|
69
|
+
refs: [sliceKey],
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
// resolveSliceDocJoin's SliceDocExport deliberately never carries Open Questions counts (same
|
|
73
|
+
// hard boundary that keeps doc.html/doc.raw out of it) — re-read via readSliceDoc for the
|
|
74
|
+
// full SliceDoc. Non-null: doc.reason === null already implies the file exists and parses.
|
|
75
|
+
const parsed = readSliceDoc(baseDir, sliceKey);
|
|
76
|
+
if (parsed.openQuestionsUnchecked > 0) {
|
|
77
|
+
pushDiag(diags, "slice-ready-open-questions-unchecked", {
|
|
78
|
+
message: `slice "${sliceKey}" has ${parsed.openQuestionsUnchecked} of ${parsed.openQuestionsTotal} Open Question(s) unchecked`,
|
|
79
|
+
line: slice.line,
|
|
80
|
+
refs: [sliceKey],
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
return diags;
|
|
84
|
+
}
|
|
85
|
+
//# sourceMappingURL=sliceReadyValidate.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sliceReadyValidate.js","sourceRoot":"","sources":["../../src/catalog/sliceReadyValidate.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { SliceDoc } from "../catalog/sliceDoc.js";
|
|
1
2
|
/** Resolved `em diff` invocation, or a user-facing `error` for an invalid form. */
|
|
2
3
|
export type DiffPlan = {
|
|
3
4
|
error: string;
|
|
@@ -42,3 +43,22 @@ export type RevisionResult = {
|
|
|
42
43
|
* unit-testable without a real repository.
|
|
43
44
|
*/
|
|
44
45
|
export declare function resolveRevision(file: string, rev: string, runGit?: GitRunner): RevisionResult;
|
|
46
|
+
/**
|
|
47
|
+
* Resolve a slice's doc (`slices/<sliceKey>.md`, relative to `anchorFile`'s directory — same
|
|
48
|
+
* convention as every other doc/note path in em) at git revision `rev`. Unlike
|
|
49
|
+
* `resolveRevision`, a missing doc at a revision is routine (not every slice had a doc yet, or
|
|
50
|
+
* the doc simply isn't tracked) rather than a user-facing error — so every failure mode
|
|
51
|
+
* collapses to `null`, for `em diff`'s lineage annotation (MIL-84) to treat as "nothing to
|
|
52
|
+
* annotate with" rather than aborting the diff.
|
|
53
|
+
*/
|
|
54
|
+
export declare function resolveDocAtRevision(anchorFile: string, sliceKey: string, rev: string, runGit?: GitRunner): SliceDoc | null;
|
|
55
|
+
/**
|
|
56
|
+
* Slice doc keys (filename stems under `slices/`, relative to `anchorFile`'s directory — same
|
|
57
|
+
* anchor convention as `resolveDocAtRevision`) present at git revision `rev`. Used by `em ledger`
|
|
58
|
+
* (MIL-89) to discover the candidate set of docs to compare across two revisions, reusing the
|
|
59
|
+
* same repo-root resolution `showAtRevision` does rather than a second, divergent way of finding
|
|
60
|
+
* "what slice docs exist here." Returns `[]` (not an error) when the repo can't be resolved or
|
|
61
|
+
* `slices/` didn't exist at `rev` — both routine (a model with no slice docs yet), matching
|
|
62
|
+
* `resolveDocAtRevision`'s "missing is routine" stance.
|
|
63
|
+
*/
|
|
64
|
+
export declare function listSliceKeysAtRevision(anchorFile: string, rev: string, runGit?: GitRunner): string[];
|
package/dist/cli/diff-inputs.js
CHANGED
|
@@ -4,7 +4,8 @@
|
|
|
4
4
|
// unit-tested without spawning a subprocess or a real git repo (cli.ts keeps the
|
|
5
5
|
// process.exit/console wiring; see test/diff-inputs.test.ts).
|
|
6
6
|
import { spawnSync } from "node:child_process";
|
|
7
|
-
import { dirname, resolve } from "node:path";
|
|
7
|
+
import { basename, dirname, resolve } from "node:path";
|
|
8
|
+
import { parseSliceDoc } from "../catalog/sliceDoc.js";
|
|
8
9
|
/**
|
|
9
10
|
* Validate the `em diff` argument/flag combination. Pure — no I/O. The two forms
|
|
10
11
|
* (two files, or one file across git revisions) are mutually exclusive.
|
|
@@ -33,30 +34,86 @@ export const realGit = (args) => {
|
|
|
33
34
|
const r = spawnSync("git", args, { encoding: "utf8" });
|
|
34
35
|
return { status: r.status, stdout: r.stdout ?? "", stderr: r.stderr ?? "" };
|
|
35
36
|
};
|
|
37
|
+
/**
|
|
38
|
+
* Read `targetAbs`'s content at git revision `rev`, resolving the repo root from
|
|
39
|
+
* `anchorFile`'s directory (a file known to be somewhere inside the repo — doesn't itself have
|
|
40
|
+
* to be `targetAbs`, e.g. a slice doc resolved relative to its `.em` file's location). The git
|
|
41
|
+
* runner is injectable so every failure branch is unit-testable without a real repository.
|
|
42
|
+
*
|
|
43
|
+
* Existence/path lookup uses `git ls-tree -r --name-only <rev>` — scoped to `rev` itself, not
|
|
44
|
+
* `git ls-files` (which reflects the *current* index/working tree). A doc that existed at
|
|
45
|
+
* `rev` but has since been deleted — the routine case for a `superseded-by`-carrying doc, which
|
|
46
|
+
* gets removed in the very commit that performs the split/merge/removal it records — would
|
|
47
|
+
* otherwise resolve to "not tracked" even though `git show <rev>:<path>` can read it just fine.
|
|
48
|
+
*/
|
|
49
|
+
function showAtRevision(runGit, anchorFile, targetAbs, rev) {
|
|
50
|
+
const toplevel = runGit(["-C", dirname(resolve(anchorFile)), "rev-parse", "--show-toplevel"]);
|
|
51
|
+
if (toplevel.status !== 0)
|
|
52
|
+
return { ok: false, reason: "no-repo" };
|
|
53
|
+
const repoRoot = toplevel.stdout.trim();
|
|
54
|
+
const lsTree = runGit(["-C", repoRoot, "ls-tree", "-r", "--name-only", rev, "--", targetAbs]);
|
|
55
|
+
const relPath = lsTree.stdout.trim().split("\n")[0];
|
|
56
|
+
if (!relPath)
|
|
57
|
+
return { ok: false, reason: "not-tracked", repoRoot };
|
|
58
|
+
const show = runGit(["-C", repoRoot, "show", `${rev}:${relPath}`]);
|
|
59
|
+
if (show.status !== 0) {
|
|
60
|
+
return { ok: false, reason: "show-failed", relPath, detail: (show.stderr || "").trim() || "unknown git error" };
|
|
61
|
+
}
|
|
62
|
+
return { ok: true, content: show.stdout };
|
|
63
|
+
}
|
|
36
64
|
/**
|
|
37
65
|
* Resolve `file`'s content at git revision `rev` via `git show <rev>:<repo-relative-path>`.
|
|
38
66
|
* The git runner is injectable so the not-a-repo / not-tracked / bad-rev branches are
|
|
39
67
|
* unit-testable without a real repository.
|
|
40
68
|
*/
|
|
41
69
|
export function resolveRevision(file, rev, runGit = realGit) {
|
|
42
|
-
const
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
70
|
+
const result = showAtRevision(runGit, file, resolve(file), rev);
|
|
71
|
+
if (result.ok)
|
|
72
|
+
return result;
|
|
73
|
+
switch (result.reason) {
|
|
74
|
+
case "no-repo":
|
|
75
|
+
return { ok: false, message: `em diff: ${file} is not inside a git repository (needed for --from/--to)` };
|
|
76
|
+
case "not-tracked":
|
|
77
|
+
return { ok: false, message: `em diff: ${file} is not tracked by git in ${result.repoRoot}` };
|
|
78
|
+
case "show-failed":
|
|
79
|
+
return { ok: false, message: `em diff: cannot read ${result.relPath} at revision "${rev}": ${result.detail}` };
|
|
46
80
|
}
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Resolve a slice's doc (`slices/<sliceKey>.md`, relative to `anchorFile`'s directory — same
|
|
84
|
+
* convention as every other doc/note path in em) at git revision `rev`. Unlike
|
|
85
|
+
* `resolveRevision`, a missing doc at a revision is routine (not every slice had a doc yet, or
|
|
86
|
+
* the doc simply isn't tracked) rather than a user-facing error — so every failure mode
|
|
87
|
+
* collapses to `null`, for `em diff`'s lineage annotation (MIL-84) to treat as "nothing to
|
|
88
|
+
* annotate with" rather than aborting the diff.
|
|
89
|
+
*/
|
|
90
|
+
export function resolveDocAtRevision(anchorFile, sliceKey, rev, runGit = realGit) {
|
|
91
|
+
const targetAbs = resolve(dirname(resolve(anchorFile)), "slices", `${sliceKey}.md`);
|
|
92
|
+
const result = showAtRevision(runGit, anchorFile, targetAbs, rev);
|
|
93
|
+
return result.ok ? parseSliceDoc(result.content) : null;
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Slice doc keys (filename stems under `slices/`, relative to `anchorFile`'s directory — same
|
|
97
|
+
* anchor convention as `resolveDocAtRevision`) present at git revision `rev`. Used by `em ledger`
|
|
98
|
+
* (MIL-89) to discover the candidate set of docs to compare across two revisions, reusing the
|
|
99
|
+
* same repo-root resolution `showAtRevision` does rather than a second, divergent way of finding
|
|
100
|
+
* "what slice docs exist here." Returns `[]` (not an error) when the repo can't be resolved or
|
|
101
|
+
* `slices/` didn't exist at `rev` — both routine (a model with no slice docs yet), matching
|
|
102
|
+
* `resolveDocAtRevision`'s "missing is routine" stance.
|
|
103
|
+
*/
|
|
104
|
+
export function listSliceKeysAtRevision(anchorFile, rev, runGit = realGit) {
|
|
105
|
+
const toplevel = runGit(["-C", dirname(resolve(anchorFile)), "rev-parse", "--show-toplevel"]);
|
|
106
|
+
if (toplevel.status !== 0)
|
|
107
|
+
return [];
|
|
47
108
|
const repoRoot = toplevel.stdout.trim();
|
|
48
|
-
const
|
|
49
|
-
const
|
|
50
|
-
if (
|
|
51
|
-
return
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
message: `em diff: cannot read ${relPath} at revision "${rev}": ${(show.stderr || "").trim() || "unknown git error"}`,
|
|
58
|
-
};
|
|
59
|
-
}
|
|
60
|
-
return { ok: true, content: show.stdout };
|
|
109
|
+
const slicesDirAbs = resolve(dirname(resolve(anchorFile)), "slices");
|
|
110
|
+
const lsTree = runGit(["-C", repoRoot, "ls-tree", "-r", "--name-only", rev, "--", slicesDirAbs]);
|
|
111
|
+
if (lsTree.status !== 0)
|
|
112
|
+
return [];
|
|
113
|
+
return lsTree.stdout
|
|
114
|
+
.split("\n")
|
|
115
|
+
.map((line) => line.trim())
|
|
116
|
+
.filter((line) => line.endsWith(".md"))
|
|
117
|
+
.map((line) => basename(line, ".md"));
|
|
61
118
|
}
|
|
62
119
|
//# sourceMappingURL=diff-inputs.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"diff-inputs.js","sourceRoot":"","sources":["../../src/cli/diff-inputs.ts"],"names":[],"mappings":"AAAA,+BAA+B;AAC/B,sEAAsE;AACtE,gFAAgF;AAChF,iFAAiF;AACjF,8DAA8D;AAE9D,OAAO,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAC;AAC/C,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;
|
|
1
|
+
{"version":3,"file":"diff-inputs.js","sourceRoot":"","sources":["../../src/cli/diff-inputs.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { GitRunner } from "./diff-inputs.js";
|
|
2
|
+
export type LedgerFindingCode = "ledger-content-without-version-bump" | "ledger-version-without-content-change" | "ledger-version-regression";
|
|
3
|
+
export interface LedgerFinding {
|
|
4
|
+
sliceKey: string;
|
|
5
|
+
code: LedgerFindingCode;
|
|
6
|
+
message: string;
|
|
7
|
+
oldVersion: number;
|
|
8
|
+
newVersion: number;
|
|
9
|
+
bodyChanged: boolean;
|
|
10
|
+
lineageChanged: boolean;
|
|
11
|
+
}
|
|
12
|
+
export type LedgerSkipReason = "no-prior-revision" | "deleted" | "frontmatter-invalid";
|
|
13
|
+
export interface LedgerSkip {
|
|
14
|
+
sliceKey: string;
|
|
15
|
+
reason: LedgerSkipReason;
|
|
16
|
+
}
|
|
17
|
+
export interface LedgerCheckResult {
|
|
18
|
+
findings: LedgerFinding[];
|
|
19
|
+
skipped: LedgerSkip[];
|
|
20
|
+
/** Total slice keys considered (the union of both revisions' `slices/*.md` — every key that
|
|
21
|
+
* produced either a finding, a skip, or a clean pass). Lets a caller report "N checked"
|
|
22
|
+
* without re-deriving it from findings.length + skipped.length + (silent clean count). */
|
|
23
|
+
checkedCount: number;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Compare every slice doc's version:/content agreement between two revisions of `anchorFile`'s
|
|
27
|
+
* repo. `to` null means "the current working tree" (same convention as `em diff`). Pure aside
|
|
28
|
+
* from the injected GitRunner and fs reads via readSliceDoc — no process.exit, no console.
|
|
29
|
+
*
|
|
30
|
+
* Deliberately excludes `status`/`implementedIn` from the content comparison: those change
|
|
31
|
+
* independently by design during re-ratification (docs/slice-doc-schema.md, "`status` under
|
|
32
|
+
* re-ratification") — including them would false-positive on every ordinary lifecycle
|
|
33
|
+
* transition. Also excludes `pattern`/`swimlane`/`schemaVersion`: decorative, not currently
|
|
34
|
+
* exposed on `SliceDoc` at all. Body comparison is `.trim()`-only — strips leading/trailing
|
|
35
|
+
* whitespace, avoiding a false positive from a trailing-newline/CRLF-only re-save, without
|
|
36
|
+
* risking a false negative on a real prose edit.
|
|
37
|
+
*/
|
|
38
|
+
export declare function checkLedger(anchorFile: string, from: string, to: string | null, runGit?: GitRunner): LedgerCheckResult;
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
// SPDX-License-Identifier: MIT
|
|
2
|
+
// `em ledger` (MIL-89): checks that a slice doc's `version:` frontmatter field and its content
|
|
3
|
+
// (body + lineage refs) always change together across two git revisions — a version bump with
|
|
4
|
+
// no real content change is a no-op ledger entry, a content change with no version bump is a
|
|
5
|
+
// stale ratification signal, and a version going backwards is a typo/regression. Opt-in,
|
|
6
|
+
// CI-recipe tier — never folded into `em validate`, which stays a fast function of the current
|
|
7
|
+
// tree (see lineageValidate.ts's header comment, which named this exact shape during MIL-84).
|
|
8
|
+
//
|
|
9
|
+
// Sibling to diff-inputs.ts in role: testable input handling / git-revision logic factored out
|
|
10
|
+
// of cli.ts, with an injectable GitRunner and no process.exit/console — see
|
|
11
|
+
// test/ledgerCheck.test.ts. Reuses resolveDocAtRevision/listSliceKeysAtRevision rather than
|
|
12
|
+
// second, divergent git plumbing.
|
|
13
|
+
import { existsSync, readdirSync } from "node:fs";
|
|
14
|
+
import { basename, dirname, join, resolve } from "node:path";
|
|
15
|
+
import { hasUsableFrontmatter } from "../catalog/sliceDoc.js";
|
|
16
|
+
import { readSliceDoc } from "../catalog/readSliceDoc.js";
|
|
17
|
+
import { realGit, listSliceKeysAtRevision, resolveDocAtRevision } from "./diff-inputs.js";
|
|
18
|
+
/** Slice keys currently on disk under `<baseDir>/slices/*.md` — the working-tree counterpart to
|
|
19
|
+
* `listSliceKeysAtRevision`, used when `to` is null (compare against the working tree). */
|
|
20
|
+
function listSliceKeysOnDisk(baseDir) {
|
|
21
|
+
const dir = join(baseDir, "slices");
|
|
22
|
+
if (!existsSync(dir))
|
|
23
|
+
return [];
|
|
24
|
+
return readdirSync(dir)
|
|
25
|
+
.filter((name) => name.endsWith(".md"))
|
|
26
|
+
.map((name) => basename(name, ".md"));
|
|
27
|
+
}
|
|
28
|
+
/** True when two lineage refs (or their absence) carry the same declared text — compared on
|
|
29
|
+
* `raw` so a malformed ref (null sliceKey/version) still counts as a real change if its text
|
|
30
|
+
* changes, not silently treated as equal to another malformed ref. */
|
|
31
|
+
function refEqual(a, b) {
|
|
32
|
+
if (a === null || b === null)
|
|
33
|
+
return a === b;
|
|
34
|
+
return a.raw === b.raw;
|
|
35
|
+
}
|
|
36
|
+
function refListEqual(a, b) {
|
|
37
|
+
if (a.length !== b.length)
|
|
38
|
+
return false;
|
|
39
|
+
return a.every((ref, i) => refEqual(ref, b[i]));
|
|
40
|
+
}
|
|
41
|
+
/** True when either side's declared lineage (`split-from`/`merged-from`/`superseded-by`)
|
|
42
|
+
* differs — these describe what the doc *asserts*, same-commit-authoring convention means a
|
|
43
|
+
* legitimate change here always co-occurs with a version bump (docs/slice-doc-schema.md). */
|
|
44
|
+
function lineageChanged(oldDoc, newDoc) {
|
|
45
|
+
return (!refEqual(oldDoc.splitFrom, newDoc.splitFrom) ||
|
|
46
|
+
!refListEqual(oldDoc.mergedFrom, newDoc.mergedFrom) ||
|
|
47
|
+
!refListEqual(oldDoc.supersededBy, newDoc.supersededBy));
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Compare every slice doc's version:/content agreement between two revisions of `anchorFile`'s
|
|
51
|
+
* repo. `to` null means "the current working tree" (same convention as `em diff`). Pure aside
|
|
52
|
+
* from the injected GitRunner and fs reads via readSliceDoc — no process.exit, no console.
|
|
53
|
+
*
|
|
54
|
+
* Deliberately excludes `status`/`implementedIn` from the content comparison: those change
|
|
55
|
+
* independently by design during re-ratification (docs/slice-doc-schema.md, "`status` under
|
|
56
|
+
* re-ratification") — including them would false-positive on every ordinary lifecycle
|
|
57
|
+
* transition. Also excludes `pattern`/`swimlane`/`schemaVersion`: decorative, not currently
|
|
58
|
+
* exposed on `SliceDoc` at all. Body comparison is `.trim()`-only — strips leading/trailing
|
|
59
|
+
* whitespace, avoiding a false positive from a trailing-newline/CRLF-only re-save, without
|
|
60
|
+
* risking a false negative on a real prose edit.
|
|
61
|
+
*/
|
|
62
|
+
export function checkLedger(anchorFile, from, to, runGit = realGit) {
|
|
63
|
+
const baseDir = dirname(resolve(anchorFile));
|
|
64
|
+
const oldKeys = listSliceKeysAtRevision(anchorFile, from, runGit);
|
|
65
|
+
const newKeys = to ? listSliceKeysAtRevision(anchorFile, to, runGit) : listSliceKeysOnDisk(baseDir);
|
|
66
|
+
const keys = [...new Set([...oldKeys, ...newKeys])].sort();
|
|
67
|
+
const findings = [];
|
|
68
|
+
const skipped = [];
|
|
69
|
+
for (const sliceKey of keys) {
|
|
70
|
+
const oldDoc = resolveDocAtRevision(anchorFile, sliceKey, from, runGit);
|
|
71
|
+
if (!oldDoc) {
|
|
72
|
+
skipped.push({ sliceKey, reason: "no-prior-revision" });
|
|
73
|
+
continue;
|
|
74
|
+
}
|
|
75
|
+
const newDoc = to ? resolveDocAtRevision(anchorFile, sliceKey, to, runGit) : readSliceDoc(baseDir, sliceKey);
|
|
76
|
+
if (!newDoc) {
|
|
77
|
+
skipped.push({ sliceKey, reason: "deleted" });
|
|
78
|
+
continue;
|
|
79
|
+
}
|
|
80
|
+
// hasUsableFrontmatter() only checks that the `version` key is *present*
|
|
81
|
+
// (missingRequiredFields), not that its value parsed to a valid positive integer —
|
|
82
|
+
// `version: abc`/`version: 0`/`version: 1.5` all pass that gate with SliceDoc.version still
|
|
83
|
+
// null (see sliceDoc.ts's parseVersion()). lineageValidate.ts hits the same gap and handles
|
|
84
|
+
// it the same way: an explicit null check, routed to the same skip a missing key gets,
|
|
85
|
+
// rather than a non-null assertion that would corrupt a finding's message/classification.
|
|
86
|
+
if (!hasUsableFrontmatter(oldDoc) || !hasUsableFrontmatter(newDoc) || oldDoc.version === null || newDoc.version === null) {
|
|
87
|
+
skipped.push({ sliceKey, reason: "frontmatter-invalid" });
|
|
88
|
+
continue;
|
|
89
|
+
}
|
|
90
|
+
const oldVersion = oldDoc.version;
|
|
91
|
+
const newVersion = newDoc.version;
|
|
92
|
+
const bodyChanged = oldDoc.body.trim() !== newDoc.body.trim();
|
|
93
|
+
const docsLineageChanged = lineageChanged(oldDoc, newDoc);
|
|
94
|
+
const contentChanged = bodyChanged || docsLineageChanged;
|
|
95
|
+
let code = null;
|
|
96
|
+
if (newVersion < oldVersion) {
|
|
97
|
+
code = "ledger-version-regression";
|
|
98
|
+
}
|
|
99
|
+
else if (contentChanged && newVersion === oldVersion) {
|
|
100
|
+
code = "ledger-content-without-version-bump";
|
|
101
|
+
}
|
|
102
|
+
else if (!contentChanged && newVersion > oldVersion) {
|
|
103
|
+
code = "ledger-version-without-content-change";
|
|
104
|
+
}
|
|
105
|
+
if (!code)
|
|
106
|
+
continue;
|
|
107
|
+
const message = code === "ledger-version-regression"
|
|
108
|
+
? `slice "${sliceKey}": version went backwards (v${oldVersion} -> v${newVersion})`
|
|
109
|
+
: code === "ledger-content-without-version-bump"
|
|
110
|
+
? `slice "${sliceKey}": doc content changed but version: didn't bump (still v${oldVersion})`
|
|
111
|
+
: `slice "${sliceKey}": version: bumped (v${oldVersion} -> v${newVersion}) but doc content is unchanged`;
|
|
112
|
+
findings.push({ sliceKey, code, message, oldVersion, newVersion, bodyChanged, lineageChanged: docsLineageChanged });
|
|
113
|
+
}
|
|
114
|
+
return { findings, skipped, checkedCount: keys.length };
|
|
115
|
+
}
|
|
116
|
+
//# sourceMappingURL=ledgerCheck.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ledgerCheck.js","sourceRoot":"","sources":["../../src/cli/ledgerCheck.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export type SkillCheckFindingCode = "skill-check-not-installed" | "skill-check-stamp-missing" | "skill-check-stamp-mismatch" | "skill-check-content-drift";
|
|
2
|
+
export interface SkillCheckFinding {
|
|
3
|
+
code: SkillCheckFindingCode;
|
|
4
|
+
message: string;
|
|
5
|
+
vendoredStamp?: string | null;
|
|
6
|
+
installedVersion?: string;
|
|
7
|
+
driftedFiles?: string[];
|
|
8
|
+
}
|
|
9
|
+
export interface SkillCheckResult {
|
|
10
|
+
findings: SkillCheckFinding[];
|
|
11
|
+
ok: boolean;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Pure: compares vendoredDir against packagedDir (content) and installedVersion (em --version
|
|
15
|
+
* — injected, never re-derived; see src/cli.ts's PKG_VERSION) against the vendored SKILL.md's
|
|
16
|
+
* em-version: stamp. No process.exit, no console.
|
|
17
|
+
*/
|
|
18
|
+
export declare function checkSkillSync(packagedDir: string, vendoredDir: string, installedVersion: string): SkillCheckResult;
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
// SPDX-License-Identifier: MIT
|
|
2
|
+
// `em skill check` (MIL-93): checks a consuming repo's vendored skill copy for drift against
|
|
3
|
+
// the em package actually installed — the downstream half of the skill-drift problem (the
|
|
4
|
+
// in-repo half is MIL-92's skillVersionCheck.ts, which checks *this* repo's own package.json
|
|
5
|
+
// against *its own* bundled skill across two git revisions). This check has no git revisions
|
|
6
|
+
// at all: both sides are directories on disk right now, so unlike ledgerCheck.ts/
|
|
7
|
+
// skillVersionCheck.ts there's no GitRunner to inject.
|
|
8
|
+
//
|
|
9
|
+
// Two independent signals, both reported (never short-circuited on the first):
|
|
10
|
+
// - the vendored SKILL.md's em-version: stamp vs. the installed em's own version
|
|
11
|
+
// - a full content diff against the packaged skill (reusing planSkillSync's hash-walk), which
|
|
12
|
+
// catches a hand-edited vendored file even when its stamp still happens to match — the
|
|
13
|
+
// ticket's explicit "stamp says current, content isn't" case.
|
|
14
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
15
|
+
import { join } from "node:path";
|
|
16
|
+
import { extractEmVersionStamp } from "./skillVersionCheck.js";
|
|
17
|
+
import { planSkillSync } from "./skillSync.js";
|
|
18
|
+
/**
|
|
19
|
+
* Pure: compares vendoredDir against packagedDir (content) and installedVersion (em --version
|
|
20
|
+
* — injected, never re-derived; see src/cli.ts's PKG_VERSION) against the vendored SKILL.md's
|
|
21
|
+
* em-version: stamp. No process.exit, no console.
|
|
22
|
+
*/
|
|
23
|
+
export function checkSkillSync(packagedDir, vendoredDir, installedVersion) {
|
|
24
|
+
if (!existsSync(vendoredDir)) {
|
|
25
|
+
return {
|
|
26
|
+
ok: false,
|
|
27
|
+
findings: [
|
|
28
|
+
{
|
|
29
|
+
code: "skill-check-not-installed",
|
|
30
|
+
message: `no vendored skill found at ${vendoredDir} — run \`em skill sync\` first`,
|
|
31
|
+
},
|
|
32
|
+
],
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
const findings = [];
|
|
36
|
+
const skillMdPath = join(vendoredDir, "SKILL.md");
|
|
37
|
+
const vendoredStamp = existsSync(skillMdPath) ? extractEmVersionStamp(readFileSync(skillMdPath, "utf8")) : null;
|
|
38
|
+
if (vendoredStamp === null) {
|
|
39
|
+
findings.push({
|
|
40
|
+
code: "skill-check-stamp-missing",
|
|
41
|
+
message: `${skillMdPath} has no em-version: stamp — vendored copy predates MIL-92 or was hand-edited`,
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
else if (vendoredStamp !== installedVersion) {
|
|
45
|
+
findings.push({
|
|
46
|
+
code: "skill-check-stamp-mismatch",
|
|
47
|
+
message: `vendored skill's em-version: stamp (${vendoredStamp}) doesn't match installed em (${installedVersion}) — run \`em skill sync\``,
|
|
48
|
+
vendoredStamp,
|
|
49
|
+
installedVersion,
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
const plan = planSkillSync(packagedDir, vendoredDir);
|
|
53
|
+
if (plan.changes.length > 0) {
|
|
54
|
+
const driftedFiles = plan.changes.map((c) => c.relPath).sort();
|
|
55
|
+
findings.push({
|
|
56
|
+
code: "skill-check-content-drift",
|
|
57
|
+
message: `vendored skill differs from the packaged skill in ${driftedFiles.length} file(s) — run \`em skill sync\``,
|
|
58
|
+
driftedFiles,
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
return { findings, ok: findings.length === 0 };
|
|
62
|
+
}
|
|
63
|
+
//# sourceMappingURL=skillCheck.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"skillCheck.js","sourceRoot":"","sources":["../../src/cli/skillCheck.ts"],"names":[],"mappings":"AAAA,+BAA+B;AAC/B,6FAA6F;AAC7F,0FAA0F;AAC1F,6FAA6F;AAC7F,6FAA6F;AAC7F,kFAAkF;AAClF,uDAAuD;AACvD,EAAE;AACF,+EAA+E;AAC/E,kFAAkF;AAClF,+FAA+F;AAC/F,0FAA0F;AAC1F,iEAAiE;AAEjE,OAAO,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AACnD,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,qBAAqB,EAAE,MAAM,wBAAwB,CAAC;AAC/D,OAAO,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAC;AAqB/C;;;;GAIG;AACH,MAAM,UAAU,cAAc,CAAC,WAAmB,EAAE,WAAmB,EAAE,gBAAwB;IAC/F,IAAI,CAAC,UAAU,CAAC,WAAW,CAAC,EAAE,CAAC;QAC7B,OAAO;YACL,EAAE,EAAE,KAAK;YACT,QAAQ,EAAE;gBACR;oBACE,IAAI,EAAE,2BAA2B;oBACjC,OAAO,EAAE,8BAA8B,WAAW,gCAAgC;iBACnF;aACF;SACF,CAAC;IACJ,CAAC;IAED,MAAM,QAAQ,GAAwB,EAAE,CAAC;IAEzC,MAAM,WAAW,GAAG,IAAI,CAAC,WAAW,EAAE,UAAU,CAAC,CAAC;IAClD,MAAM,aAAa,GAAG,UAAU,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC,qBAAqB,CAAC,YAAY,CAAC,WAAW,EAAE,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;IAEhH,IAAI,aAAa,KAAK,IAAI,EAAE,CAAC;QAC3B,QAAQ,CAAC,IAAI,CAAC;YACZ,IAAI,EAAE,2BAA2B;YACjC,OAAO,EAAE,GAAG,WAAW,8EAA8E;SACtG,CAAC,CAAC;IACL,CAAC;SAAM,IAAI,aAAa,KAAK,gBAAgB,EAAE,CAAC;QAC9C,QAAQ,CAAC,IAAI,CAAC;YACZ,IAAI,EAAE,4BAA4B;YAClC,OAAO,EAAE,uCAAuC,aAAa,iCAAiC,gBAAgB,2BAA2B;YACzI,aAAa;YACb,gBAAgB;SACjB,CAAC,CAAC;IACL,CAAC;IAED,MAAM,IAAI,GAAG,aAAa,CAAC,WAAW,EAAE,WAAW,CAAC,CAAC;IACrD,IAAI,IAAI,CAAC,OAAO,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QAC5B,MAAM,YAAY,GAAG,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,IAAI,EAAE,CAAC;QAC/D,QAAQ,CAAC,IAAI,CAAC;YACZ,IAAI,EAAE,2BAA2B;YACjC,OAAO,EAAE,qDAAqD,YAAY,CAAC,MAAM,kCAAkC;YACnH,YAAY;SACb,CAAC,CAAC;IACL,CAAC;IAED,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,QAAQ,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;AACjD,CAAC"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
export type SkillSyncFileChangeKind = "added" | "modified" | "removed";
|
|
2
|
+
export interface SkillSyncFileChange {
|
|
3
|
+
relPath: string;
|
|
4
|
+
kind: SkillSyncFileChangeKind;
|
|
5
|
+
}
|
|
6
|
+
export interface SkillSyncPlan {
|
|
7
|
+
/** Sorted by relPath. Empty means vendoredDir already matches packagedDir exactly. */
|
|
8
|
+
changes: SkillSyncFileChange[];
|
|
9
|
+
/** Files present, identical, in both dirs — not listed in `changes`. */
|
|
10
|
+
unchangedCount: number;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Pure: compares packagedDir (source of truth) against vendoredDir (which may not exist yet —
|
|
14
|
+
* that's the fresh-sync case, not an error) by per-file sha256. No fs writes.
|
|
15
|
+
*/
|
|
16
|
+
export declare function planSkillSync(packagedDir: string, vendoredDir: string): SkillSyncPlan;
|
|
17
|
+
/**
|
|
18
|
+
* Shell: applies a plan produced by planSkillSync — copies added/modified files from
|
|
19
|
+
* packagedDir into vendoredDir, deletes removed files from vendoredDir. Idempotent; a plan
|
|
20
|
+
* with no changes is a no-op. Creates vendoredDir (and any needed subdirectories) as needed.
|
|
21
|
+
*/
|
|
22
|
+
export declare function applySkillSync(plan: SkillSyncPlan, packagedDir: string, vendoredDir: string): void;
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
// SPDX-License-Identifier: MIT
|
|
2
|
+
// `em skill sync` (MIL-93): materializes/updates a consuming repo's vendored copy of the
|
|
3
|
+
// event-modeling skill from the em package actually installed. Unlike `em ledger`/
|
|
4
|
+
// skillVersionCheck.ts (which diff two *git revisions* of the same repo), this diffs two
|
|
5
|
+
// *directories on disk right now* — the packaged skill bundled with the running em, and a
|
|
6
|
+
// vendored copy in some other repo — so there's no GitRunner here at all.
|
|
7
|
+
//
|
|
8
|
+
// Contract: the vendored copy is read-only from the consuming repo's point of view. Local
|
|
9
|
+
// edits are never merged — sync always makes vendoredDir byte-identical to packagedDir, the
|
|
10
|
+
// same "you don't own this copy" stance rlm-blueprint's hand-rolled sync guard already
|
|
11
|
+
// enforced by hand. That's why, unlike `em skill install`, there's no --force/confirmation
|
|
12
|
+
// gate: overwriting is the entire point.
|
|
13
|
+
//
|
|
14
|
+
// Split into a pure plan (walk + hash, no writes) and a thin apply (the actual fs mutation) so
|
|
15
|
+
// `em skill check` (src/cli/skillCheck.ts) can reuse planSkillSync's diff for its own
|
|
16
|
+
// content-drift detection without performing any writes.
|
|
17
|
+
import { existsSync, mkdirSync, copyFileSync, readFileSync, rmSync } from "node:fs";
|
|
18
|
+
import { createHash } from "node:crypto";
|
|
19
|
+
import { dirname, join } from "node:path";
|
|
20
|
+
import { walkDir } from "../util/walkDir.js";
|
|
21
|
+
function sha256(path) {
|
|
22
|
+
return createHash("sha256").update(readFileSync(path)).digest("hex");
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Pure: compares packagedDir (source of truth) against vendoredDir (which may not exist yet —
|
|
26
|
+
* that's the fresh-sync case, not an error) by per-file sha256. No fs writes.
|
|
27
|
+
*/
|
|
28
|
+
export function planSkillSync(packagedDir, vendoredDir) {
|
|
29
|
+
const packagedFiles = new Set(walkDir(packagedDir));
|
|
30
|
+
const vendoredFiles = new Set(existsSync(vendoredDir) ? walkDir(vendoredDir) : []);
|
|
31
|
+
const allPaths = [...new Set([...packagedFiles, ...vendoredFiles])].sort();
|
|
32
|
+
const changes = [];
|
|
33
|
+
let unchangedCount = 0;
|
|
34
|
+
for (const relPath of allPaths) {
|
|
35
|
+
const inPackaged = packagedFiles.has(relPath);
|
|
36
|
+
const inVendored = vendoredFiles.has(relPath);
|
|
37
|
+
if (inPackaged && !inVendored) {
|
|
38
|
+
changes.push({ relPath, kind: "added" });
|
|
39
|
+
}
|
|
40
|
+
else if (!inPackaged && inVendored) {
|
|
41
|
+
changes.push({ relPath, kind: "removed" });
|
|
42
|
+
}
|
|
43
|
+
else if (sha256(join(packagedDir, relPath)) !== sha256(join(vendoredDir, relPath))) {
|
|
44
|
+
changes.push({ relPath, kind: "modified" });
|
|
45
|
+
}
|
|
46
|
+
else {
|
|
47
|
+
unchangedCount++;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
return { changes, unchangedCount };
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Shell: applies a plan produced by planSkillSync — copies added/modified files from
|
|
54
|
+
* packagedDir into vendoredDir, deletes removed files from vendoredDir. Idempotent; a plan
|
|
55
|
+
* with no changes is a no-op. Creates vendoredDir (and any needed subdirectories) as needed.
|
|
56
|
+
*/
|
|
57
|
+
export function applySkillSync(plan, packagedDir, vendoredDir) {
|
|
58
|
+
for (const change of plan.changes) {
|
|
59
|
+
const dest = join(vendoredDir, change.relPath);
|
|
60
|
+
if (change.kind === "removed") {
|
|
61
|
+
rmSync(dest);
|
|
62
|
+
}
|
|
63
|
+
else {
|
|
64
|
+
mkdirSync(dirname(dest), { recursive: true });
|
|
65
|
+
copyFileSync(join(packagedDir, change.relPath), dest);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
//# sourceMappingURL=skillSync.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"skillSync.js","sourceRoot":"","sources":["../../src/cli/skillSync.ts"],"names":[],"mappings":"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"}
|