lockstep-cli 0.0.5 → 0.1.1
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/README.md +29 -0
- package/dist/adapters/claude.js +26 -7
- package/dist/adapters/claude.js.map +1 -1
- package/dist/adapters/install.test.js +53 -2
- package/dist/adapters/install.test.js.map +1 -1
- package/dist/adapters/merge.d.ts +11 -0
- package/dist/adapters/merge.js +41 -0
- package/dist/adapters/merge.js.map +1 -1
- package/dist/adapters/templates.d.ts +3 -2
- package/dist/adapters/templates.js +92 -3
- package/dist/adapters/templates.js.map +1 -1
- package/dist/capture/classify-edge.test.js +9 -7
- package/dist/capture/classify-edge.test.js.map +1 -1
- package/dist/capture/classify.d.ts +6 -4
- package/dist/capture/classify.js +9 -8
- package/dist/capture/classify.js.map +1 -1
- package/dist/capture/classify.test.js +7 -2
- package/dist/capture/classify.test.js.map +1 -1
- package/dist/capture/diff.d.ts +2 -0
- package/dist/capture/diff.js +15 -0
- package/dist/capture/diff.js.map +1 -1
- package/dist/capture/extract.d.ts +5 -0
- package/dist/capture/extract.js +17 -0
- package/dist/capture/extract.js.map +1 -0
- package/dist/capture/feature-context.d.ts +3 -0
- package/dist/capture/feature-context.js +43 -0
- package/dist/capture/feature-context.js.map +1 -0
- package/dist/capture/feature-context.test.d.ts +1 -0
- package/dist/capture/feature-context.test.js +45 -0
- package/dist/capture/feature-context.test.js.map +1 -0
- package/dist/capture/index.d.ts +59 -0
- package/dist/capture/index.js +101 -21
- package/dist/capture/index.js.map +1 -1
- package/dist/capture/manifest.d.ts +26 -0
- package/dist/capture/manifest.js +98 -0
- package/dist/capture/manifest.js.map +1 -0
- package/dist/capture/manifest.test.d.ts +1 -0
- package/dist/capture/manifest.test.js +40 -0
- package/dist/capture/manifest.test.js.map +1 -0
- package/dist/capture/outbound.d.ts +10 -0
- package/dist/capture/outbound.js +139 -0
- package/dist/capture/outbound.js.map +1 -0
- package/dist/capture/outbound.test.d.ts +1 -0
- package/dist/capture/outbound.test.js +89 -0
- package/dist/capture/outbound.test.js.map +1 -0
- package/dist/capture/python.d.ts +5 -0
- package/dist/capture/python.js +127 -0
- package/dist/capture/python.js.map +1 -0
- package/dist/capture/python.test.d.ts +1 -0
- package/dist/capture/python.test.js +99 -0
- package/dist/capture/python.test.js.map +1 -0
- package/dist/capture/replay.test.d.ts +1 -0
- package/dist/capture/replay.test.js +101 -0
- package/dist/capture/replay.test.js.map +1 -0
- package/dist/capture/surface.d.ts +23 -0
- package/dist/capture/surface.js +83 -0
- package/dist/capture/surface.js.map +1 -0
- package/dist/capture/surface.test.d.ts +1 -0
- package/dist/capture/surface.test.js +37 -0
- package/dist/capture/surface.test.js.map +1 -0
- package/dist/connect.d.ts +1 -1
- package/dist/connect.js +23 -12
- package/dist/connect.js.map +1 -1
- package/dist/index.js +25 -0
- package/dist/index.js.map +1 -1
- package/dist/init.js +3 -1
- package/dist/init.js.map +1 -1
- package/dist/mcp/server.d.ts +1 -1
- package/dist/mcp/server.js +26 -3
- package/dist/mcp/server.js.map +1 -1
- package/dist/scan.d.ts +64 -0
- package/dist/scan.js +176 -0
- package/dist/scan.js.map +1 -0
- package/dist/scan.test.d.ts +1 -0
- package/dist/scan.test.js +258 -0
- package/dist/scan.test.js.map +1 -0
- package/dist/statusline.d.ts +6 -0
- package/dist/statusline.js +108 -0
- package/dist/statusline.js.map +1 -0
- package/package.json +8 -4
package/dist/capture/diff.js
CHANGED
|
@@ -16,4 +16,19 @@ export function changedFiles(cwd) {
|
|
|
16
16
|
return [];
|
|
17
17
|
}
|
|
18
18
|
}
|
|
19
|
+
/** Every tracked file in the repo (+ untracked, minus gitignored), read-only. The full-repo walk `lockstep scan` runs over. */
|
|
20
|
+
export function trackedFiles(cwd) {
|
|
21
|
+
try {
|
|
22
|
+
const tracked = execFileSync("git", ["-C", cwd, "ls-files"], { encoding: "utf8" }).split("\n").filter(Boolean);
|
|
23
|
+
const untracked = execFileSync("git", ["-C", cwd, "ls-files", "--others", "--exclude-standard"], {
|
|
24
|
+
encoding: "utf8",
|
|
25
|
+
})
|
|
26
|
+
.split("\n")
|
|
27
|
+
.filter(Boolean);
|
|
28
|
+
return [...new Set([...tracked, ...untracked])];
|
|
29
|
+
}
|
|
30
|
+
catch {
|
|
31
|
+
return [];
|
|
32
|
+
}
|
|
33
|
+
}
|
|
19
34
|
//# sourceMappingURL=diff.js.map
|
package/dist/capture/diff.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"diff.js","sourceRoot":"","sources":["../../src/capture/diff.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAElD,+FAA+F;AAC/F,MAAM,UAAU,YAAY,CAAC,GAAW;IACtC,IAAI,CAAC;QACH,MAAM,OAAO,GAAG,YAAY,CAAC,KAAK,EAAE,CAAC,IAAI,EAAE,GAAG,EAAE,MAAM,EAAE,aAAa,EAAE,MAAM,CAAC,EAAE,EAAE,QAAQ,EAAE,MAAM,EAAE,CAAC;aAClG,KAAK,CAAC,IAAI,CAAC;aACX,MAAM,CAAC,OAAO,CAAC,CAAC;QACnB,MAAM,SAAS,GAAG,YAAY,CAAC,KAAK,EAAE,CAAC,IAAI,EAAE,GAAG,EAAE,UAAU,EAAE,UAAU,EAAE,oBAAoB,CAAC,EAAE;YAC/F,QAAQ,EAAE,MAAM;SACjB,CAAC;aACC,KAAK,CAAC,IAAI,CAAC;aACX,MAAM,CAAC,OAAO,CAAC,CAAC;QACnB,OAAO,CAAC,GAAG,IAAI,GAAG,CAAC,CAAC,GAAG,OAAO,EAAE,GAAG,SAAS,CAAC,CAAC,CAAC,CAAC;IAClD,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,EAAE,CAAC;IACZ,CAAC;AACH,CAAC"}
|
|
1
|
+
{"version":3,"file":"diff.js","sourceRoot":"","sources":["../../src/capture/diff.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAElD,+FAA+F;AAC/F,MAAM,UAAU,YAAY,CAAC,GAAW;IACtC,IAAI,CAAC;QACH,MAAM,OAAO,GAAG,YAAY,CAAC,KAAK,EAAE,CAAC,IAAI,EAAE,GAAG,EAAE,MAAM,EAAE,aAAa,EAAE,MAAM,CAAC,EAAE,EAAE,QAAQ,EAAE,MAAM,EAAE,CAAC;aAClG,KAAK,CAAC,IAAI,CAAC;aACX,MAAM,CAAC,OAAO,CAAC,CAAC;QACnB,MAAM,SAAS,GAAG,YAAY,CAAC,KAAK,EAAE,CAAC,IAAI,EAAE,GAAG,EAAE,UAAU,EAAE,UAAU,EAAE,oBAAoB,CAAC,EAAE;YAC/F,QAAQ,EAAE,MAAM;SACjB,CAAC;aACC,KAAK,CAAC,IAAI,CAAC;aACX,MAAM,CAAC,OAAO,CAAC,CAAC;QACnB,OAAO,CAAC,GAAG,IAAI,GAAG,CAAC,CAAC,GAAG,OAAO,EAAE,GAAG,SAAS,CAAC,CAAC,CAAC,CAAC;IAClD,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,EAAE,CAAC;IACZ,CAAC;AACH,CAAC;AAED,+HAA+H;AAC/H,MAAM,UAAU,YAAY,CAAC,GAAW;IACtC,IAAI,CAAC;QACH,MAAM,OAAO,GAAG,YAAY,CAAC,KAAK,EAAE,CAAC,IAAI,EAAE,GAAG,EAAE,UAAU,CAAC,EAAE,EAAE,QAAQ,EAAE,MAAM,EAAE,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;QAC/G,MAAM,SAAS,GAAG,YAAY,CAAC,KAAK,EAAE,CAAC,IAAI,EAAE,GAAG,EAAE,UAAU,EAAE,UAAU,EAAE,oBAAoB,CAAC,EAAE;YAC/F,QAAQ,EAAE,MAAM;SACjB,CAAC;aACC,KAAK,CAAC,IAAI,CAAC;aACX,MAAM,CAAC,OAAO,CAAC,CAAC;QACnB,OAAO,CAAC,GAAG,IAAI,GAAG,CAAC,CAAC,GAAG,OAAO,EAAE,GAAG,SAAS,CAAC,CAAC,CAAC,CAAC;IAClD,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,EAAE,CAAC;IACZ,CAAC;AACH,CAAC"}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import { type OutboundRef } from "./outbound.js";
|
|
2
|
+
/** Every canonical surface ID a file defines (produces), across all supported languages. */
|
|
3
|
+
export declare function extractAllSurfaces(path: string, content?: string): string[];
|
|
4
|
+
/** Every outbound-call candidate a file makes (consumes), across all supported languages. */
|
|
5
|
+
export declare function extractAllOutbound(path: string, content?: string): OutboundRef[];
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Language-composed extraction — the single entry point scan/capture use. Each language module
|
|
3
|
+
* stays pure and self-gated by extension; this file just unions their results. `surface.ts` is
|
|
4
|
+
* deliberately untouched (it is vendored to the GitHub Action's `surface.mjs`).
|
|
5
|
+
*/
|
|
6
|
+
import { extractSurfaces } from "./surface.js";
|
|
7
|
+
import { extractOutbound } from "./outbound.js";
|
|
8
|
+
import { extractPythonOutbound, extractPythonSurfaces } from "./python.js";
|
|
9
|
+
/** Every canonical surface ID a file defines (produces), across all supported languages. */
|
|
10
|
+
export function extractAllSurfaces(path, content) {
|
|
11
|
+
return [...new Set([...extractSurfaces(path, content), ...extractPythonSurfaces(path, content)])];
|
|
12
|
+
}
|
|
13
|
+
/** Every outbound-call candidate a file makes (consumes), across all supported languages. */
|
|
14
|
+
export function extractAllOutbound(path, content) {
|
|
15
|
+
return [...extractOutbound(path, content), ...extractPythonOutbound(path, content)];
|
|
16
|
+
}
|
|
17
|
+
//# sourceMappingURL=extract.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"extract.js","sourceRoot":"","sources":["../../src/capture/extract.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,OAAO,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC;AAC/C,OAAO,EAAE,eAAe,EAAoB,MAAM,eAAe,CAAC;AAClE,OAAO,EAAE,qBAAqB,EAAE,qBAAqB,EAAE,MAAM,aAAa,CAAC;AAE3E,4FAA4F;AAC5F,MAAM,UAAU,kBAAkB,CAAC,IAAY,EAAE,OAAgB;IAC/D,OAAO,CAAC,GAAG,IAAI,GAAG,CAAC,CAAC,GAAG,eAAe,CAAC,IAAI,EAAE,OAAO,CAAC,EAAE,GAAG,qBAAqB,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC;AACpG,CAAC;AAED,6FAA6F;AAC7F,MAAM,UAAU,kBAAkB,CAAC,IAAY,EAAE,OAAgB;IAC/D,OAAO,CAAC,GAAG,eAAe,CAAC,IAAI,EAAE,OAAO,CAAC,EAAE,GAAG,qBAAqB,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC,CAAC;AACtF,CAAC"}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { readFileSync, writeFileSync, unlinkSync } from "node:fs";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import { tmpdir } from "node:os";
|
|
4
|
+
import { createHash } from "node:crypto";
|
|
5
|
+
/**
|
|
6
|
+
* Session feature-context cache — mirrors statusline.ts's tmpdir cache, but keyed by git remote.
|
|
7
|
+
* `set_feature_context` writes here so the (separate-process) capture hook can attach the
|
|
8
|
+
* capabilityRef to change events. Accepted limitation (B-DB2): concurrent sessions on one
|
|
9
|
+
* checkout share one context, same as the statusline cache.
|
|
10
|
+
*/
|
|
11
|
+
const TTL_MS = 8 * 3600_000; // 8 hours
|
|
12
|
+
function pathFor(remote) {
|
|
13
|
+
const key = createHash("sha1").update(remote).digest("hex").slice(0, 12);
|
|
14
|
+
return join(tmpdir(), `lockstep-feature-${key}.json`);
|
|
15
|
+
}
|
|
16
|
+
export function writeFeatureContext(remote, ref) {
|
|
17
|
+
try {
|
|
18
|
+
writeFileSync(pathFor(remote), JSON.stringify({ ref, ts: Date.now() }));
|
|
19
|
+
}
|
|
20
|
+
catch {
|
|
21
|
+
/* ignore */
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
export function readFeatureContext(remote) {
|
|
25
|
+
try {
|
|
26
|
+
const c = JSON.parse(readFileSync(pathFor(remote), "utf8"));
|
|
27
|
+
if (Date.now() - c.ts < TTL_MS)
|
|
28
|
+
return c.ref;
|
|
29
|
+
}
|
|
30
|
+
catch {
|
|
31
|
+
/* no context */
|
|
32
|
+
}
|
|
33
|
+
return null;
|
|
34
|
+
}
|
|
35
|
+
export function clearFeatureContext(remote) {
|
|
36
|
+
try {
|
|
37
|
+
unlinkSync(pathFor(remote));
|
|
38
|
+
}
|
|
39
|
+
catch {
|
|
40
|
+
/* ignore */
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
//# sourceMappingURL=feature-context.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"feature-context.js","sourceRoot":"","sources":["../../src/capture/feature-context.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,aAAa,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AAClE,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,MAAM,EAAE,MAAM,SAAS,CAAC;AACjC,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAEzC;;;;;GAKG;AACH,MAAM,MAAM,GAAG,CAAC,GAAG,QAAQ,CAAC,CAAC,UAAU;AAOvC,SAAS,OAAO,CAAC,MAAc;IAC7B,MAAM,GAAG,GAAG,UAAU,CAAC,MAAM,CAAC,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;IACzE,OAAO,IAAI,CAAC,MAAM,EAAE,EAAE,oBAAoB,GAAG,OAAO,CAAC,CAAC;AACxD,CAAC;AAED,MAAM,UAAU,mBAAmB,CAAC,MAAc,EAAE,GAAW;IAC7D,IAAI,CAAC;QACH,aAAa,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,IAAI,CAAC,SAAS,CAAC,EAAE,GAAG,EAAE,EAAE,EAAE,IAAI,CAAC,GAAG,EAAE,EAAgB,CAAC,CAAC,CAAC;IACxF,CAAC;IAAC,MAAM,CAAC;QACP,YAAY;IACd,CAAC;AACH,CAAC;AAED,MAAM,UAAU,kBAAkB,CAAC,MAAc;IAC/C,IAAI,CAAC;QACH,MAAM,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC,CAAQ,CAAC;QACnE,IAAI,IAAI,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC,EAAE,GAAG,MAAM;YAAE,OAAO,CAAC,CAAC,GAAG,CAAC;IAC/C,CAAC;IAAC,MAAM,CAAC;QACP,gBAAgB;IAClB,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC;AAED,MAAM,UAAU,mBAAmB,CAAC,MAAc;IAChD,IAAI,CAAC;QACH,UAAU,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC;IAC9B,CAAC;IAAC,MAAM,CAAC;QACP,YAAY;IACd,CAAC;AACH,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { test } from "node:test";
|
|
2
|
+
import assert from "node:assert/strict";
|
|
3
|
+
import { readFileSync, writeFileSync } from "node:fs";
|
|
4
|
+
import { join } from "node:path";
|
|
5
|
+
import { tmpdir } from "node:os";
|
|
6
|
+
import { createHash } from "node:crypto";
|
|
7
|
+
import { writeFeatureContext, readFeatureContext, clearFeatureContext } from "./feature-context.js";
|
|
8
|
+
// Unique remote per run so parallel/repeat runs don't collide on the tmpdir file.
|
|
9
|
+
const remote = `github.com/lockstep-test/feature-ctx-${process.pid}-${Date.now()}`;
|
|
10
|
+
const pathFor = (r) => join(tmpdir(), `lockstep-feature-${createHash("sha1").update(r).digest("hex").slice(0, 12)}.json`);
|
|
11
|
+
test("write → read round-trips the capabilityRef", () => {
|
|
12
|
+
clearFeatureContext(remote);
|
|
13
|
+
writeFeatureContext(remote, "feature:guest-checkout");
|
|
14
|
+
assert.equal(readFeatureContext(remote), "feature:guest-checkout");
|
|
15
|
+
clearFeatureContext(remote);
|
|
16
|
+
});
|
|
17
|
+
test("missing context → null", () => {
|
|
18
|
+
clearFeatureContext(remote);
|
|
19
|
+
assert.equal(readFeatureContext(remote), null);
|
|
20
|
+
});
|
|
21
|
+
test("stale ts (older than TTL) → null", () => {
|
|
22
|
+
// Hand-write a context with a timestamp well beyond the 8h TTL.
|
|
23
|
+
const stale = { ref: "feature:old", ts: Date.now() - 9 * 3600_000 };
|
|
24
|
+
writeFileSync(pathFor(remote), JSON.stringify(stale));
|
|
25
|
+
assert.equal(readFeatureContext(remote), null);
|
|
26
|
+
clearFeatureContext(remote);
|
|
27
|
+
});
|
|
28
|
+
test("distinct remotes get distinct files", () => {
|
|
29
|
+
const a = `${remote}-a`;
|
|
30
|
+
const b = `${remote}-b`;
|
|
31
|
+
writeFeatureContext(a, "feature:a");
|
|
32
|
+
writeFeatureContext(b, "feature:b");
|
|
33
|
+
assert.equal(readFeatureContext(a), "feature:a");
|
|
34
|
+
assert.equal(readFeatureContext(b), "feature:b");
|
|
35
|
+
clearFeatureContext(a);
|
|
36
|
+
clearFeatureContext(b);
|
|
37
|
+
});
|
|
38
|
+
test("clear removes the file (read → null after)", () => {
|
|
39
|
+
writeFeatureContext(remote, "feature:x");
|
|
40
|
+
const p = pathFor(remote);
|
|
41
|
+
assert.equal(JSON.parse(readFileSync(p, "utf8")).ref, "feature:x");
|
|
42
|
+
clearFeatureContext(remote);
|
|
43
|
+
assert.equal(readFeatureContext(remote), null);
|
|
44
|
+
});
|
|
45
|
+
//# sourceMappingURL=feature-context.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"feature-context.test.js","sourceRoot":"","sources":["../../src/capture/feature-context.test.ts"],"names":[],"mappings":"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"}
|
package/dist/capture/index.d.ts
CHANGED
|
@@ -1,6 +1,65 @@
|
|
|
1
|
+
interface InboxResp {
|
|
2
|
+
unread?: number;
|
|
3
|
+
changes?: Array<{
|
|
4
|
+
summary: string;
|
|
5
|
+
surface: string | null;
|
|
6
|
+
impact?: number;
|
|
7
|
+
}>;
|
|
8
|
+
questions?: Array<{
|
|
9
|
+
id: string;
|
|
10
|
+
body: string;
|
|
11
|
+
scopeRef: string | null;
|
|
12
|
+
urgent: boolean;
|
|
13
|
+
status: string;
|
|
14
|
+
}>;
|
|
15
|
+
tasks?: Array<{
|
|
16
|
+
id: string;
|
|
17
|
+
title: string;
|
|
18
|
+
runState: string;
|
|
19
|
+
status: string;
|
|
20
|
+
}>;
|
|
21
|
+
decisions?: Array<{
|
|
22
|
+
id: string;
|
|
23
|
+
scopeRef: string;
|
|
24
|
+
ruleText: string;
|
|
25
|
+
status: string;
|
|
26
|
+
impact?: number;
|
|
27
|
+
}>;
|
|
28
|
+
conflicts?: Array<{
|
|
29
|
+
id: string;
|
|
30
|
+
surface: string;
|
|
31
|
+
constraintRuleText: string;
|
|
32
|
+
engRuleText: string;
|
|
33
|
+
}>;
|
|
34
|
+
}
|
|
35
|
+
interface DecisionsResp {
|
|
36
|
+
decisions?: Array<{
|
|
37
|
+
scopeRef: string;
|
|
38
|
+
status: string;
|
|
39
|
+
ruleText: string;
|
|
40
|
+
impact?: number;
|
|
41
|
+
}>;
|
|
42
|
+
}
|
|
43
|
+
/** Product-layer briefing: ranked, budget-capped constraints (origin=document, binding) in scope. */
|
|
44
|
+
export interface BriefingResp {
|
|
45
|
+
constraints: Array<{
|
|
46
|
+
line: string;
|
|
47
|
+
impact: number;
|
|
48
|
+
conflictOpen: boolean;
|
|
49
|
+
}>;
|
|
50
|
+
overflow: number;
|
|
51
|
+
/** Phase P: binding decisionType=principle decisions — the team's standing decision criteria. */
|
|
52
|
+
principles?: Array<{
|
|
53
|
+
line: string;
|
|
54
|
+
impact: number;
|
|
55
|
+
}>;
|
|
56
|
+
principlesOverflow?: number;
|
|
57
|
+
}
|
|
58
|
+
export declare function formatReplay(inbox: InboxResp | null, decisions: DecisionsResp | null, briefing?: BriefingResp | null): string;
|
|
1
59
|
/**
|
|
2
60
|
* Hook entrypoint. Resilient by design — never break the agent: on any error it exits 0.
|
|
3
61
|
* SessionStart → replay inbox + binding decisions as additionalContext.
|
|
4
62
|
* PostToolUse/Stop → diff → classify surface → risk-tiered publish via notify.
|
|
5
63
|
*/
|
|
6
64
|
export declare function runCapture(event: string): Promise<void>;
|
|
65
|
+
export {};
|
package/dist/capture/index.js
CHANGED
|
@@ -3,15 +3,29 @@ import { join } from "node:path";
|
|
|
3
3
|
import { createHash } from "node:crypto";
|
|
4
4
|
import { registerSession } from "../mcp/session.js";
|
|
5
5
|
import { call } from "../mcp/api.js";
|
|
6
|
+
import { gitRemote } from "../mcp/git.js";
|
|
7
|
+
import { readFeatureContext } from "./feature-context.js";
|
|
6
8
|
import { changedFiles } from "./diff.js";
|
|
7
9
|
import { isContractSurface, riskTierFor } from "./classify.js";
|
|
8
|
-
|
|
10
|
+
import { extractAllSurfaces } from "./extract.js";
|
|
11
|
+
import { readManifest } from "./manifest.js";
|
|
12
|
+
/** Sync this repo's declared dependencies (lockstep.yaml `consumes:`) into the usage graph. Idempotent. */
|
|
13
|
+
async function syncManifestDeps(cwd, sessionId) {
|
|
14
|
+
const { consumes } = readManifest(cwd);
|
|
15
|
+
for (const producedSurface of consumes) {
|
|
16
|
+
await call("POST", "/dependencies", sessionId, { producedSurface, source: "manifest" }).catch(() => { });
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
/** Highest-blast-radius first, so the session-start briefing leads with what matters most. */
|
|
20
|
+
const byImpact = (a, b) => (b.impact ?? 0) - (a.impact ?? 0);
|
|
21
|
+
const tag = (impact) => ((impact ?? 0) > 0 ? `[impact ${impact}] ` : "");
|
|
22
|
+
export function formatReplay(inbox, decisions, briefing) {
|
|
9
23
|
const lines = [];
|
|
10
|
-
const changes = inbox?.changes ?? [];
|
|
24
|
+
const changes = [...(inbox?.changes ?? [])].sort(byImpact);
|
|
11
25
|
if (changes.length) {
|
|
12
26
|
lines.push(`📥 ${changes.length} change(s) since you were last here:`);
|
|
13
27
|
for (const c of changes.slice(0, 8))
|
|
14
|
-
lines.push(` • ${c.summary}${c.surface ? ` (${c.surface})` : ""}`);
|
|
28
|
+
lines.push(` • ${tag(c.impact)}${c.summary}${c.surface ? ` (${c.surface})` : ""}`);
|
|
15
29
|
}
|
|
16
30
|
const qs = (inbox?.questions ?? []).filter((q) => q.status === "open");
|
|
17
31
|
if (qs.length) {
|
|
@@ -25,12 +39,44 @@ function formatReplay(inbox, decisions) {
|
|
|
25
39
|
for (const t of ts.slice(0, 8))
|
|
26
40
|
lines.push(` • ${t.title}`);
|
|
27
41
|
}
|
|
28
|
-
const
|
|
29
|
-
if (
|
|
30
|
-
lines.push(
|
|
31
|
-
for (const d of
|
|
42
|
+
const pendingDecisions = (inbox?.decisions ?? []).filter((d) => d.status === "open");
|
|
43
|
+
if (pendingDecisions.length) {
|
|
44
|
+
lines.push(`⚖️ ${pendingDecisions.length} decision(s) pending your acknowledgment:`);
|
|
45
|
+
for (const d of pendingDecisions.slice(0, 8))
|
|
32
46
|
lines.push(` • [${d.scopeRef}] ${d.ruleText}`);
|
|
33
47
|
}
|
|
48
|
+
const conflicts = inbox?.conflicts ?? [];
|
|
49
|
+
if (conflicts.length) {
|
|
50
|
+
lines.push(`⚔️ ${conflicts.length} drift conflict(s) — your work may conflict with a ratified product constraint:`);
|
|
51
|
+
for (const c of conflicts.slice(0, 8))
|
|
52
|
+
lines.push(` • [${c.surface}] your "${c.engRuleText}" vs constraint "${c.constraintRuleText}" — review both`);
|
|
53
|
+
}
|
|
54
|
+
// Principles lead — they're the criteria the agent should judge everything else by (TOMASP "M").
|
|
55
|
+
const principles = briefing?.principles ?? [];
|
|
56
|
+
if (principles.length) {
|
|
57
|
+
lines.push(`◆ ${principles.length} project principle(s) — the team's standing decision criteria:`);
|
|
58
|
+
for (const p of principles)
|
|
59
|
+
lines.push(` • ${p.line}`);
|
|
60
|
+
if ((briefing?.principlesOverflow ?? 0) > 0)
|
|
61
|
+
lines.push(` • (+${briefing?.principlesOverflow} more — query the ledger)`);
|
|
62
|
+
}
|
|
63
|
+
const binding = (decisions?.decisions ?? []).filter((d) => d.status === "binding").sort(byImpact);
|
|
64
|
+
const constraints = briefing?.constraints ?? [];
|
|
65
|
+
if (binding.length || constraints.length) {
|
|
66
|
+
const rows = [
|
|
67
|
+
...binding.map((d) => ({
|
|
68
|
+
impact: d.impact ?? 0,
|
|
69
|
+
isConstraint: false,
|
|
70
|
+
line: ` • ${tag(d.impact)}[${d.scopeRef}] ${d.ruleText}`,
|
|
71
|
+
})),
|
|
72
|
+
...constraints.map((c) => ({ impact: c.impact, isConstraint: true, line: ` • ${c.line}` })),
|
|
73
|
+
].sort((a, b) => b.impact - a.impact || Number(b.isConstraint) - Number(a.isConstraint));
|
|
74
|
+
lines.push(`📌 ${rows.length} binding decision(s) in effect:`);
|
|
75
|
+
for (const r of rows.slice(0, 8))
|
|
76
|
+
lines.push(r.line);
|
|
77
|
+
if ((briefing?.overflow ?? 0) > 0)
|
|
78
|
+
lines.push(` • (+${briefing?.overflow} product constraints in scope — get_product_context)`);
|
|
79
|
+
}
|
|
34
80
|
return lines.length ? `Lockstep:\n${lines.join("\n")}` : "Lockstep: nothing new.";
|
|
35
81
|
}
|
|
36
82
|
/** Format a short badge from inbox peek counts. Returns null if nothing new. */
|
|
@@ -42,6 +88,8 @@ function formatPeek(peek) {
|
|
|
42
88
|
parts.push(`${peek.questions} question${peek.questions > 1 ? "s" : ""}`);
|
|
43
89
|
if (peek.tasks)
|
|
44
90
|
parts.push(`${peek.tasks} task${peek.tasks > 1 ? "s" : ""}`);
|
|
91
|
+
if (peek.decisions)
|
|
92
|
+
parts.push(`${peek.decisions} decision${peek.decisions > 1 ? "s" : ""} to review`);
|
|
45
93
|
if (peek.changes)
|
|
46
94
|
parts.push(`${peek.changes} change${peek.changes > 1 ? "s" : ""}`);
|
|
47
95
|
if (parts.length === 0)
|
|
@@ -63,11 +111,15 @@ export async function runCapture(event) {
|
|
|
63
111
|
catch {
|
|
64
112
|
process.exit(0); // not a connected repo / not logged in → silent no-op
|
|
65
113
|
}
|
|
114
|
+
const remote = gitRemote(cwd);
|
|
115
|
+
const capabilityRef = remote ? readFeatureContext(remote) : null;
|
|
66
116
|
try {
|
|
67
117
|
if (event === "SessionStart") {
|
|
118
|
+
await syncManifestDeps(cwd, session.sessionId); // keep the usage graph current from lockstep.yaml
|
|
68
119
|
const inbox = await call("GET", "/inbox", session.sessionId).catch(() => null);
|
|
69
120
|
const decisions = await call("GET", "/decisions", session.sessionId).catch(() => null);
|
|
70
|
-
const
|
|
121
|
+
const briefing = await call("GET", "/briefing", session.sessionId).catch(() => null);
|
|
122
|
+
const replay = formatReplay(inbox, decisions, briefing);
|
|
71
123
|
process.stdout.write(JSON.stringify({
|
|
72
124
|
hookSpecificOutput: { hookEventName: "SessionStart", additionalContext: replay },
|
|
73
125
|
}));
|
|
@@ -81,7 +133,9 @@ export async function runCapture(event) {
|
|
|
81
133
|
const files = changedFiles(cwd);
|
|
82
134
|
if (files.length === 0)
|
|
83
135
|
return;
|
|
84
|
-
|
|
136
|
+
// Extract CANONICAL surface IDs (e.g. "http:POST /auth/session") — the shared vocabulary that
|
|
137
|
+
// lets a consumer's declared dependency match a producer's change. File paths never matched.
|
|
138
|
+
const surfaceIds = new Set();
|
|
85
139
|
let anyContractSurface = false;
|
|
86
140
|
for (const f of files) {
|
|
87
141
|
let content = "";
|
|
@@ -91,24 +145,50 @@ export async function runCapture(event) {
|
|
|
91
145
|
catch {
|
|
92
146
|
/* deleted/binary — skip content */
|
|
93
147
|
}
|
|
94
|
-
|
|
148
|
+
const ids = extractAllSurfaces(f, content);
|
|
149
|
+
if (ids.length > 0) {
|
|
95
150
|
anyContractSurface = true;
|
|
96
|
-
|
|
151
|
+
ids.forEach((s) => surfaceIds.add(s));
|
|
152
|
+
}
|
|
153
|
+
else if (isContractSurface(f, content)) {
|
|
154
|
+
anyContractSurface = true; // contract-ish file we couldn't parse into an exact surface
|
|
97
155
|
}
|
|
98
156
|
}
|
|
99
157
|
// v1: contract surface ⇒ shared (the safety-critical bias). Ownership-based refinement later.
|
|
100
158
|
const riskTier = riskTierFor({ anyContractSurface, allOwnedByMe: true });
|
|
101
159
|
const summary = `${event}: changed ${files.length} file(s): ${files.slice(0, 5).join(", ")}${files.length > 5 ? "…" : ""}`;
|
|
102
|
-
const
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
surface
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
160
|
+
const baseHash = createHash("sha256").update(files.sort().join("|")).digest("hex").slice(0, 16);
|
|
161
|
+
const ids = [...surfaceIds].slice(0, 10);
|
|
162
|
+
if (ids.length === 0) {
|
|
163
|
+
// No contract surface touched — record a single owned activity entry (routes to no one).
|
|
164
|
+
await call("POST", "/changes", session.sessionId, {
|
|
165
|
+
summary,
|
|
166
|
+
riskTier,
|
|
167
|
+
verified: true,
|
|
168
|
+
verifiedAgainst: "git-diff",
|
|
169
|
+
diffHash: baseHash,
|
|
170
|
+
capabilityRef: capabilityRef ?? undefined,
|
|
171
|
+
}).catch(() => { });
|
|
172
|
+
}
|
|
173
|
+
else {
|
|
174
|
+
// One change per changed surface, so each routes to that surface's consumers.
|
|
175
|
+
for (const surface of ids) {
|
|
176
|
+
await call("POST", "/changes", session.sessionId, {
|
|
177
|
+
summary,
|
|
178
|
+
surface,
|
|
179
|
+
riskTier,
|
|
180
|
+
verified: true, // mechanical delta is derived from real local code (displayed as "extracted", not "verified")
|
|
181
|
+
verifiedAgainst: "git-diff",
|
|
182
|
+
diffHash: `${baseHash}:${surface}`,
|
|
183
|
+
capabilityRef: capabilityRef ?? undefined,
|
|
184
|
+
}).catch(() => { });
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
process.stderr.write(`[lockstep] published change (${riskTier}, ${ids.length} surface(s))\n`);
|
|
188
|
+
// NOTE: capture publishes a CHANGE event only. A change is NOT a decision — decisions are
|
|
189
|
+
// durable rules/architectural choices, logged deliberately by the agent via propose_decision
|
|
190
|
+
// (see the lockstep skill). Auto-minting a decision per save was a category error that flooded
|
|
191
|
+
// the ledger; routing/impact for changes is handled by recordChange on the server.
|
|
112
192
|
// Peek at inbox (without marking as read) — notify the agent if there are unread items
|
|
113
193
|
const peek = await call("GET", "/inbox/peek", session.sessionId).catch(() => null);
|
|
114
194
|
const badge = formatPeek(peek);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/capture/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AACvC,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACzC,OAAO,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AACpD,OAAO,EAAE,IAAI,EAAE,MAAM,eAAe,CAAC;AACrC,OAAO,EAAE,YAAY,EAAE,MAAM,WAAW,CAAC;AACzC,OAAO,EAAE,iBAAiB,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/capture/index.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
export interface Manifest {
|
|
2
|
+
produces: string[];
|
|
3
|
+
consumes: string[];
|
|
4
|
+
}
|
|
5
|
+
/**
|
|
6
|
+
* Read `lockstep.yaml` from the repo root. This is the deterministic source of truth for the usage
|
|
7
|
+
* graph: `consumes:` declares the canonical surface IDs this repo depends on (so it gets warned when
|
|
8
|
+
* they change), `produces:` optionally declares/overrides what it exposes. We parse a minimal YAML
|
|
9
|
+
* subset (two top-level keys, each a `- item` list) to stay dependency-free and predictable.
|
|
10
|
+
*
|
|
11
|
+
* Example:
|
|
12
|
+
* produces:
|
|
13
|
+
* - http:POST /auth/session
|
|
14
|
+
* consumes:
|
|
15
|
+
* - http:GET /orders/:id
|
|
16
|
+
*/
|
|
17
|
+
export declare function readManifest(cwd: string): Manifest;
|
|
18
|
+
/**
|
|
19
|
+
* Write `lockstep.yaml`, MERGE-preserving what's already there — existing human entries are unioned
|
|
20
|
+
* with the proposed ones (never dropped), and the file's leading comment block is kept. Atomic write
|
|
21
|
+
* with a one-time `.lockstep.bak` and idempotent no-op, via the shared `applyFile`. This is the single
|
|
22
|
+
* sanctioned writer of the manifest — the capture hook never touches it (see IMPROVEMENTS #11).
|
|
23
|
+
*/
|
|
24
|
+
export declare function writeManifest(cwd: string, manifest: Manifest, opts?: {
|
|
25
|
+
dryRun?: boolean;
|
|
26
|
+
}): Promise<string>;
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import { readFileSync } from "node:fs";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import { applyFile } from "../adapters/fsutil.js";
|
|
4
|
+
/**
|
|
5
|
+
* Read `lockstep.yaml` from the repo root. This is the deterministic source of truth for the usage
|
|
6
|
+
* graph: `consumes:` declares the canonical surface IDs this repo depends on (so it gets warned when
|
|
7
|
+
* they change), `produces:` optionally declares/overrides what it exposes. We parse a minimal YAML
|
|
8
|
+
* subset (two top-level keys, each a `- item` list) to stay dependency-free and predictable.
|
|
9
|
+
*
|
|
10
|
+
* Example:
|
|
11
|
+
* produces:
|
|
12
|
+
* - http:POST /auth/session
|
|
13
|
+
* consumes:
|
|
14
|
+
* - http:GET /orders/:id
|
|
15
|
+
*/
|
|
16
|
+
export function readManifest(cwd) {
|
|
17
|
+
let raw;
|
|
18
|
+
try {
|
|
19
|
+
raw = readFileSync(join(cwd, "lockstep.yaml"), "utf8");
|
|
20
|
+
}
|
|
21
|
+
catch {
|
|
22
|
+
return { produces: [], consumes: [] };
|
|
23
|
+
}
|
|
24
|
+
const out = { produces: [], consumes: [] };
|
|
25
|
+
let key = null;
|
|
26
|
+
for (const line of raw.split(/\r?\n/)) {
|
|
27
|
+
if (/^\s*#/.test(line) || line.trim() === "")
|
|
28
|
+
continue;
|
|
29
|
+
const top = line.match(/^(produces|consumes)\s*:\s*$/);
|
|
30
|
+
if (top) {
|
|
31
|
+
key = top[1];
|
|
32
|
+
continue;
|
|
33
|
+
}
|
|
34
|
+
const item = line.match(/^\s*-\s*(.+?)\s*$/);
|
|
35
|
+
if (item && key) {
|
|
36
|
+
out[key].push(item[1].replace(/^["']|["']$/g, ""));
|
|
37
|
+
}
|
|
38
|
+
else if (/^\S/.test(line)) {
|
|
39
|
+
key = null; // a new unindented non-list key ends the current list
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
return out;
|
|
43
|
+
}
|
|
44
|
+
const DEFAULT_HEADER = `# Lockstep manifest — the deterministic source of truth for the usage graph.
|
|
45
|
+
# Managed by the \`/lockstep-setup\` skill (the only sanctioned writer; the capture hook is read-only).
|
|
46
|
+
# Surface IDs are canonical: http:METHOD /path · proto:pkg.Service/Rpc · gql:Root.field
|
|
47
|
+
`;
|
|
48
|
+
/** Preserve the file's existing top-of-file comment block (so human notes survive a rewrite). */
|
|
49
|
+
function leadingComment(raw) {
|
|
50
|
+
if (!raw)
|
|
51
|
+
return null;
|
|
52
|
+
const head = [];
|
|
53
|
+
for (const line of raw.split(/\r?\n/)) {
|
|
54
|
+
if (/^\s*#/.test(line) || line.trim() === "")
|
|
55
|
+
head.push(line);
|
|
56
|
+
else
|
|
57
|
+
break;
|
|
58
|
+
}
|
|
59
|
+
const block = head.join("\n").trimEnd();
|
|
60
|
+
return block ? block + "\n" : null;
|
|
61
|
+
}
|
|
62
|
+
const union = (a, b) => {
|
|
63
|
+
const out = [];
|
|
64
|
+
const seen = new Set();
|
|
65
|
+
for (const x of [...a, ...b])
|
|
66
|
+
if (x && !seen.has(x))
|
|
67
|
+
(seen.add(x), out.push(x));
|
|
68
|
+
return out;
|
|
69
|
+
};
|
|
70
|
+
function serialize(m, header) {
|
|
71
|
+
const list = (items) => items.map((i) => ` - ${i}`).join("\n");
|
|
72
|
+
const block = (key, items) => (items.length ? `${key}:\n${list(items)}\n` : `${key}:\n`);
|
|
73
|
+
return `${header}\n${block("produces", m.produces)}\n${block("consumes", m.consumes)}`;
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Write `lockstep.yaml`, MERGE-preserving what's already there — existing human entries are unioned
|
|
77
|
+
* with the proposed ones (never dropped), and the file's leading comment block is kept. Atomic write
|
|
78
|
+
* with a one-time `.lockstep.bak` and idempotent no-op, via the shared `applyFile`. This is the single
|
|
79
|
+
* sanctioned writer of the manifest — the capture hook never touches it (see IMPROVEMENTS #11).
|
|
80
|
+
*/
|
|
81
|
+
export async function writeManifest(cwd, manifest, opts = {}) {
|
|
82
|
+
const path = join(cwd, "lockstep.yaml");
|
|
83
|
+
let raw;
|
|
84
|
+
try {
|
|
85
|
+
raw = readFileSync(path, "utf8");
|
|
86
|
+
}
|
|
87
|
+
catch {
|
|
88
|
+
raw = null;
|
|
89
|
+
}
|
|
90
|
+
const existing = readManifest(cwd);
|
|
91
|
+
const merged = {
|
|
92
|
+
produces: union(existing.produces, manifest.produces),
|
|
93
|
+
consumes: union(existing.consumes, manifest.consumes),
|
|
94
|
+
};
|
|
95
|
+
const header = leadingComment(raw) ?? DEFAULT_HEADER;
|
|
96
|
+
return applyFile(path, () => serialize(merged, header), opts.dryRun ?? false);
|
|
97
|
+
}
|
|
98
|
+
//# sourceMappingURL=manifest.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"manifest.js","sourceRoot":"","sources":["../../src/capture/manifest.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { test } from "node:test";
|
|
2
|
+
import assert from "node:assert/strict";
|
|
3
|
+
import { mkdtemp, readFile, writeFile, access } from "node:fs/promises";
|
|
4
|
+
import { tmpdir } from "node:os";
|
|
5
|
+
import { join } from "node:path";
|
|
6
|
+
import { readManifest, writeManifest } from "./manifest.js";
|
|
7
|
+
const tmp = () => mkdtemp(join(tmpdir(), "lockstep-manifest-"));
|
|
8
|
+
test("writeManifest round-trips through readManifest", async () => {
|
|
9
|
+
const dir = await tmp();
|
|
10
|
+
await writeManifest(dir, { produces: ["http:POST /a"], consumes: ["http:GET /b"] });
|
|
11
|
+
assert.deepEqual(readManifest(dir), { produces: ["http:POST /a"], consumes: ["http:GET /b"] });
|
|
12
|
+
});
|
|
13
|
+
test("merges with (never drops) existing human entries", async () => {
|
|
14
|
+
const dir = await tmp();
|
|
15
|
+
await writeFile(join(dir, "lockstep.yaml"), "# my notes\nproduces:\n - http:POST /a\nconsumes:\n - http:GET /manual\n");
|
|
16
|
+
await writeManifest(dir, { produces: ["http:POST /a", "http:GET /new"], consumes: ["http:GET /b"] });
|
|
17
|
+
const m = readManifest(dir);
|
|
18
|
+
assert.deepEqual(m.produces, ["http:POST /a", "http:GET /new"]);
|
|
19
|
+
assert.deepEqual(m.consumes, ["http:GET /manual", "http:GET /b"], "human entry kept, new one appended");
|
|
20
|
+
const raw = await readFile(join(dir, "lockstep.yaml"), "utf8");
|
|
21
|
+
assert.ok(raw.startsWith("# my notes"), "existing comment header preserved");
|
|
22
|
+
});
|
|
23
|
+
test("writes a one-time .lockstep.bak and is idempotent on no-op re-write", async () => {
|
|
24
|
+
const dir = await tmp();
|
|
25
|
+
await writeFile(join(dir, "lockstep.yaml"), "produces:\nconsumes:\n");
|
|
26
|
+
await writeManifest(dir, { produces: ["http:POST /a"], consumes: [] });
|
|
27
|
+
await access(join(dir, "lockstep.yaml.lockstep.bak")); // throws if missing
|
|
28
|
+
const first = await readFile(join(dir, "lockstep.yaml"), "utf8");
|
|
29
|
+
const status = await writeManifest(dir, { produces: ["http:POST /a"], consumes: [] });
|
|
30
|
+
const second = await readFile(join(dir, "lockstep.yaml"), "utf8");
|
|
31
|
+
assert.equal(first, second, "re-write with same content is a no-op");
|
|
32
|
+
assert.match(status, /^unchanged/);
|
|
33
|
+
});
|
|
34
|
+
test("dry-run does not write the file", async () => {
|
|
35
|
+
const dir = await tmp();
|
|
36
|
+
const status = await writeManifest(dir, { produces: ["http:POST /a"], consumes: [] }, { dryRun: true });
|
|
37
|
+
assert.match(status, /^would write/);
|
|
38
|
+
await assert.rejects(access(join(dir, "lockstep.yaml")));
|
|
39
|
+
});
|
|
40
|
+
//# sourceMappingURL=manifest.test.js.map
|