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/README.md
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# lockstep-cli
|
|
2
|
+
|
|
3
|
+
The CLI for [Lockstep](https://www.getlockstep.dev) — one source of truth for every engineering decision, read by every human and every AI agent before work starts.
|
|
4
|
+
|
|
5
|
+
Lockstep captures decisions automatically from Slack, Notion, Jira, and pull requests, gets each one confirmed by its owner, briefs any MCP-compatible coding agent (Claude Code, Cursor, and others) at session start, and flags work that contradicts a Locked decision before it ships.
|
|
6
|
+
|
|
7
|
+
## Install
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npm install -g lockstep-cli
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Quick start
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
lockstep login # authenticate
|
|
17
|
+
lockstep onboard # one step: wire up hooks + MCP for your agent, link this repo
|
|
18
|
+
lockstep status # check auth + config health
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
Full setup, MCP configuration, and self-hosting (Apache-2.0, one `docker compose up`):
|
|
22
|
+
|
|
23
|
+
- **Website:** https://www.getlockstep.dev
|
|
24
|
+
- **How it works:** https://www.getlockstep.dev/how-it-works
|
|
25
|
+
- **Source:** https://github.com/lockstep-team-agent/lockstep
|
|
26
|
+
|
|
27
|
+
## License
|
|
28
|
+
|
|
29
|
+
Apache-2.0
|
package/dist/adapters/claude.js
CHANGED
|
@@ -2,22 +2,31 @@ import { homedir } from "node:os";
|
|
|
2
2
|
import { join } from "node:path";
|
|
3
3
|
import { access } from "node:fs/promises";
|
|
4
4
|
import { applyFile, readIfExists } from "./fsutil.js";
|
|
5
|
-
import { mergeHooks, mergeMcp, upsertManagedBlock } from "./merge.js";
|
|
6
|
-
import { captureHooks, mcpSpec, SKILL_MD, CLAUDE_BLOCK } from "./templates.js";
|
|
5
|
+
import { mergeHooks, mergeMcp, mergeStatusLine, removeManagedHooks, removeManagedStatusLine, upsertManagedBlock, } from "./merge.js";
|
|
6
|
+
import { captureHooks, mcpSpec, SKILL_MD, SETUP_SKILL_MD, CLAUDE_BLOCK } from "./templates.js";
|
|
7
7
|
function paths(cwd, scope) {
|
|
8
8
|
if (scope === "user") {
|
|
9
9
|
const h = homedir();
|
|
10
|
+
const hooks = join(h, ".claude", "settings.json");
|
|
10
11
|
return {
|
|
11
12
|
mcp: join(h, ".mcp.json"),
|
|
12
|
-
hooks
|
|
13
|
+
hooks,
|
|
14
|
+
// ~/.claude is already personal — hooks live in the same file (no shared/local split).
|
|
15
|
+
hooksLocal: hooks,
|
|
13
16
|
skill: join(h, ".claude", "skills", "lockstep", "SKILL.md"),
|
|
17
|
+
setupSkill: join(h, ".claude", "skills", "lockstep-setup", "SKILL.md"),
|
|
14
18
|
instructions: join(h, ".claude", "CLAUDE.md"),
|
|
15
19
|
};
|
|
16
20
|
}
|
|
17
21
|
return {
|
|
18
22
|
mcp: join(cwd, ".mcp.json"),
|
|
19
23
|
hooks: join(cwd, ".claude", "settings.json"),
|
|
24
|
+
// Hooks + statusline invoke the locally-installed `lockstep` bin — personal/machine state, so
|
|
25
|
+
// they go in settings.local.json (Claude Code's personal scope, auto-gitignored), never in the
|
|
26
|
+
// committed settings.json where they'd leak to teammates on git pull (IMPROVEMENTS #2).
|
|
27
|
+
hooksLocal: join(cwd, ".claude", "settings.local.json"),
|
|
20
28
|
skill: join(cwd, ".claude", "skills", "lockstep", "SKILL.md"),
|
|
29
|
+
setupSkill: join(cwd, ".claude", "skills", "lockstep-setup", "SKILL.md"),
|
|
21
30
|
instructions: join(cwd, "CLAUDE.md"),
|
|
22
31
|
};
|
|
23
32
|
}
|
|
@@ -34,22 +43,32 @@ export const claudeAdapter = {
|
|
|
34
43
|
},
|
|
35
44
|
async install(cwd, scope, dryRun) {
|
|
36
45
|
const p = paths(cwd, scope);
|
|
37
|
-
|
|
46
|
+
const results = [
|
|
38
47
|
await applyFile(p.mcp, (cur) => mergeMcp(cur, "lockstep", mcpSpec("claude")), dryRun),
|
|
39
|
-
await applyFile(p.
|
|
48
|
+
await applyFile(p.hooksLocal, (cur) => {
|
|
49
|
+
const withHooks = mergeHooks(cur, captureHooks, "lockstep");
|
|
50
|
+
return mergeStatusLine(withHooks, "lockstep", ["statusline"]);
|
|
51
|
+
}, dryRun),
|
|
40
52
|
await applyFile(p.skill, () => SKILL_MD, dryRun),
|
|
53
|
+
await applyFile(p.setupSkill, () => SETUP_SKILL_MD, dryRun),
|
|
41
54
|
await applyFile(p.instructions, (cur) => upsertManagedBlock(cur, CLAUDE_BLOCK), dryRun),
|
|
42
55
|
];
|
|
56
|
+
// Self-heal pre-existing installs: strip our hooks/statusline from the SHARED settings.json
|
|
57
|
+
// (foreign entries preserved; the file is never created if absent).
|
|
58
|
+
if (p.hooksLocal !== p.hooks && (await readIfExists(p.hooks)) !== null) {
|
|
59
|
+
results.push(await applyFile(p.hooks, (cur) => removeManagedStatusLine(removeManagedHooks(cur ?? "{}")), dryRun));
|
|
60
|
+
}
|
|
61
|
+
return results;
|
|
43
62
|
},
|
|
44
63
|
async verify(cwd, scope) {
|
|
45
64
|
const p = paths(cwd, scope);
|
|
46
65
|
const mcp = await readIfExists(p.mcp);
|
|
47
|
-
const hooks = await readIfExists(p.
|
|
66
|
+
const hooks = await readIfExists(p.hooksLocal);
|
|
48
67
|
const mcpOk = !!mcp && mcp.includes('"lockstep"');
|
|
49
68
|
const hooksOk = !!hooks && hooks.includes("lockstep");
|
|
50
69
|
return {
|
|
51
70
|
ok: mcpOk && hooksOk,
|
|
52
|
-
details: [`${mcpOk ? "✓" : "✗"} mcp server (${p.mcp})`, `${hooksOk ? "✓" : "✗"} hooks (${p.
|
|
71
|
+
details: [`${mcpOk ? "✓" : "✗"} mcp server (${p.mcp})`, `${hooksOk ? "✓" : "✗"} hooks (${p.hooksLocal})`],
|
|
53
72
|
};
|
|
54
73
|
},
|
|
55
74
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"claude.js","sourceRoot":"","sources":["../../src/adapters/claude.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AAClC,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAC;AAC1C,OAAO,EAAE,SAAS,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AACtD,OAAO,
|
|
1
|
+
{"version":3,"file":"claude.js","sourceRoot":"","sources":["../../src/adapters/claude.ts"],"names":[],"mappings":"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"}
|
|
@@ -12,6 +12,7 @@ test("init writes config, is idempotent on disk, and preserves foreign config",
|
|
|
12
12
|
await writeFile(join(dir, "CLAUDE.md"), "# Project\n\nhand-written notes\n");
|
|
13
13
|
const read = async () => ({
|
|
14
14
|
settings: await readFile(join(dir, ".claude", "settings.json"), "utf8"),
|
|
15
|
+
local: await readFile(join(dir, ".claude", "settings.local.json"), "utf8"),
|
|
15
16
|
mcp: await readFile(join(dir, ".mcp.json"), "utf8"),
|
|
16
17
|
claude: await readFile(join(dir, "CLAUDE.md"), "utf8"),
|
|
17
18
|
skill: await readFile(join(dir, ".claude", "skills", "lockstep", "SKILL.md"), "utf8"),
|
|
@@ -21,12 +22,62 @@ test("init writes config, is idempotent on disk, and preserves foreign config",
|
|
|
21
22
|
await claudeAdapter.install(dir, "project", false);
|
|
22
23
|
const after2 = await read();
|
|
23
24
|
assert.deepEqual(after1, after2, "second init is byte-identical (idempotent)");
|
|
24
|
-
assert.ok(after1.settings.includes("my-linter"), "foreign hook preserved");
|
|
25
|
+
assert.ok(after1.settings.includes("my-linter"), "foreign hook preserved in the shared file");
|
|
26
|
+
assert.ok(!after1.settings.includes("capture"), "our hooks stay OUT of the committed settings.json");
|
|
27
|
+
assert.ok(after1.local.includes("capture"), "our hooks land in personal settings.local.json");
|
|
28
|
+
assert.ok(after1.local.includes("statusLine"), "statusline lands in personal settings.local.json");
|
|
25
29
|
assert.ok(after1.mcp.includes('"other"'), "foreign mcp server preserved");
|
|
26
30
|
assert.ok(after1.claude.includes("hand-written notes"), "user CLAUDE.md content preserved");
|
|
27
|
-
assert.ok(after1.settings.includes("capture"), "our hooks installed");
|
|
28
31
|
assert.ok(after1.mcp.includes('"lockstep"'), "our mcp server installed");
|
|
29
32
|
const v = await claudeAdapter.verify(dir, "project");
|
|
30
33
|
assert.ok(v.ok, "verify passes after install");
|
|
31
34
|
});
|
|
35
|
+
test("init heals a pre-existing shared settings.json: lockstep hooks/statusline move to local, foreign stays", async () => {
|
|
36
|
+
const dir = await mkdtemp(join(tmpdir(), "lockstep-init-heal-"));
|
|
37
|
+
await mkdir(join(dir, ".claude"), { recursive: true });
|
|
38
|
+
// A repo initialized BEFORE the personal-scope split: our hooks + statusline sit in the committed file.
|
|
39
|
+
await writeFile(join(dir, ".claude", "settings.json"), JSON.stringify({
|
|
40
|
+
hooks: {
|
|
41
|
+
PostToolUse: [
|
|
42
|
+
{ matcher: "Bash", hooks: [{ type: "command", command: "my-linter" }] },
|
|
43
|
+
{ matcher: "Edit|Write", hooks: [{ type: "command", command: "lockstep", args: ["capture"] }] },
|
|
44
|
+
],
|
|
45
|
+
Stop: [{ hooks: [{ type: "command", command: "lockstep", args: ["capture", "--event", "Stop"] }] }],
|
|
46
|
+
},
|
|
47
|
+
statusLine: { type: "command", command: "lockstep", args: ["statusline"] },
|
|
48
|
+
}, null, 2));
|
|
49
|
+
await claudeAdapter.install(dir, "project", false);
|
|
50
|
+
const shared = await readFile(join(dir, ".claude", "settings.json"), "utf8");
|
|
51
|
+
const local = await readFile(join(dir, ".claude", "settings.local.json"), "utf8");
|
|
52
|
+
assert.ok(shared.includes("my-linter"), "foreign hook survives the heal");
|
|
53
|
+
assert.ok(!shared.includes("lockstep"), "every lockstep-managed entry is stripped from the shared file");
|
|
54
|
+
const sharedObj = JSON.parse(shared);
|
|
55
|
+
assert.equal(sharedObj.hooks?.Stop, undefined, "an event left empty by the heal is dropped");
|
|
56
|
+
assert.equal(sharedObj.statusLine, undefined, "lockstep statusline removed from the shared file");
|
|
57
|
+
assert.ok(local.includes("capture") && local.includes("statusLine"), "personal file carries hooks + statusline");
|
|
58
|
+
// A FOREIGN statusline is never touched (mergeStatusLine never overwrote it; the heal must not strip it).
|
|
59
|
+
const dir2 = await mkdtemp(join(tmpdir(), "lockstep-init-heal2-"));
|
|
60
|
+
await mkdir(join(dir2, ".claude"), { recursive: true });
|
|
61
|
+
await writeFile(join(dir2, ".claude", "settings.json"), JSON.stringify({ statusLine: { type: "command", command: "my-status" } }, null, 2));
|
|
62
|
+
await claudeAdapter.install(dir2, "project", false);
|
|
63
|
+
const shared2 = await readFile(join(dir2, ".claude", "settings.json"), "utf8");
|
|
64
|
+
assert.ok(shared2.includes("my-status"), "foreign statusline untouched");
|
|
65
|
+
});
|
|
66
|
+
test("init never creates a shared settings.json just to heal it", async () => {
|
|
67
|
+
const dir = await mkdtemp(join(tmpdir(), "lockstep-init-noshared-"));
|
|
68
|
+
await claudeAdapter.install(dir, "project", false);
|
|
69
|
+
await assert.rejects(readFile(join(dir, ".claude", "settings.json"), "utf8"), "shared file not minted");
|
|
70
|
+
await readFile(join(dir, ".claude", "settings.local.json"), "utf8"); // personal file exists
|
|
71
|
+
});
|
|
72
|
+
test("init installs the lockstep-setup onboarding skill", async () => {
|
|
73
|
+
const dir = await mkdtemp(join(tmpdir(), "lockstep-setup-init-"));
|
|
74
|
+
await mkdir(join(dir, ".claude"), { recursive: true });
|
|
75
|
+
const results = await claudeAdapter.install(dir, "project", false);
|
|
76
|
+
const setup = await readFile(join(dir, ".claude", "skills", "lockstep-setup", "SKILL.md"), "utf8");
|
|
77
|
+
assert.ok(setup.includes("name: lockstep-setup"), "setup skill frontmatter written");
|
|
78
|
+
assert.ok(setup.includes("lockstep scan"), "setup skill drives the scan command");
|
|
79
|
+
assert.ok(results.some((r) => r.includes("lockstep-setup")), "install reports the setup-skill artifact");
|
|
80
|
+
const dry = await claudeAdapter.install(dir, "project", true);
|
|
81
|
+
assert.ok(dry.some((r) => r.startsWith("unchanged") && r.includes("lockstep-setup")), "dry-run reports the setup skill unchanged after install");
|
|
82
|
+
});
|
|
32
83
|
//# sourceMappingURL=install.test.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"install.test.js","sourceRoot":"","sources":["../../src/adapters/install.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,MAAM,MAAM,oBAAoB,CAAC;AACxC,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,QAAQ,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AACvE,OAAO,EAAE,MAAM,EAAE,MAAM,SAAS,CAAC;AACjC,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAE5C,IAAI,CAAC,yEAAyE,EAAE,KAAK,IAAI,EAAE;IACzF,MAAM,GAAG,GAAG,MAAM,OAAO,CAAC,IAAI,CAAC,MAAM,EAAE,EAAE,gBAAgB,CAAC,CAAC,CAAC;IAC5D,MAAM,KAAK,CAAC,IAAI,CAAC,GAAG,EAAE,SAAS,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IACvD,MAAM,SAAS,CACb,IAAI,CAAC,GAAG,EAAE,SAAS,EAAE,eAAe,CAAC,EACrC,IAAI,CAAC,SAAS,CACZ,EAAE,KAAK,EAAE,EAAE,WAAW,EAAE,CAAC,EAAE,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC,EAAE,IAAI,EAAE,SAAS,EAAE,OAAO,EAAE,WAAW,EAAE,CAAC,EAAE,CAAC,EAAE,EAAE,EACrG,IAAI,EACJ,CAAC,CACF,CACF,CAAC;IACF,MAAM,SAAS,CACb,IAAI,CAAC,GAAG,EAAE,WAAW,CAAC,EACtB,IAAI,CAAC,SAAS,CAAC,EAAE,UAAU,EAAE,EAAE,KAAK,EAAE,EAAE,OAAO,EAAE,GAAG,EAAE,IAAI,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,IAAI,EAAE,CAAC,CAAC,CAC/E,CAAC;IACF,MAAM,SAAS,CAAC,IAAI,CAAC,GAAG,EAAE,WAAW,CAAC,EAAE,mCAAmC,CAAC,CAAC;IAE7E,MAAM,IAAI,GAAG,KAAK,IAAI,EAAE,CAAC,CAAC;QACxB,QAAQ,EAAE,MAAM,QAAQ,CAAC,IAAI,CAAC,GAAG,EAAE,SAAS,EAAE,eAAe,CAAC,EAAE,MAAM,CAAC;QACvE,GAAG,EAAE,MAAM,QAAQ,CAAC,IAAI,CAAC,GAAG,EAAE,WAAW,CAAC,EAAE,MAAM,CAAC;QACnD,MAAM,EAAE,MAAM,QAAQ,CAAC,IAAI,CAAC,GAAG,EAAE,WAAW,CAAC,EAAE,MAAM,CAAC;QACtD,KAAK,EAAE,MAAM,QAAQ,CAAC,IAAI,CAAC,GAAG,EAAE,SAAS,EAAE,QAAQ,EAAE,UAAU,EAAE,UAAU,CAAC,EAAE,MAAM,CAAC;KACtF,CAAC,CAAC;IAEH,MAAM,aAAa,CAAC,OAAO,CAAC,GAAG,EAAE,SAAS,EAAE,KAAK,CAAC,CAAC;IACnD,MAAM,MAAM,GAAG,MAAM,IAAI,EAAE,CAAC;IAC5B,MAAM,aAAa,CAAC,OAAO,CAAC,GAAG,EAAE,SAAS,EAAE,KAAK,CAAC,CAAC;IACnD,MAAM,MAAM,GAAG,MAAM,IAAI,EAAE,CAAC;IAE5B,MAAM,CAAC,SAAS,CAAC,MAAM,EAAE,MAAM,EAAE,4CAA4C,CAAC,CAAC;IAC/E,MAAM,CAAC,EAAE,CAAC,MAAM,CAAC,QAAQ,CAAC,QAAQ,CAAC,WAAW,CAAC,EAAE,
|
|
1
|
+
{"version":3,"file":"install.test.js","sourceRoot":"","sources":["../../src/adapters/install.test.ts"],"names":[],"mappings":"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"}
|
package/dist/adapters/merge.d.ts
CHANGED
|
@@ -23,5 +23,16 @@ export interface McpServerSpec {
|
|
|
23
23
|
export declare function mergeHooks(existing: string | null, managed: ManagedHook[], command?: string): string;
|
|
24
24
|
/** Upsert our MCP server under its stable key; leave sibling servers untouched. */
|
|
25
25
|
export declare function mergeMcp(existing: string | null, name: string, spec: McpServerSpec): string;
|
|
26
|
+
/** Merge statusLine config into settings JSON. Only sets it if not already configured. */
|
|
27
|
+
export declare function mergeStatusLine(existing: string | null, command: string, args: string[]): string;
|
|
28
|
+
/**
|
|
29
|
+
* Strip lockstep-managed hook entries from a settings JSON, preserving foreign hooks byte-for-byte.
|
|
30
|
+
* The healing half of the personal-scope move (IMPROVEMENTS #2): hooks/statusline used to land in
|
|
31
|
+
* the committed `.claude/settings.json`; re-running init removes them there (this) and re-writes
|
|
32
|
+
* them to `settings.local.json` (mergeHooks).
|
|
33
|
+
*/
|
|
34
|
+
export declare function removeManagedHooks(existing: string): string;
|
|
35
|
+
/** Strip a lockstep-managed statusLine (a user's custom one is never ours — mergeStatusLine never overwrote it). */
|
|
36
|
+
export declare function removeManagedStatusLine(existing: string): string;
|
|
26
37
|
/** Replace only the delimited managed block in a markdown file; append if absent. */
|
|
27
38
|
export declare function upsertManagedBlock(existing: string | null, block: string): string;
|
package/dist/adapters/merge.js
CHANGED
|
@@ -34,6 +34,47 @@ export function mergeMcp(existing, name, spec) {
|
|
|
34
34
|
obj.mcpServers = servers;
|
|
35
35
|
return JSON.stringify(obj, null, 2) + "\n";
|
|
36
36
|
}
|
|
37
|
+
/** Merge statusLine config into settings JSON. Only sets it if not already configured. */
|
|
38
|
+
export function mergeStatusLine(existing, command, args) {
|
|
39
|
+
const obj = existing ? JSON.parse(existing) : {};
|
|
40
|
+
// Only install if no statusLine is configured yet (don't overwrite user's custom one)
|
|
41
|
+
if (!obj.statusLine) {
|
|
42
|
+
obj.statusLine = { type: "command", command, args };
|
|
43
|
+
}
|
|
44
|
+
return JSON.stringify(obj, null, 2) + "\n";
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Strip lockstep-managed hook entries from a settings JSON, preserving foreign hooks byte-for-byte.
|
|
48
|
+
* The healing half of the personal-scope move (IMPROVEMENTS #2): hooks/statusline used to land in
|
|
49
|
+
* the committed `.claude/settings.json`; re-running init removes them there (this) and re-writes
|
|
50
|
+
* them to `settings.local.json` (mergeHooks).
|
|
51
|
+
*/
|
|
52
|
+
export function removeManagedHooks(existing) {
|
|
53
|
+
const obj = JSON.parse(existing);
|
|
54
|
+
const hooks = obj.hooks;
|
|
55
|
+
if (hooks) {
|
|
56
|
+
for (const [event, arr] of Object.entries(hooks)) {
|
|
57
|
+
if (!Array.isArray(arr))
|
|
58
|
+
continue;
|
|
59
|
+
const foreign = arr.filter((e) => !isOurs(e));
|
|
60
|
+
if (foreign.length > 0)
|
|
61
|
+
hooks[event] = foreign;
|
|
62
|
+
else
|
|
63
|
+
delete hooks[event];
|
|
64
|
+
}
|
|
65
|
+
if (Object.keys(hooks).length === 0)
|
|
66
|
+
delete obj.hooks;
|
|
67
|
+
}
|
|
68
|
+
return JSON.stringify(obj, null, 2) + "\n";
|
|
69
|
+
}
|
|
70
|
+
/** Strip a lockstep-managed statusLine (a user's custom one is never ours — mergeStatusLine never overwrote it). */
|
|
71
|
+
export function removeManagedStatusLine(existing) {
|
|
72
|
+
const obj = JSON.parse(existing);
|
|
73
|
+
const sl = obj.statusLine;
|
|
74
|
+
if (sl && `${sl.command ?? ""} ${(sl.args ?? []).join(" ")}`.includes(MARKER))
|
|
75
|
+
delete obj.statusLine;
|
|
76
|
+
return JSON.stringify(obj, null, 2) + "\n";
|
|
77
|
+
}
|
|
37
78
|
const START = "<!-- lockstep:start -->";
|
|
38
79
|
const END = "<!-- lockstep:end -->";
|
|
39
80
|
/** Replace only the delimited managed block in a markdown file; append if absent. */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"merge.js","sourceRoot":"","sources":["../../src/adapters/merge.ts"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"merge.js","sourceRoot":"","sources":["../../src/adapters/merge.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { ManagedHook, McpServerSpec } from "./merge.js";
|
|
2
2
|
export declare const mcpSpec: (vendor: string) => McpServerSpec;
|
|
3
3
|
export declare const captureHooks: ManagedHook[];
|
|
4
|
-
export declare const SKILL_MD = "---\nname: lockstep\ndescription: Keep this repo's coding agents in lockstep \u2014 read the shared ledger before coding, publish changes after.\n---\n\n# Lockstep\n\nThis project uses Lockstep to coordinate multiple developers' coding agents on the same codebase.\n\n## On session start\n- Call `inbox` to see what changed, what's newly binding, and what's delegated to you.\n- Call `decisions` to load the binding rules for the areas you'll touch.\n- If you see open questions or tasks in the inbox, tell the user about them.\n\n## Before coding a shared/contract surface\n- Call `query` to check the ledger for existing decisions/contracts (answer instantly if known).\n- Respect any `binding` decision in scope.\n\n## After making a change\n- Summarize the change and call `notify` (include a contract delta for interface changes).\n- For any surface you call, record the dependency with `register_dependency`.\n\n## Coordinating\n- Use `ask` for code/repo questions (set `urgent` if you're blocked).\n- Use `delegate` / `complete` for handoffs
|
|
5
|
-
export declare const
|
|
4
|
+
export declare const SKILL_MD = "---\nname: lockstep\ndescription: Keep this repo's coding agents in lockstep \u2014 read the shared ledger before coding, publish changes after.\n---\n\n# Lockstep\n\nThis project uses Lockstep to coordinate multiple developers' coding agents on the same codebase.\n\n## On session start\n- Call `inbox` to see what changed, what's newly binding, and what's delegated to you.\n- Call `decisions` to load the binding rules for the areas you'll touch. A rule may cite an external source (e.g. a Slack thread) \u2014 it was distilled from a human decision and confirmed by a teammate; treat it exactly like any other binding decision.\n- The briefing leads with **project principles** (`decisionType: principle`) \u2014 the team's standing\n decision criteria (\"server-side business logic \u2192 X\"). Apply them when making or judging decisions;\n a choice that a principle already settles needs no fresh deliberation, just cite the principle.\n- If you see open questions or tasks in the inbox, tell the user about them.\n\n## Before coding a shared/contract surface\n- Call `query` to check the ledger for existing decisions/contracts (answer instantly if known).\n- Before changing or removing an endpoint/RPC, call `consumers` with its canonical surface ID\n (e.g. `http:POST /auth/session`) to see who depends on it \u2014 answer \"does anyone use this?\" from\n the graph instead of pinging a human. A high consumer count means high blast radius: log a decision.\n- Respect any `binding` decision in scope.\n\n## Decisions vs changes \u2014 the most important distinction\n- A **change** is a routine event (you edited some files). It is captured automatically \u2014 you do NOT\n log it. Never call `propose_decision` just because you edited code.\n- A **decision** is a durable **rule** or **architectural choice** that shapes future work\n (e.g. \"auth tokens are JWT, 15-min expiry\"; \"we standardize on Postgres\"; \"rename POST /login to\n POST /session across the org\"). These are the hero of Lockstep.\n- **Whenever you (or the user) make a real decision like that, you MUST log it** with\n `propose_decision` \u2014 set `decisionType` to `rule` or `architecture`. If you're unsure whether\n something is a decision, ask: \"will this constrain how others build later?\" If yes, log it.\n- A decision is more than its verdict \u2014 record the deliberation too (this is what makes the ledger\n readable a year later):\n - `rationale`: WHY, in one or two sentences (the constraint or trade-off that drove it).\n - `alternatives`: the options considered and rejected, when any were discussed.\n - `reviewAt` (ISO date): only when the decision is explicitly temporary (\"revisit after the\n migration\", \"review in 30 days\"). If the revisit point is an event with no date, ask the user\n for a date or omit it \u2014 never invent one.\n\n## After making a change\n- Summarize the change and call `notify` (include a contract delta for interface changes).\n- For any surface you call, record the dependency with `register_dependency`.\n\n## Coordinating\n- Use `ask` for code/repo questions (set `urgent` if you're blocked).\n- Use `delegate` / `complete` for handoffs.\n\n## Incoming messages\n- When you see a \"[Lockstep]\" notification, inform the user about the pending message(s).\n- The user decides whether to respond \u2014 don't auto-answer on their behalf.\n\n## First-run onboarding & keeping the manifest current\n- If this repo has no `lockstep.yaml` (or you've added/removed routes or outbound service calls), run the\n `/lockstep-setup` skill. It scans the repo, auto-fills `produces`, and resolves `consumes` against the\n team's dependency graph \u2014 always human-approved. Do NOT hand-edit `lockstep.yaml` yourself; `/lockstep-setup` is the only writer.\n";
|
|
5
|
+
export declare const SETUP_SKILL_MD = "---\nname: lockstep-setup\ndescription: Bootstrap and maintain this repo's lockstep.yaml \u2014 auto-fill produces, graph-resolve consumes, human-approved.\ntrigger: /lockstep-setup\n---\n\n# /lockstep-setup\n\nRun-once onboarding (and re-run maintenance) that generates this repo's `lockstep.yaml` \u2014 the manifest\nthat declares what surfaces this repo **produces** (its served routes/RPCs) and **consumes** (the surfaces\nit calls on other repos). This is the concrete first-run step `lockstep init` does NOT do.\n\n## Why this is the only place the manifest is written\nThe raw capture hook is read-only and must stay that way. This skill is the **single sanctioned writer**\nof `lockstep.yaml`, and every write is proposed to the human first. You (the agent) never hand-edit the\nfile \u2014 you run `lockstep scan`, present its proposal, get approval, then let `--apply` write it.\n\n## How consumes are resolved (don't guess URLs)\nLockstep is a graph. `lockstep scan` detects this repo's outbound calls (`fetch`/`axios`/gRPC clients) and\n**matches them against the produced-surface catalog of the other repos in the project.** A call that\nmatches a sibling's produce is a real dependency (the producer is named); one that matches nothing is\nexternal. The deterministic catalog does the resolving \u2014 your job is judgment on the fuzzy leftovers and\ndriving the human approval.\n\n## What You Must Do When Invoked\n\nFollow these steps in order.\n\n### 1. Preconditions\nMake sure the repo is connected: if `lockstep status` shows it isn't, tell the user to run\n`lockstep connect` first (without a connection, `produces` still works but `consumes` can't be resolved\nagainst the graph).\n\n### 2. Scan\nRun `lockstep scan --json` and read the proposal. It has:\n- `produces` (+ `newProduces`) \u2014 served surfaces, deterministic. Safe to accept as-is.\n- `consumes` (+ `newConsumes`) \u2014 outbound calls **matched to a sibling repo** (`producer` named). High confidence.\n- `review` \u2014 client/import hints that could map to a sibling's surfaces. Ask the user which (if any) apply.\n- `unmatched` \u2014 outbound calls with no producer in the graph (external, e.g. Stripe, or a service not yet onboarded). Don't add these unless the user says so.\n\n### 3. Present for approval\nShow the user a concise diff: the new produces, the matched consumes (with producer repos), and any\n`review`/`unmatched` items that need a human call. Ask explicitly before writing \u2014 especially for\nanything from `review`/`unmatched`.\n\n### 4. Apply\nOn approval, run `lockstep scan --apply`. This merge-preserves any existing manual entries, writes\n`lockstep.yaml` (+ a `.lockstep.bak`), and syncs the produces/consumes to the graph. For anything the\nuser chose from `review`/`unmatched`, add those surface IDs to `consumes` in `lockstep.yaml` and re-run\n`lockstep scan --apply` (it re-syncs).\n\n### 5. Offer next steps\nSuggest committing `lockstep.yaml` so teammates get the same graph, and (if not connected yet)\n`lockstep connect`. Shared config to commit: `.mcp.json`, `CLAUDE.md`, `.claude/skills`.\n`.claude/settings.local.json` (hooks + statusline) is personal and auto-gitignored \u2014 each teammate\nruns `lockstep onboard` to get their own.\n\n## Maintenance (re-run anytime)\nRe-running `lockstep scan` diffs current code surfaces against `lockstep.yaml` and shows only the delta\n(e.g. \"new outbound call to `http:GET /inventory/:sku` \u2014 add to consumes?\"). Approve the delta the same way.\n";
|
|
6
|
+
export declare const CLAUDE_BLOCK = "## Lockstep (team coordination)\nIMPORTANT: On session start, BEFORE doing anything else, call `inbox` and `decisions`. If there are any open questions, tasks, or changes, you MUST tell the user immediately \u2014 do not skip this. Example: \"You have 1 new question from a teammate: [question text]\". Then proceed with the user's request.\nBefore coding a shared/contract surface, `query` the ledger and obey binding decisions. After a change, summarize it, `register_dependency` for surfaces you call, and `notify`. IMPORTANT: a routine code change is captured automatically \u2014 do NOT log it as a decision. But whenever you or the user make a durable **rule or architectural choice** that will constrain future work, you MUST record it with `propose_decision` (`decisionType: rule | architecture | principle`) \u2014 include `rationale` (why) and `alternatives` (what was rejected) when known, and `reviewAt` (ISO date) when the decision is explicitly temporary. Project principles (`decisionType: principle`) are the team's standing decision criteria \u2014 apply them when judging new decisions. Ask code/repo questions with `ask` (urgent if blocking). When you see a \"[Lockstep]\" notification, inform the user about pending messages. If this repo has no `lockstep.yaml` (or you changed its routes/outbound calls), run the `/lockstep-setup` skill to (re)generate it \u2014 never hand-edit `lockstep.yaml`. See the `lockstep` skill for detail.";
|
|
@@ -26,26 +26,115 @@ This project uses Lockstep to coordinate multiple developers' coding agents on t
|
|
|
26
26
|
|
|
27
27
|
## On session start
|
|
28
28
|
- Call \`inbox\` to see what changed, what's newly binding, and what's delegated to you.
|
|
29
|
-
- Call \`decisions\` to load the binding rules for the areas you'll touch.
|
|
29
|
+
- Call \`decisions\` to load the binding rules for the areas you'll touch. A rule may cite an external source (e.g. a Slack thread) — it was distilled from a human decision and confirmed by a teammate; treat it exactly like any other binding decision.
|
|
30
|
+
- The briefing leads with **project principles** (\`decisionType: principle\`) — the team's standing
|
|
31
|
+
decision criteria ("server-side business logic → X"). Apply them when making or judging decisions;
|
|
32
|
+
a choice that a principle already settles needs no fresh deliberation, just cite the principle.
|
|
30
33
|
- If you see open questions or tasks in the inbox, tell the user about them.
|
|
31
34
|
|
|
32
35
|
## Before coding a shared/contract surface
|
|
33
36
|
- Call \`query\` to check the ledger for existing decisions/contracts (answer instantly if known).
|
|
37
|
+
- Before changing or removing an endpoint/RPC, call \`consumers\` with its canonical surface ID
|
|
38
|
+
(e.g. \`http:POST /auth/session\`) to see who depends on it — answer "does anyone use this?" from
|
|
39
|
+
the graph instead of pinging a human. A high consumer count means high blast radius: log a decision.
|
|
34
40
|
- Respect any \`binding\` decision in scope.
|
|
35
41
|
|
|
42
|
+
## Decisions vs changes — the most important distinction
|
|
43
|
+
- A **change** is a routine event (you edited some files). It is captured automatically — you do NOT
|
|
44
|
+
log it. Never call \`propose_decision\` just because you edited code.
|
|
45
|
+
- A **decision** is a durable **rule** or **architectural choice** that shapes future work
|
|
46
|
+
(e.g. "auth tokens are JWT, 15-min expiry"; "we standardize on Postgres"; "rename POST /login to
|
|
47
|
+
POST /session across the org"). These are the hero of Lockstep.
|
|
48
|
+
- **Whenever you (or the user) make a real decision like that, you MUST log it** with
|
|
49
|
+
\`propose_decision\` — set \`decisionType\` to \`rule\` or \`architecture\`. If you're unsure whether
|
|
50
|
+
something is a decision, ask: "will this constrain how others build later?" If yes, log it.
|
|
51
|
+
- A decision is more than its verdict — record the deliberation too (this is what makes the ledger
|
|
52
|
+
readable a year later):
|
|
53
|
+
- \`rationale\`: WHY, in one or two sentences (the constraint or trade-off that drove it).
|
|
54
|
+
- \`alternatives\`: the options considered and rejected, when any were discussed.
|
|
55
|
+
- \`reviewAt\` (ISO date): only when the decision is explicitly temporary ("revisit after the
|
|
56
|
+
migration", "review in 30 days"). If the revisit point is an event with no date, ask the user
|
|
57
|
+
for a date or omit it — never invent one.
|
|
58
|
+
|
|
36
59
|
## After making a change
|
|
37
60
|
- Summarize the change and call \`notify\` (include a contract delta for interface changes).
|
|
38
61
|
- For any surface you call, record the dependency with \`register_dependency\`.
|
|
39
62
|
|
|
40
63
|
## Coordinating
|
|
41
64
|
- Use \`ask\` for code/repo questions (set \`urgent\` if you're blocked).
|
|
42
|
-
- Use \`delegate\` / \`complete\` for handoffs.
|
|
65
|
+
- Use \`delegate\` / \`complete\` for handoffs.
|
|
43
66
|
|
|
44
67
|
## Incoming messages
|
|
45
68
|
- When you see a "[Lockstep]" notification, inform the user about the pending message(s).
|
|
46
69
|
- The user decides whether to respond — don't auto-answer on their behalf.
|
|
70
|
+
|
|
71
|
+
## First-run onboarding & keeping the manifest current
|
|
72
|
+
- If this repo has no \`lockstep.yaml\` (or you've added/removed routes or outbound service calls), run the
|
|
73
|
+
\`/lockstep-setup\` skill. It scans the repo, auto-fills \`produces\`, and resolves \`consumes\` against the
|
|
74
|
+
team's dependency graph — always human-approved. Do NOT hand-edit \`lockstep.yaml\` yourself; \`/lockstep-setup\` is the only writer.
|
|
75
|
+
`;
|
|
76
|
+
export const SETUP_SKILL_MD = `---
|
|
77
|
+
name: lockstep-setup
|
|
78
|
+
description: Bootstrap and maintain this repo's lockstep.yaml — auto-fill produces, graph-resolve consumes, human-approved.
|
|
79
|
+
trigger: /lockstep-setup
|
|
80
|
+
---
|
|
81
|
+
|
|
82
|
+
# /lockstep-setup
|
|
83
|
+
|
|
84
|
+
Run-once onboarding (and re-run maintenance) that generates this repo's \`lockstep.yaml\` — the manifest
|
|
85
|
+
that declares what surfaces this repo **produces** (its served routes/RPCs) and **consumes** (the surfaces
|
|
86
|
+
it calls on other repos). This is the concrete first-run step \`lockstep init\` does NOT do.
|
|
87
|
+
|
|
88
|
+
## Why this is the only place the manifest is written
|
|
89
|
+
The raw capture hook is read-only and must stay that way. This skill is the **single sanctioned writer**
|
|
90
|
+
of \`lockstep.yaml\`, and every write is proposed to the human first. You (the agent) never hand-edit the
|
|
91
|
+
file — you run \`lockstep scan\`, present its proposal, get approval, then let \`--apply\` write it.
|
|
92
|
+
|
|
93
|
+
## How consumes are resolved (don't guess URLs)
|
|
94
|
+
Lockstep is a graph. \`lockstep scan\` detects this repo's outbound calls (\`fetch\`/\`axios\`/gRPC clients) and
|
|
95
|
+
**matches them against the produced-surface catalog of the other repos in the project.** A call that
|
|
96
|
+
matches a sibling's produce is a real dependency (the producer is named); one that matches nothing is
|
|
97
|
+
external. The deterministic catalog does the resolving — your job is judgment on the fuzzy leftovers and
|
|
98
|
+
driving the human approval.
|
|
99
|
+
|
|
100
|
+
## What You Must Do When Invoked
|
|
101
|
+
|
|
102
|
+
Follow these steps in order.
|
|
103
|
+
|
|
104
|
+
### 1. Preconditions
|
|
105
|
+
Make sure the repo is connected: if \`lockstep status\` shows it isn't, tell the user to run
|
|
106
|
+
\`lockstep connect\` first (without a connection, \`produces\` still works but \`consumes\` can't be resolved
|
|
107
|
+
against the graph).
|
|
108
|
+
|
|
109
|
+
### 2. Scan
|
|
110
|
+
Run \`lockstep scan --json\` and read the proposal. It has:
|
|
111
|
+
- \`produces\` (+ \`newProduces\`) — served surfaces, deterministic. Safe to accept as-is.
|
|
112
|
+
- \`consumes\` (+ \`newConsumes\`) — outbound calls **matched to a sibling repo** (\`producer\` named). High confidence.
|
|
113
|
+
- \`review\` — client/import hints that could map to a sibling's surfaces. Ask the user which (if any) apply.
|
|
114
|
+
- \`unmatched\` — outbound calls with no producer in the graph (external, e.g. Stripe, or a service not yet onboarded). Don't add these unless the user says so.
|
|
115
|
+
|
|
116
|
+
### 3. Present for approval
|
|
117
|
+
Show the user a concise diff: the new produces, the matched consumes (with producer repos), and any
|
|
118
|
+
\`review\`/\`unmatched\` items that need a human call. Ask explicitly before writing — especially for
|
|
119
|
+
anything from \`review\`/\`unmatched\`.
|
|
120
|
+
|
|
121
|
+
### 4. Apply
|
|
122
|
+
On approval, run \`lockstep scan --apply\`. This merge-preserves any existing manual entries, writes
|
|
123
|
+
\`lockstep.yaml\` (+ a \`.lockstep.bak\`), and syncs the produces/consumes to the graph. For anything the
|
|
124
|
+
user chose from \`review\`/\`unmatched\`, add those surface IDs to \`consumes\` in \`lockstep.yaml\` and re-run
|
|
125
|
+
\`lockstep scan --apply\` (it re-syncs).
|
|
126
|
+
|
|
127
|
+
### 5. Offer next steps
|
|
128
|
+
Suggest committing \`lockstep.yaml\` so teammates get the same graph, and (if not connected yet)
|
|
129
|
+
\`lockstep connect\`. Shared config to commit: \`.mcp.json\`, \`CLAUDE.md\`, \`.claude/skills\`.
|
|
130
|
+
\`.claude/settings.local.json\` (hooks + statusline) is personal and auto-gitignored — each teammate
|
|
131
|
+
runs \`lockstep onboard\` to get their own.
|
|
132
|
+
|
|
133
|
+
## Maintenance (re-run anytime)
|
|
134
|
+
Re-running \`lockstep scan\` diffs current code surfaces against \`lockstep.yaml\` and shows only the delta
|
|
135
|
+
(e.g. "new outbound call to \`http:GET /inventory/:sku\` — add to consumes?"). Approve the delta the same way.
|
|
47
136
|
`;
|
|
48
137
|
export const CLAUDE_BLOCK = `## Lockstep (team coordination)
|
|
49
138
|
IMPORTANT: On session start, BEFORE doing anything else, call \`inbox\` and \`decisions\`. If there are any open questions, tasks, or changes, you MUST tell the user immediately — do not skip this. Example: "You have 1 new question from a teammate: [question text]". Then proceed with the user's request.
|
|
50
|
-
Before coding a shared/contract surface, \`query\` the ledger and obey binding decisions. After a change, summarize it, \`register_dependency\` for surfaces you call, and \`notify\`. Ask code/repo questions with \`ask\` (urgent if blocking). When you see a "[Lockstep]" notification, inform the user about pending messages. See the \`lockstep\` skill for detail.`;
|
|
139
|
+
Before coding a shared/contract surface, \`query\` the ledger and obey binding decisions. After a change, summarize it, \`register_dependency\` for surfaces you call, and \`notify\`. IMPORTANT: a routine code change is captured automatically — do NOT log it as a decision. But whenever you or the user make a durable **rule or architectural choice** that will constrain future work, you MUST record it with \`propose_decision\` (\`decisionType: rule | architecture | principle\`) — include \`rationale\` (why) and \`alternatives\` (what was rejected) when known, and \`reviewAt\` (ISO date) when the decision is explicitly temporary. Project principles (\`decisionType: principle\`) are the team's standing decision criteria — apply them when judging new decisions. Ask code/repo questions with \`ask\` (urgent if blocking). When you see a "[Lockstep]" notification, inform the user about pending messages. If this repo has no \`lockstep.yaml\` (or you changed its routes/outbound calls), run the \`/lockstep-setup\` skill to (re)generate it — never hand-edit \`lockstep.yaml\`. See the \`lockstep\` skill for detail.`;
|
|
51
140
|
//# sourceMappingURL=templates.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"templates.js","sourceRoot":"","sources":["../../src/adapters/templates.ts"],"names":[],"mappings":"AAEA,qFAAqF;AACrF,yFAAyF;AACzF,MAAM,CAAC,MAAM,OAAO,GAAG,CAAC,MAAc,EAAiB,EAAE,CAAC,CAAC;IACzD,OAAO,EAAE,UAAU;IACnB,IAAI,EAAE,CAAC,KAAK,CAAC;IACb,GAAG,EAAE,EAAE,eAAe,EAAE,MAAM,EAAE;CACjC,CAAC,CAAC;AAEH,MAAM,CAAC,MAAM,YAAY,GAAkB;IACzC,EAAE,KAAK,EAAE,cAAc,EAAE,OAAO,EAAE,GAAG,EAAE,IAAI,EAAE,CAAC,SAAS,EAAE,SAAS,EAAE,cAAc,CAAC,EAAE,OAAO,EAAE,EAAE,EAAE;IAClG;QACE,KAAK,EAAE,aAAa;QACpB,OAAO,EAAE,mCAAmC;QAC5C,IAAI,EAAE,CAAC,SAAS,EAAE,SAAS,EAAE,aAAa,CAAC;QAC3C,OAAO,EAAE,EAAE;KACZ;IACD,EAAE,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,GAAG,EAAE,IAAI,EAAE,CAAC,SAAS,EAAE,SAAS,EAAE,MAAM,CAAC,EAAE,OAAO,EAAE,EAAE,EAAE;CACnF,CAAC;AAEF,MAAM,CAAC,MAAM,QAAQ,GAAG
|
|
1
|
+
{"version":3,"file":"templates.js","sourceRoot":"","sources":["../../src/adapters/templates.ts"],"names":[],"mappings":"AAEA,qFAAqF;AACrF,yFAAyF;AACzF,MAAM,CAAC,MAAM,OAAO,GAAG,CAAC,MAAc,EAAiB,EAAE,CAAC,CAAC;IACzD,OAAO,EAAE,UAAU;IACnB,IAAI,EAAE,CAAC,KAAK,CAAC;IACb,GAAG,EAAE,EAAE,eAAe,EAAE,MAAM,EAAE;CACjC,CAAC,CAAC;AAEH,MAAM,CAAC,MAAM,YAAY,GAAkB;IACzC,EAAE,KAAK,EAAE,cAAc,EAAE,OAAO,EAAE,GAAG,EAAE,IAAI,EAAE,CAAC,SAAS,EAAE,SAAS,EAAE,cAAc,CAAC,EAAE,OAAO,EAAE,EAAE,EAAE;IAClG;QACE,KAAK,EAAE,aAAa;QACpB,OAAO,EAAE,mCAAmC;QAC5C,IAAI,EAAE,CAAC,SAAS,EAAE,SAAS,EAAE,aAAa,CAAC;QAC3C,OAAO,EAAE,EAAE;KACZ;IACD,EAAE,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,GAAG,EAAE,IAAI,EAAE,CAAC,SAAS,EAAE,SAAS,EAAE,MAAM,CAAC,EAAE,OAAO,EAAE,EAAE,EAAE;CACnF,CAAC;AAEF,MAAM,CAAC,MAAM,QAAQ,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAyDvB,CAAC;AAEF,MAAM,CAAC,MAAM,cAAc,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA4D7B,CAAC;AAEF,MAAM,CAAC,MAAM,YAAY,GAAG;;8lCAEkkC,CAAC"}
|
|
@@ -17,15 +17,17 @@ test("does not flag test files without contract markers", () => {
|
|
|
17
17
|
assert.ok(!isContractSurface("src/utils/test-helper.ts", "function internal() {}"));
|
|
18
18
|
assert.ok(!isContractSurface("src/utils/math.ts", "const x = 1;"));
|
|
19
19
|
});
|
|
20
|
-
test("exported interface in
|
|
21
|
-
assert.ok(isContractSurface("src/types.ts", "export interface OrderPayload { id: string; }"));
|
|
22
|
-
assert.ok(isContractSurface("src/types.ts", "export type OrderId = string;"));
|
|
20
|
+
test("a bare exported interface/type in a non-route file is NOT a contract surface (noise fix)", () => {
|
|
21
|
+
assert.ok(!isContractSurface("src/types.ts", "export interface OrderPayload { id: string; }"));
|
|
22
|
+
assert.ok(!isContractSurface("src/types.ts", "export type OrderId = string;"));
|
|
23
23
|
});
|
|
24
|
-
test("exported class is a contract surface", () => {
|
|
25
|
-
assert.ok(isContractSurface("src/service.ts", "export class OrderService {}"));
|
|
24
|
+
test("a bare exported class/function in a non-route file is NOT a contract surface (noise fix)", () => {
|
|
25
|
+
assert.ok(!isContractSurface("src/service.ts", "export class OrderService {}"));
|
|
26
|
+
assert.ok(!isContractSurface("src/orders.ts", "export async function fetchOrders() {}"));
|
|
26
27
|
});
|
|
27
|
-
test("
|
|
28
|
-
assert.ok(isContractSurface("
|
|
28
|
+
test("contract path still counts even without extractable content", () => {
|
|
29
|
+
assert.ok(isContractSurface("contracts/order-api.ts"));
|
|
30
|
+
assert.ok(isContractSurface("src/handlers/order-handler.ts"));
|
|
29
31
|
});
|
|
30
32
|
test("non-TS files without path markers are not contract surfaces", () => {
|
|
31
33
|
assert.ok(!isContractSurface("README.md", "# Readme"));
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"classify-edge.test.js","sourceRoot":"","sources":["../../src/capture/classify-edge.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,MAAM,MAAM,oBAAoB,CAAC;AACxC,OAAO,EAAE,iBAAiB,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAE/D,IAAI,CAAC,yCAAyC,EAAE,GAAG,EAAE;IACnD,MAAM,CAAC,EAAE,CAAC,iBAAiB,CAAC,iBAAiB,CAAC,CAAC,CAAC;IAChD,MAAM,CAAC,EAAE,CAAC,iBAAiB,CAAC,cAAc,CAAC,CAAC,CAAC;IAC7C,MAAM,CAAC,EAAE,CAAC,iBAAiB,CAAC,mBAAmB,CAAC,CAAC,CAAC;AACpD,CAAC,CAAC,CAAC;AAEH,IAAI,CAAC,uBAAuB,EAAE,GAAG,EAAE;IACjC,MAAM,CAAC,EAAE,CAAC,iBAAiB,CAAC,+BAA+B,CAAC,CAAC,CAAC;IAC9D,MAAM,CAAC,EAAE,CAAC,iBAAiB,CAAC,2BAA2B,CAAC,CAAC,CAAC;AAC5D,CAAC,CAAC,CAAC;AAEH,IAAI,CAAC,wBAAwB,EAAE,GAAG,EAAE;IAClC,MAAM,CAAC,EAAE,CAAC,iBAAiB,CAAC,wBAAwB,CAAC,CAAC,CAAC;AACzD,CAAC,CAAC,CAAC;AAEH,IAAI,CAAC,mDAAmD,EAAE,GAAG,EAAE;IAC7D,MAAM,CAAC,EAAE,CAAC,CAAC,iBAAiB,CAAC,0BAA0B,EAAE,wBAAwB,CAAC,CAAC,CAAC;IACpF,MAAM,CAAC,EAAE,CAAC,CAAC,iBAAiB,CAAC,mBAAmB,EAAE,cAAc,CAAC,CAAC,CAAC;AACrE,CAAC,CAAC,CAAC;AAEH,IAAI,CAAC,
|
|
1
|
+
{"version":3,"file":"classify-edge.test.js","sourceRoot":"","sources":["../../src/capture/classify-edge.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Contract-surface detection (
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
2
|
+
* Contract-surface detection (a safety gate). A file is a contract surface if it exposes a public
|
|
3
|
+
* interface others code against. We deliberately DROPPED the old "any file with an `export` is a
|
|
4
|
+
* contract surface" heuristic — it flagged essentially all application code as `shared`, which both
|
|
5
|
+
* destroyed the owned/shared signal and flooded the ledger. Now: real extractable surfaces (HTTP
|
|
6
|
+
* routes, proto/graphql RPCs) or unambiguous interface files (.proto/.graphql, OpenAPI specs,
|
|
7
|
+
* route/controller paths). Pure + testable.
|
|
6
8
|
*/
|
|
7
9
|
export declare function isContractSurface(path: string, content?: string): boolean;
|
|
8
10
|
/** owned (only you own all changed paths, none are contract surfaces) vs shared. */
|
package/dist/capture/classify.js
CHANGED
|
@@ -1,20 +1,21 @@
|
|
|
1
|
+
import { extractAllSurfaces } from "./extract.js";
|
|
1
2
|
/**
|
|
2
|
-
* Contract-surface detection (
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
3
|
+
* Contract-surface detection (a safety gate). A file is a contract surface if it exposes a public
|
|
4
|
+
* interface others code against. We deliberately DROPPED the old "any file with an `export` is a
|
|
5
|
+
* contract surface" heuristic — it flagged essentially all application code as `shared`, which both
|
|
6
|
+
* destroyed the owned/shared signal and flooded the ledger. Now: real extractable surfaces (HTTP
|
|
7
|
+
* routes, proto/graphql RPCs) or unambiguous interface files (.proto/.graphql, OpenAPI specs,
|
|
8
|
+
* route/controller paths). Pure + testable.
|
|
6
9
|
*/
|
|
7
10
|
export function isContractSurface(path, content) {
|
|
11
|
+
if (extractAllSurfaces(path, content).length > 0)
|
|
12
|
+
return true;
|
|
8
13
|
if (/(openapi|swagger)/i.test(path) && /\.(ya?ml|json)$/i.test(path))
|
|
9
14
|
return true;
|
|
10
15
|
if (/\.(proto|graphql|gql)$/i.test(path))
|
|
11
16
|
return true;
|
|
12
17
|
if (/(^|\/)(routes?|controllers?|api|handlers?|endpoints?|contracts?)(\/|\.)/i.test(path))
|
|
13
18
|
return true;
|
|
14
|
-
if (content && /\.(ts|tsx|js|mjs)$/i.test(path)) {
|
|
15
|
-
if (/\bexport\s+(async\s+)?(function|const|class|interface|type)\b/.test(content))
|
|
16
|
-
return true;
|
|
17
|
-
}
|
|
18
19
|
return false;
|
|
19
20
|
}
|
|
20
21
|
/** owned (only you own all changed paths, none are contract surfaces) vs shared. */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"classify.js","sourceRoot":"","sources":["../../src/capture/classify.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"classify.js","sourceRoot":"","sources":["../../src/capture/classify.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,kBAAkB,EAAE,MAAM,cAAc,CAAC;AAElD;;;;;;;GAOG;AACH,MAAM,UAAU,iBAAiB,CAAC,IAAY,EAAE,OAAgB;IAC9D,IAAI,kBAAkB,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC,MAAM,GAAG,CAAC;QAAE,OAAO,IAAI,CAAC;IAC9D,IAAI,oBAAoB,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,kBAAkB,CAAC,IAAI,CAAC,IAAI,CAAC;QAAE,OAAO,IAAI,CAAC;IAClF,IAAI,yBAAyB,CAAC,IAAI,CAAC,IAAI,CAAC;QAAE,OAAO,IAAI,CAAC;IACtD,IAAI,0EAA0E,CAAC,IAAI,CAAC,IAAI,CAAC;QAAE,OAAO,IAAI,CAAC;IACvG,OAAO,KAAK,CAAC;AACf,CAAC;AAED,oFAAoF;AACpF,MAAM,UAAU,WAAW,CAAC,IAA4D;IACtF,IAAI,IAAI,CAAC,kBAAkB;QAAE,OAAO,QAAQ,CAAC;IAC7C,OAAO,IAAI,CAAC,YAAY,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,QAAQ,CAAC;AAChD,CAAC"}
|
|
@@ -10,10 +10,15 @@ test("detects route/controller paths", () => {
|
|
|
10
10
|
assert.ok(isContractSurface("src/routes/orders.ts"));
|
|
11
11
|
assert.ok(isContractSurface("app/controllers/cart.rb"));
|
|
12
12
|
});
|
|
13
|
-
test("
|
|
14
|
-
|
|
13
|
+
test("a bare export is NOT a contract surface (noise fix)", () => {
|
|
14
|
+
// The old heuristic flagged any `export` as shared, flooding the ledger. A plain exported helper
|
|
15
|
+
// in a non-route file is no longer a contract surface.
|
|
16
|
+
assert.ok(!isContractSurface("src/orders.ts", "export function createOrder() {}"));
|
|
15
17
|
assert.ok(!isContractSurface("src/util.ts", "function localHelper() {}"));
|
|
16
18
|
});
|
|
19
|
+
test("detects an actual HTTP route by content", () => {
|
|
20
|
+
assert.ok(isContractSurface("src/routes/orders.ts", "router.post('/orders', handler)"));
|
|
21
|
+
});
|
|
17
22
|
test("plain internal file is not a contract surface", () => {
|
|
18
23
|
assert.ok(!isContractSurface("src/internal/helpers.go", "func helper() {}"));
|
|
19
24
|
});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"classify.test.js","sourceRoot":"","sources":["../../src/capture/classify.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,MAAM,MAAM,oBAAoB,CAAC;AACxC,OAAO,EAAE,iBAAiB,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAE/D,IAAI,CAAC,qDAAqD,EAAE,GAAG,EAAE;IAC/D,MAAM,CAAC,EAAE,CAAC,iBAAiB,CAAC,kBAAkB,CAAC,CAAC,CAAC;IACjD,MAAM,CAAC,EAAE,CAAC,iBAAiB,CAAC,gBAAgB,CAAC,CAAC,CAAC;IAC/C,MAAM,CAAC,EAAE,CAAC,iBAAiB,CAAC,oBAAoB,CAAC,CAAC,CAAC;AACrD,CAAC,CAAC,CAAC;AAEH,IAAI,CAAC,gCAAgC,EAAE,GAAG,EAAE;IAC1C,MAAM,CAAC,EAAE,CAAC,iBAAiB,CAAC,sBAAsB,CAAC,CAAC,CAAC;IACrD,MAAM,CAAC,EAAE,CAAC,iBAAiB,CAAC,yBAAyB,CAAC,CAAC,CAAC;AAC1D,CAAC,CAAC,CAAC;AAEH,IAAI,CAAC,
|
|
1
|
+
{"version":3,"file":"classify.test.js","sourceRoot":"","sources":["../../src/capture/classify.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,MAAM,MAAM,oBAAoB,CAAC;AACxC,OAAO,EAAE,iBAAiB,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAE/D,IAAI,CAAC,qDAAqD,EAAE,GAAG,EAAE;IAC/D,MAAM,CAAC,EAAE,CAAC,iBAAiB,CAAC,kBAAkB,CAAC,CAAC,CAAC;IACjD,MAAM,CAAC,EAAE,CAAC,iBAAiB,CAAC,gBAAgB,CAAC,CAAC,CAAC;IAC/C,MAAM,CAAC,EAAE,CAAC,iBAAiB,CAAC,oBAAoB,CAAC,CAAC,CAAC;AACrD,CAAC,CAAC,CAAC;AAEH,IAAI,CAAC,gCAAgC,EAAE,GAAG,EAAE;IAC1C,MAAM,CAAC,EAAE,CAAC,iBAAiB,CAAC,sBAAsB,CAAC,CAAC,CAAC;IACrD,MAAM,CAAC,EAAE,CAAC,iBAAiB,CAAC,yBAAyB,CAAC,CAAC,CAAC;AAC1D,CAAC,CAAC,CAAC;AAEH,IAAI,CAAC,qDAAqD,EAAE,GAAG,EAAE;IAC/D,iGAAiG;IACjG,uDAAuD;IACvD,MAAM,CAAC,EAAE,CAAC,CAAC,iBAAiB,CAAC,eAAe,EAAE,kCAAkC,CAAC,CAAC,CAAC;IACnF,MAAM,CAAC,EAAE,CAAC,CAAC,iBAAiB,CAAC,aAAa,EAAE,2BAA2B,CAAC,CAAC,CAAC;AAC5E,CAAC,CAAC,CAAC;AAEH,IAAI,CAAC,yCAAyC,EAAE,GAAG,EAAE;IACnD,MAAM,CAAC,EAAE,CAAC,iBAAiB,CAAC,sBAAsB,EAAE,iCAAiC,CAAC,CAAC,CAAC;AAC1F,CAAC,CAAC,CAAC;AAEH,IAAI,CAAC,+CAA+C,EAAE,GAAG,EAAE;IACzD,MAAM,CAAC,EAAE,CAAC,CAAC,iBAAiB,CAAC,yBAAyB,EAAE,kBAAkB,CAAC,CAAC,CAAC;AAC/E,CAAC,CAAC,CAAC;AAEH,IAAI,CAAC,gDAAgD,EAAE,GAAG,EAAE;IAC1D,MAAM,CAAC,KAAK,CAAC,WAAW,CAAC,EAAE,kBAAkB,EAAE,IAAI,EAAE,YAAY,EAAE,IAAI,EAAE,CAAC,EAAE,QAAQ,CAAC,CAAC;IACtF,MAAM,CAAC,KAAK,CAAC,WAAW,CAAC,EAAE,kBAAkB,EAAE,KAAK,EAAE,YAAY,EAAE,IAAI,EAAE,CAAC,EAAE,OAAO,CAAC,CAAC;IACtF,MAAM,CAAC,KAAK,CAAC,WAAW,CAAC,EAAE,kBAAkB,EAAE,KAAK,EAAE,YAAY,EAAE,KAAK,EAAE,CAAC,EAAE,QAAQ,CAAC,CAAC;AAC1F,CAAC,CAAC,CAAC"}
|
package/dist/capture/diff.d.ts
CHANGED
|
@@ -1,2 +1,4 @@
|
|
|
1
1
|
/** Changed files (tracked vs HEAD + untracked), read-only. Source never leaves the machine. */
|
|
2
2
|
export declare function changedFiles(cwd: string): string[];
|
|
3
|
+
/** Every tracked file in the repo (+ untracked, minus gitignored), read-only. The full-repo walk `lockstep scan` runs over. */
|
|
4
|
+
export declare function trackedFiles(cwd: string): string[];
|