@esneiderbravo/speclaw 2.0.21 → 2.0.22
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.
|
@@ -8,6 +8,7 @@ import crypto from "node:crypto";
|
|
|
8
8
|
import fs from "node:fs";
|
|
9
9
|
import path from "node:path";
|
|
10
10
|
import { pkgName, pkgVersion } from "../../shared/version.js";
|
|
11
|
+
import { COMPASS_MAP_END, COMPASS_MAP_START, stripCompassMapBlock, } from "../../shared/compass-map.js";
|
|
11
12
|
export const LOCKFILE_NAME = "speclaw.lock";
|
|
12
13
|
export const LOCKFILE_VERSION = 1;
|
|
13
14
|
/** Delimited provenance block excluded from digests (self-reference). */
|
|
@@ -270,23 +271,9 @@ function walkFiles(dir, projectPath, out, pred) {
|
|
|
270
271
|
}
|
|
271
272
|
}
|
|
272
273
|
}
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
export
|
|
276
|
-
/**
|
|
277
|
-
* Strip the regenerable map body between markers so integrity digests stay stable
|
|
278
|
-
* across `speclaw index` (which rewrites the map in CI before verify).
|
|
279
|
-
*
|
|
280
|
-
* @param text - Full docs/compass.md contents.
|
|
281
|
-
* @returns The same text with an empty map body, or `text` if markers are missing.
|
|
282
|
-
*/
|
|
283
|
-
export function stripCompassMapBlock(text) {
|
|
284
|
-
const start = text.indexOf(COMPASS_MAP_START);
|
|
285
|
-
const end = text.indexOf(COMPASS_MAP_END);
|
|
286
|
-
if (start < 0 || end < 0 || end < start)
|
|
287
|
-
return text;
|
|
288
|
-
return text.slice(0, start + COMPASS_MAP_START.length) + "\n" + text.slice(end);
|
|
289
|
-
}
|
|
274
|
+
// The map block helpers moved to shared/ so the lawbook can ignore the
|
|
275
|
+
// regenerated map too; re-exported here for existing importers.
|
|
276
|
+
export { COMPASS_MAP_END, COMPASS_MAP_START, stripCompassMapBlock };
|
|
290
277
|
/**
|
|
291
278
|
* Path-specific bytes that feed {@link digestText}: speclaw-owned coderabbit
|
|
292
279
|
* region, regenerable Compass map body stripped, otherwise the file as-is.
|
|
@@ -4,6 +4,7 @@ import { spawn, spawnSync } from "node:child_process";
|
|
|
4
4
|
import { fileURLToPath } from "node:url";
|
|
5
5
|
import { createHash } from "node:crypto";
|
|
6
6
|
import { changedFiles, isGitRepo, mergeBase, worktreeChangedFiles } from "../../shared/git.js";
|
|
7
|
+
import { COMPASS_DOC, stripCompassMapBlock } from "../../shared/compass-map.js";
|
|
7
8
|
import { handleHarness, readHarness } from "../cortex/harness.js";
|
|
8
9
|
import { deltaSpecFiles, specArchive, specArchivePreconditions } from "./engine.js";
|
|
9
10
|
import { artifactNeeds, confirmedLevel, gatherSignals, loadCeremonyConfig, promoteCeremonyLevel, proposeLevel, readCeremonyRecord, readChangeType, } from "./levels.js";
|
|
@@ -89,8 +90,28 @@ function branchFiles(projectPath) {
|
|
|
89
90
|
]),
|
|
90
91
|
]
|
|
91
92
|
.filter((f) => !f.startsWith("lawbook/changes/"))
|
|
93
|
+
.filter((f) => f !== COMPASS_DOC || !onlyMapChanged(projectPath, base))
|
|
92
94
|
.sort();
|
|
93
95
|
}
|
|
96
|
+
/**
|
|
97
|
+
* Whether `docs/compass.md` differs from `base` only inside its generated map
|
|
98
|
+
* block: every full index rewrites that block, so on a fresh branch it would
|
|
99
|
+
* otherwise read as the agent's work and ship an empty change.
|
|
100
|
+
*/
|
|
101
|
+
function onlyMapChanged(projectPath, base) {
|
|
102
|
+
if (!base)
|
|
103
|
+
return false;
|
|
104
|
+
try {
|
|
105
|
+
const now = fs.readFileSync(path.join(projectPath, COMPASS_DOC), "utf8");
|
|
106
|
+
const then = git(projectPath, ["show", `${base}:${COMPASS_DOC}`]);
|
|
107
|
+
// Line endings are the platform's (core.autocrlf), not the agent's work.
|
|
108
|
+
const lf = (t) => stripCompassMapBlock(t.replace(/\r\n/g, "\n"));
|
|
109
|
+
return then !== "" && lf(now) === lf(then);
|
|
110
|
+
}
|
|
111
|
+
catch {
|
|
112
|
+
return false;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
94
115
|
/** New files not yet added, outside ship's own output (`lawbook/changes/`, `.speclaw/`). */
|
|
95
116
|
function untrackedFiles(projectPath) {
|
|
96
117
|
return git(projectPath, [
|
|
@@ -748,12 +769,18 @@ function workFingerprint(projectPath) {
|
|
|
748
769
|
":(exclude).speclaw",
|
|
749
770
|
]).split("\n");
|
|
750
771
|
const hash = createHash("sha256");
|
|
751
|
-
|
|
772
|
+
const files = [...new Set([...tracked, ...untrackedFiles(projectPath)])]
|
|
773
|
+
// A map-only docs/compass.md is no work: neither its bytes nor its name count.
|
|
774
|
+
.filter((f) => f !== COMPASS_DOC || !onlyMapChanged(projectPath, base))
|
|
775
|
+
.sort();
|
|
776
|
+
for (const f of files) {
|
|
752
777
|
if (!f)
|
|
753
778
|
continue;
|
|
754
779
|
hash.update(`${f}\n`);
|
|
755
780
|
try {
|
|
756
|
-
|
|
781
|
+
const bytes = fs.readFileSync(path.join(projectPath, f));
|
|
782
|
+
// A re-index rewrites the map block; only the rest of the file is work.
|
|
783
|
+
hash.update(f === COMPASS_DOC ? stripCompassMapBlock(bytes.toString("utf8")) : bytes);
|
|
757
784
|
}
|
|
758
785
|
catch {
|
|
759
786
|
// Deleted, vanished or unreadable: its name stands in.
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/** Markers for the regenerable map block inside `docs/compass.md` (see compass/map.ts). */
|
|
2
|
+
export const COMPASS_MAP_START = "<!-- speclaw:map:start -->";
|
|
3
|
+
export const COMPASS_MAP_END = "<!-- speclaw:map:end -->";
|
|
4
|
+
/** The project-relative path of the Compass guide that carries the generated map. */
|
|
5
|
+
export const COMPASS_DOC = "docs/compass.md";
|
|
6
|
+
/**
|
|
7
|
+
* Strip the regenerable map body between markers. `speclaw index` rewrites it on
|
|
8
|
+
* every full run, so integrity digests and "did the agent change this file?"
|
|
9
|
+
* checks must both ignore it.
|
|
10
|
+
*
|
|
11
|
+
* @param text - Full docs/compass.md contents.
|
|
12
|
+
* @returns The same text with an empty map body, or `text` if markers are missing.
|
|
13
|
+
*/
|
|
14
|
+
export function stripCompassMapBlock(text) {
|
|
15
|
+
const start = text.indexOf(COMPASS_MAP_START);
|
|
16
|
+
const end = text.indexOf(COMPASS_MAP_END);
|
|
17
|
+
if (start < 0 || end < 0 || end < start)
|
|
18
|
+
return text;
|
|
19
|
+
return text.slice(0, start + COMPASS_MAP_START.length) + "\n" + text.slice(end);
|
|
20
|
+
}
|