@esneiderbravo/speclaw 1.0.0 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +70 -56
- package/dist/cli/commands/cortex.js +49 -0
- package/dist/cli/commands/init.js +21 -15
- package/dist/cli/commands/lawbook.js +34 -1
- package/dist/cli/commands/update.js +25 -2
- package/dist/cli/index.js +10 -1
- package/dist/modules/cortex/brief.js +75 -0
- package/dist/modules/cortex/harness.js +270 -0
- package/dist/modules/cortex/register.js +49 -0
- package/dist/modules/foundation/assets/AGENTS.template.md +13 -6
- package/dist/modules/foundation/assets/CLAUDE.template.md +15 -11
- package/dist/modules/foundation/assets/docs/standards/lawbook.template.md +24 -37
- package/dist/modules/foundation/assets/laws/laws-manifest.json +16 -7
- package/dist/modules/foundation/assets/workflows/speclaw.yml +1 -1
- package/dist/modules/foundation/compile-laws.js +5 -7
- package/dist/modules/foundation/context-budget.js +2 -0
- package/dist/modules/foundation/graph.js +7 -3
- package/dist/modules/foundation/laws.js +30 -32
- package/dist/modules/foundation/scaffold.js +9 -6
- package/dist/modules/foundation/seed-laws.js +263 -0
- package/dist/modules/foundation/setup-tool.js +3 -2
- package/dist/modules/foundation/verify.js +2 -2
- package/dist/modules/lawbook/assets/agents/archiver.md +19 -0
- package/dist/modules/lawbook/assets/agents/explorer.md +24 -0
- package/dist/modules/lawbook/assets/agents/implementer.md +20 -0
- package/dist/modules/lawbook/assets/agents/planner.md +27 -0
- package/dist/modules/lawbook/assets/agents/reviewer.md +25 -0
- package/dist/modules/lawbook/assets/agents/tester.md +21 -0
- package/dist/modules/lawbook/assets/commands/cortex.md +11 -0
- package/dist/modules/lawbook/assets/rules/spec-tasks-mandatory-steps.md +11 -10
- package/dist/modules/lawbook/assets/skills/build/steps/03-implement.md +1 -1
- package/dist/modules/lawbook/assets/skills/build/steps/04-hand-off.md +17 -0
- package/dist/modules/lawbook/assets/skills/cortex/SKILL.md +13 -0
- package/dist/modules/lawbook/assets/skills/cortex/steps/01-load-or-start.md +25 -0
- package/dist/modules/lawbook/assets/skills/cortex/steps/02-dispatch-loop.md +19 -0
- package/dist/modules/lawbook/assets/skills/cortex/steps/03-complete.md +10 -0
- package/dist/modules/lawbook/assets/skills/test/SKILL.md +11 -0
- package/dist/modules/lawbook/assets/skills/{build/steps/04-quality-gates.md → test/steps/01-quality-gates.md} +1 -1
- package/dist/modules/lawbook/assets/skills/{build/steps/05-manual-verification.md → test/steps/02-manual-verification.md} +1 -1
- package/dist/modules/lawbook/assets/skills/{build/steps/06-discipline-reports.md → test/steps/03-discipline-reports.md} +5 -2
- package/dist/modules/lawbook/change-tool.js +22 -0
- package/dist/modules/lawbook/engine.js +4 -0
- package/dist/modules/lawbook/levels.js +9 -3
- package/dist/modules/lawbook/register.js +7 -0
- package/dist/modules/tools/assets/packs.json +1 -6
- package/dist/server.js +4 -1
- package/dist/shared/agents.js +3 -3
- package/dist/shared/exposure.js +2 -2
- package/dist/shared/tool-catalog.js +3 -2
- package/package.json +2 -2
- package/dist/modules/lawbook/assets/skills/build/steps/07-hand-off.md +0 -9
- package/dist/modules/tools/assets/packs/agents/backend-developer.md +0 -61
- package/dist/modules/tools/assets/packs/agents/frontend-developer.md +0 -62
- package/dist/modules/tools/assets/packs/agents/product-strategy-analyst.md +0 -56
|
@@ -1,13 +1,7 @@
|
|
|
1
1
|
import fs from "node:fs";
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
import { z } from "zod";
|
|
4
|
-
import {
|
|
5
|
-
// The machine-readable law model and its manifest. This is the contract seam
|
|
6
|
-
// (`.speclaw/laws-manifest.json`) between where laws come from and how they are
|
|
7
|
-
// enforced: check-dispatcher owns the schema and the single `path` verification
|
|
8
|
-
// backend; executable-laws extends the same model with `ast`/`deps`/`process`
|
|
9
|
-
// backends by filling in more `verification.kind` cases — it never rewrites it.
|
|
10
|
-
const ASSETS = assetsDir(import.meta.url);
|
|
4
|
+
import { adaptedLaws, catalogLaws, mergeAdaptedSeed } from "./seed-laws.js";
|
|
11
5
|
const depsRuleSchema = z.object({
|
|
12
6
|
name: z.string().optional(),
|
|
13
7
|
from: z.string(),
|
|
@@ -144,46 +138,50 @@ export function writeLawManifest(projectPath, manifest) {
|
|
|
144
138
|
fs.writeFileSync(p, JSON.stringify(validated, null, 2) + "\n");
|
|
145
139
|
}
|
|
146
140
|
/**
|
|
147
|
-
* The
|
|
148
|
-
*
|
|
149
|
-
*
|
|
150
|
-
* laws are authored in `docs/standards/*` and compiled here instead. Laws whose
|
|
151
|
-
* scope does not match a given repo are simply inert there.
|
|
141
|
+
* The full shipped catalog as persistable laws (adapter metadata stripped).
|
|
142
|
+
* Prefer {@link seedManifestFor} at init/verify time so consumer repos do not
|
|
143
|
+
* inherit speclaw-dogfood paths that do not exist there.
|
|
152
144
|
*
|
|
153
|
-
* @returns The validated
|
|
154
|
-
* @throws If the
|
|
145
|
+
* @returns The validated catalog.
|
|
146
|
+
* @throws If the catalog fails the law schema.
|
|
155
147
|
*/
|
|
156
148
|
export function seedManifest() {
|
|
157
|
-
|
|
158
|
-
|
|
149
|
+
return manifestSchema.parse({ version: 1, laws: catalogLaws() });
|
|
150
|
+
}
|
|
151
|
+
/**
|
|
152
|
+
* Catalog laws adapted to `projectPath`: only laws whose `requires` paths exist,
|
|
153
|
+
* with the cycle law scoped to detected source roots.
|
|
154
|
+
*
|
|
155
|
+
* @param projectPath - Project root whose tree is inspected.
|
|
156
|
+
* @returns The validated adapted manifest.
|
|
157
|
+
*/
|
|
158
|
+
export function seedManifestFor(projectPath) {
|
|
159
|
+
// Covers: req~adapt-seed-to-repo~1
|
|
160
|
+
return manifestSchema.parse({ version: 1, laws: adaptedLaws(projectPath) });
|
|
159
161
|
}
|
|
160
162
|
/**
|
|
161
163
|
* The manifest the batch verifier should use: the project's file when present,
|
|
162
|
-
* otherwise the
|
|
164
|
+
* otherwise the **adapted** seed (so a clean CI clone does not silently pass,
|
|
165
|
+
* and does not evaluate inapplicable dogfood laws).
|
|
163
166
|
*
|
|
164
167
|
* @param projectPath - Project root to read from.
|
|
165
168
|
*/
|
|
166
169
|
export function loadManifestForVerify(projectPath) {
|
|
167
|
-
return readLawManifest(projectPath) ??
|
|
170
|
+
return readLawManifest(projectPath) ?? seedManifestFor(projectPath);
|
|
168
171
|
}
|
|
169
172
|
/**
|
|
170
|
-
*
|
|
171
|
-
*
|
|
172
|
-
*
|
|
173
|
+
* Merge an on-disk manifest with the adapted catalog. Existing curated entries
|
|
174
|
+
* (title or prose differs from the catalog) are never overwritten. Unmodified
|
|
175
|
+
* shipped laws whose required paths are absent are removed; unmodified cycle
|
|
176
|
+
* laws are replaced with the adapted scope.
|
|
173
177
|
*
|
|
174
178
|
* @param existing - The project's current manifest.
|
|
175
|
-
* @
|
|
179
|
+
* @param projectPath - Project root used to adapt the catalog.
|
|
180
|
+
* @returns The merged manifest and the ids that were added or removed.
|
|
176
181
|
*/
|
|
177
|
-
export function mergeSeedLaws(existing) {
|
|
178
|
-
const
|
|
179
|
-
|
|
180
|
-
const extra = seed.laws.filter((l) => !have.has(l.id));
|
|
181
|
-
if (extra.length === 0)
|
|
182
|
-
return { manifest: existing, added: [] };
|
|
183
|
-
return {
|
|
184
|
-
manifest: { ...existing, laws: [...existing.laws, ...extra] },
|
|
185
|
-
added: extra.map((l) => l.id),
|
|
186
|
-
};
|
|
182
|
+
export function mergeSeedLaws(existing, projectPath) {
|
|
183
|
+
const { manifest, added, removed } = mergeAdaptedSeed({ version: existing.version, laws: existing.laws }, projectPath);
|
|
184
|
+
return { manifest: manifestSchema.parse(manifest), added, removed };
|
|
187
185
|
}
|
|
188
186
|
// ─── Glob matching (the `path` backend) ──────────────────────────────────────
|
|
189
187
|
/**
|
|
@@ -8,7 +8,7 @@ import { installWorkflow } from "../lawbook/register.js";
|
|
|
8
8
|
import { installPack, loadPacks } from "../tools/packs.js";
|
|
9
9
|
import { readManifest, writeManifest } from "../../shared/manifest.js";
|
|
10
10
|
import { pkgVersion } from "../../shared/version.js";
|
|
11
|
-
import { mergeSeedLaws, readLawManifest,
|
|
11
|
+
import { mergeSeedLaws, readLawManifest, seedManifestFor, writeLawManifest, } from "./laws.js";
|
|
12
12
|
import { installHooks } from "./hooks.js";
|
|
13
13
|
import { compileLaws } from "./compile-laws.js";
|
|
14
14
|
import { refreshLockfile } from "./lock.js";
|
|
@@ -28,19 +28,22 @@ const FOUNDATION_DEFAULTS = {
|
|
|
28
28
|
documentation_extra: "",
|
|
29
29
|
};
|
|
30
30
|
/**
|
|
31
|
-
* Ensure the project has a law manifest. Missing → seed
|
|
32
|
-
*
|
|
31
|
+
* Ensure the project has a law manifest. Missing → adapted seed for this tree.
|
|
32
|
+
* Present → merge the adapted catalog (never overwrite a curated entry; prune
|
|
33
|
+
* unmodified dogfood laws whose required paths are absent).
|
|
33
34
|
*/
|
|
34
35
|
function ensureLawManifest(projectPath, report) {
|
|
35
36
|
const existing = readLawManifest(projectPath);
|
|
36
37
|
if (!existing) {
|
|
37
|
-
const seed =
|
|
38
|
+
const seed = seedManifestFor(projectPath);
|
|
38
39
|
writeLawManifest(projectPath, seed);
|
|
39
40
|
report.written.push(path.join(projectPath, ".speclaw", "laws-manifest.json"));
|
|
40
41
|
return seed;
|
|
41
42
|
}
|
|
42
|
-
const { manifest, added } = mergeSeedLaws(existing);
|
|
43
|
-
if (added.length > 0
|
|
43
|
+
const { manifest, added, removed } = mergeSeedLaws(existing, projectPath);
|
|
44
|
+
if (added.length > 0 ||
|
|
45
|
+
removed.length > 0 ||
|
|
46
|
+
JSON.stringify(manifest.laws) !== JSON.stringify(existing.laws)) {
|
|
44
47
|
writeLawManifest(projectPath, manifest);
|
|
45
48
|
report.written.push(path.join(projectPath, ".speclaw", "laws-manifest.json"));
|
|
46
49
|
}
|
|
@@ -0,0 +1,263 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { assetsDir } from "../../shared/paths.js";
|
|
4
|
+
// Catalog adapter: the shipped JSON is speclaw's own architecture plus a few
|
|
5
|
+
// portable laws. Consumer repos get only what their tree can actually host —
|
|
6
|
+
// never compass/foundation/ATTRIBUTION rules copied from this package.
|
|
7
|
+
//
|
|
8
|
+
// Types are declared here (not imported from laws.ts) so the adapter and the
|
|
9
|
+
// schema do not form a file-level import cycle.
|
|
10
|
+
const ASSETS = assetsDir(import.meta.url);
|
|
11
|
+
const CATALOG_PATH = path.join(ASSETS, "laws", "laws-manifest.json");
|
|
12
|
+
/** Globs that drop test doubles from the cycle-law graph. */
|
|
13
|
+
export const TEST_SCOPE_EXCLUSIONS = [
|
|
14
|
+
"!**/*.{spec,test}.{ts,tsx,js,jsx}",
|
|
15
|
+
"!**/test/**",
|
|
16
|
+
"!**/__tests__/**",
|
|
17
|
+
];
|
|
18
|
+
let cachedCatalog = null;
|
|
19
|
+
/**
|
|
20
|
+
* Read the shipped law catalog (including adapter metadata). Cached for the
|
|
21
|
+
* process; tests that mutate the asset are not a supported path.
|
|
22
|
+
*
|
|
23
|
+
* @returns The parsed catalog.
|
|
24
|
+
* @throws If the asset is missing or not an object with a `laws` array.
|
|
25
|
+
*/
|
|
26
|
+
export function readLawCatalog() {
|
|
27
|
+
if (cachedCatalog)
|
|
28
|
+
return cachedCatalog;
|
|
29
|
+
const raw = JSON.parse(fs.readFileSync(CATALOG_PATH, "utf8"));
|
|
30
|
+
if (!raw || !Array.isArray(raw.laws)) {
|
|
31
|
+
throw new Error(`invalid law catalog at ${CATALOG_PATH}`);
|
|
32
|
+
}
|
|
33
|
+
cachedCatalog = raw;
|
|
34
|
+
return raw;
|
|
35
|
+
}
|
|
36
|
+
/** Drop adapter-only fields so the result is a persistable law. */
|
|
37
|
+
export function asLaw(entry) {
|
|
38
|
+
const law = {
|
|
39
|
+
id: entry.id,
|
|
40
|
+
title: entry.title,
|
|
41
|
+
severity: entry.severity,
|
|
42
|
+
scope: [...entry.scope],
|
|
43
|
+
prose: entry.prose,
|
|
44
|
+
verification: structuredClone(entry.verification),
|
|
45
|
+
enforcement: entry.enforcement,
|
|
46
|
+
source: { ...entry.source },
|
|
47
|
+
};
|
|
48
|
+
if (entry.rationale !== undefined)
|
|
49
|
+
law.rationale = entry.rationale;
|
|
50
|
+
if (entry.status !== undefined)
|
|
51
|
+
law.status = entry.status;
|
|
52
|
+
return law;
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Whether `req` names an existing file, directory, or glob under `projectPath`.
|
|
56
|
+
*
|
|
57
|
+
* @param projectPath - Project root.
|
|
58
|
+
* @param req - Relative path or glob (`*` / `**` in a segment).
|
|
59
|
+
*/
|
|
60
|
+
export function pathRequirementExists(projectPath, req) {
|
|
61
|
+
const trimmed = req.replace(/^!/, "").replace(/\/\*\*$/, "");
|
|
62
|
+
return globExists(projectPath, trimmed.split("/").filter(Boolean));
|
|
63
|
+
}
|
|
64
|
+
function globExists(dir, parts) {
|
|
65
|
+
if (parts.length === 0)
|
|
66
|
+
return fs.existsSync(dir);
|
|
67
|
+
if (!fs.existsSync(dir))
|
|
68
|
+
return false;
|
|
69
|
+
const head = parts[0];
|
|
70
|
+
const rest = parts.slice(1);
|
|
71
|
+
if (head === "**") {
|
|
72
|
+
if (globExists(dir, rest))
|
|
73
|
+
return true;
|
|
74
|
+
let st;
|
|
75
|
+
try {
|
|
76
|
+
st = fs.statSync(dir);
|
|
77
|
+
}
|
|
78
|
+
catch {
|
|
79
|
+
return false;
|
|
80
|
+
}
|
|
81
|
+
if (!st.isDirectory())
|
|
82
|
+
return false;
|
|
83
|
+
for (const name of fs.readdirSync(dir)) {
|
|
84
|
+
if (name.startsWith("."))
|
|
85
|
+
continue;
|
|
86
|
+
if (globExists(path.join(dir, name), parts))
|
|
87
|
+
return true;
|
|
88
|
+
}
|
|
89
|
+
return false;
|
|
90
|
+
}
|
|
91
|
+
if (head === "*") {
|
|
92
|
+
let st;
|
|
93
|
+
try {
|
|
94
|
+
st = fs.statSync(dir);
|
|
95
|
+
}
|
|
96
|
+
catch {
|
|
97
|
+
return false;
|
|
98
|
+
}
|
|
99
|
+
if (!st.isDirectory())
|
|
100
|
+
return false;
|
|
101
|
+
for (const name of fs.readdirSync(dir)) {
|
|
102
|
+
if (name.startsWith("."))
|
|
103
|
+
continue;
|
|
104
|
+
if (globExists(path.join(dir, name), rest))
|
|
105
|
+
return true;
|
|
106
|
+
}
|
|
107
|
+
return false;
|
|
108
|
+
}
|
|
109
|
+
return globExists(path.join(dir, head), rest);
|
|
110
|
+
}
|
|
111
|
+
function childSrcExists(projectPath, parent) {
|
|
112
|
+
const dir = path.join(projectPath, parent);
|
|
113
|
+
try {
|
|
114
|
+
if (!fs.statSync(dir).isDirectory())
|
|
115
|
+
return false;
|
|
116
|
+
}
|
|
117
|
+
catch {
|
|
118
|
+
return false;
|
|
119
|
+
}
|
|
120
|
+
for (const name of fs.readdirSync(dir)) {
|
|
121
|
+
if (name.startsWith("."))
|
|
122
|
+
continue;
|
|
123
|
+
try {
|
|
124
|
+
if (fs.statSync(path.join(dir, name, "src")).isDirectory())
|
|
125
|
+
return true;
|
|
126
|
+
}
|
|
127
|
+
catch {
|
|
128
|
+
/* skip */
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
return false;
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* Source-root globs inferred from a repository layout.
|
|
135
|
+
*
|
|
136
|
+
* @param projectPath - Project root.
|
|
137
|
+
* @returns Globs covering app/package `src` trees, plus top-level `src/` or `lib/`, when they exist.
|
|
138
|
+
*/
|
|
139
|
+
export function detectSourceRootGlobs(projectPath) {
|
|
140
|
+
const out = [];
|
|
141
|
+
if (childSrcExists(projectPath, "apps"))
|
|
142
|
+
out.push("apps/*/src/**");
|
|
143
|
+
if (childSrcExists(projectPath, "packages"))
|
|
144
|
+
out.push("packages/*/src/**");
|
|
145
|
+
if (pathRequirementExists(projectPath, "src"))
|
|
146
|
+
out.push("src/**");
|
|
147
|
+
if (pathRequirementExists(projectPath, "lib"))
|
|
148
|
+
out.push("lib/**");
|
|
149
|
+
return out;
|
|
150
|
+
}
|
|
151
|
+
function positivesSatisfied(projectPath, scope) {
|
|
152
|
+
const positives = scope.filter((g) => !g.startsWith("!"));
|
|
153
|
+
if (positives.length === 0)
|
|
154
|
+
return true;
|
|
155
|
+
return positives.some((g) => pathRequirementExists(projectPath, g));
|
|
156
|
+
}
|
|
157
|
+
function withTestExclusions(scope) {
|
|
158
|
+
const have = new Set(scope);
|
|
159
|
+
const out = [...scope];
|
|
160
|
+
for (const g of TEST_SCOPE_EXCLUSIONS) {
|
|
161
|
+
if (!have.has(g))
|
|
162
|
+
out.push(g);
|
|
163
|
+
}
|
|
164
|
+
return out;
|
|
165
|
+
}
|
|
166
|
+
function adaptCycleLaw(projectPath, entry) {
|
|
167
|
+
const law = asLaw(entry);
|
|
168
|
+
const catalogHits = positivesSatisfied(projectPath, entry.scope);
|
|
169
|
+
const roots = catalogHits
|
|
170
|
+
? entry.scope.filter((g) => !g.startsWith("!"))
|
|
171
|
+
: detectSourceRootGlobs(projectPath);
|
|
172
|
+
if (roots.length === 0)
|
|
173
|
+
return null;
|
|
174
|
+
law.scope = withTestExclusions(roots);
|
|
175
|
+
if (law.verification.kind === "graph") {
|
|
176
|
+
law.verification = {
|
|
177
|
+
kind: "graph",
|
|
178
|
+
rule: { ...(law.verification.rule ?? {}), circular: true, edgeKinds: ["import"] },
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
return law;
|
|
182
|
+
}
|
|
183
|
+
function isApplicable(projectPath, entry) {
|
|
184
|
+
const reqs = entry.requires ?? [];
|
|
185
|
+
return reqs.every((r) => pathRequirementExists(projectPath, r));
|
|
186
|
+
}
|
|
187
|
+
/**
|
|
188
|
+
* Catalog laws stripped of adapter metadata — the full shipped set, unfiltered.
|
|
189
|
+
*
|
|
190
|
+
* @returns Persistable laws in catalog order.
|
|
191
|
+
*/
|
|
192
|
+
export function catalogLaws() {
|
|
193
|
+
return readLawCatalog().laws.map(asLaw);
|
|
194
|
+
}
|
|
195
|
+
/**
|
|
196
|
+
* Laws from the catalog that apply to `projectPath`, with cycle-law scope
|
|
197
|
+
* rewritten to the repo's real source roots when needed.
|
|
198
|
+
*
|
|
199
|
+
* @param projectPath - Project root whose tree is inspected.
|
|
200
|
+
* @returns Persistable laws; adapter fields omitted.
|
|
201
|
+
*/
|
|
202
|
+
export function adaptedLaws(projectPath) {
|
|
203
|
+
// Covers: req~adapt-seed-to-repo~1
|
|
204
|
+
const out = [];
|
|
205
|
+
for (const entry of readLawCatalog().laws) {
|
|
206
|
+
if (!isApplicable(projectPath, entry))
|
|
207
|
+
continue;
|
|
208
|
+
if (entry.adaptScope === "source-roots") {
|
|
209
|
+
const adapted = adaptCycleLaw(projectPath, entry);
|
|
210
|
+
if (adapted)
|
|
211
|
+
out.push(adapted);
|
|
212
|
+
continue;
|
|
213
|
+
}
|
|
214
|
+
out.push(asLaw(entry));
|
|
215
|
+
}
|
|
216
|
+
return out;
|
|
217
|
+
}
|
|
218
|
+
/** True when `existing` is still the shipped catalog text (title + prose). */
|
|
219
|
+
export function isUnmodifiedCatalogLaw(existing, catalog) {
|
|
220
|
+
return existing.title === catalog.title && existing.prose === catalog.prose;
|
|
221
|
+
}
|
|
222
|
+
/**
|
|
223
|
+
* Merge an on-disk manifest with the adapted catalog: append applicable ids,
|
|
224
|
+
* rewrite unmodified cycle-law scope, drop unmodified inapplicable dogfood.
|
|
225
|
+
*
|
|
226
|
+
* @param existing - The project's current manifest.
|
|
227
|
+
* @param projectPath - Project root used to adapt the catalog.
|
|
228
|
+
* @returns The merged manifest and the ids that were added or removed.
|
|
229
|
+
*/
|
|
230
|
+
export function mergeAdaptedSeed(existing, projectPath) {
|
|
231
|
+
const catalog = readLawCatalog();
|
|
232
|
+
const byId = new Map(catalog.laws.map((l) => [l.id, l]));
|
|
233
|
+
const adapted = adaptedLaws(projectPath);
|
|
234
|
+
const adaptedById = new Map(adapted.map((l) => [l.id, l]));
|
|
235
|
+
const added = [];
|
|
236
|
+
const removed = [];
|
|
237
|
+
const result = [];
|
|
238
|
+
const seen = new Set();
|
|
239
|
+
for (const law of existing.laws) {
|
|
240
|
+
const cat = byId.get(law.id);
|
|
241
|
+
if (!cat || !isUnmodifiedCatalogLaw(law, cat)) {
|
|
242
|
+
result.push(law);
|
|
243
|
+
seen.add(law.id);
|
|
244
|
+
continue;
|
|
245
|
+
}
|
|
246
|
+
const next = adaptedById.get(law.id);
|
|
247
|
+
if (next) {
|
|
248
|
+
result.push(next);
|
|
249
|
+
seen.add(law.id);
|
|
250
|
+
}
|
|
251
|
+
else {
|
|
252
|
+
removed.push(law.id);
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
for (const law of adapted) {
|
|
256
|
+
if (seen.has(law.id))
|
|
257
|
+
continue;
|
|
258
|
+
result.push(law);
|
|
259
|
+
added.push(law.id);
|
|
260
|
+
seen.add(law.id);
|
|
261
|
+
}
|
|
262
|
+
return { manifest: { ...existing, laws: result }, added, removed };
|
|
263
|
+
}
|
|
@@ -64,12 +64,13 @@ export function handleSpeclawSetup(args) {
|
|
|
64
64
|
return {
|
|
65
65
|
instructions: [
|
|
66
66
|
"1. Analyze the repository at projectPath and fill profile fields from the real codebase.",
|
|
67
|
-
"2. Call speclaw_setup with action configure-agent
|
|
67
|
+
"2. Call speclaw_setup with action configure-agent as needed (packs catalog may be empty).",
|
|
68
68
|
"3. Run lawbook_change action init and compass_index when scaffold completes via CLI if needed.",
|
|
69
|
+
"4. Non-trivial work uses Cortex: cortex skill / speclaw cortex (MCP tool cortex).",
|
|
69
70
|
],
|
|
70
71
|
profileFields: profileFieldHelp,
|
|
71
72
|
packs: loadPacks(),
|
|
72
|
-
note: "Full scaffold runs via CLI: speclaw init — not MCP.",
|
|
73
|
+
note: "Full scaffold runs via CLI: speclaw init — not MCP. Role agents install with the lawbook workflow.",
|
|
73
74
|
};
|
|
74
75
|
case "configure-agent": {
|
|
75
76
|
if (!args.agent)
|
|
@@ -17,8 +17,8 @@ export { underPaths } from "./verify-model.js";
|
|
|
17
17
|
*
|
|
18
18
|
* When the project has no index, every selected batch law is reported as
|
|
19
19
|
* `skipped` with reason `no-index` (never silently passed). When the gitignored
|
|
20
|
-
* manifest file is missing, the
|
|
21
|
-
* report an empty pass. Each evaluated law lands in exactly one of `passed` /
|
|
20
|
+
* manifest file is missing, the adapted seed is used so a clean clone does not
|
|
21
|
+
* report an empty pass or inherit inapplicable dogfood laws. Each evaluated law lands in exactly one of `passed` /
|
|
22
22
|
* `failed` / `unknown`: it fails when the engine produced a finding, is
|
|
23
23
|
* `unknown` when it produced none but rests on unresolved edges (which could
|
|
24
24
|
* hide a violation), and passes otherwise.
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: archiver
|
|
3
|
+
description: Reconcile specs, sync, and archive a change when Cortex stage is archiving. No new feature implementation.
|
|
4
|
+
tools: Read, Grep, Glob, Write, Edit, Bash, CallMcpTool, mcp__speclaw__compass_explore, mcp__speclaw__lawbook_change, mcp__speclaw__lawbook_validate, mcp__speclaw__lawbook_sync, mcp__speclaw__lawbook_archive, mcp__speclaw__cortex
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
You are the **archiver** role in speclaw's **Cortex** (One brain. Many agents.).
|
|
8
|
+
|
|
9
|
+
## Goal
|
|
10
|
+
|
|
11
|
+
Follow the `sync` then `archive` skills: reconcile delta specs to built code,
|
|
12
|
+
`lawbook_change` action sync when needed, then archive. Advance Cortex to
|
|
13
|
+
`done` after a successful archive.
|
|
14
|
+
|
|
15
|
+
## Hard constraints
|
|
16
|
+
|
|
17
|
+
- Do **not** start new feature work outside reconciliation.
|
|
18
|
+
- If archive is gated, report blockers to the coordinator — do not bypass the
|
|
19
|
+
engine.
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: explorer
|
|
3
|
+
description: Read-only codebase investigation for a lawbook change. Use when the Cortex stage is exploring (or investigate for bugs). Compass-first; writes nothing under lawbook/ or src/.
|
|
4
|
+
tools: Read, Grep, Glob, CallMcpTool, mcp__speclaw__compass_explore, mcp__speclaw__compass_find, mcp__speclaw__compass_impact, mcp__speclaw__compass_trace, mcp__speclaw__compass_index, mcp__speclaw__lawbook_investigate
|
|
5
|
+
disallowedTools: Write, Edit, MultiEdit, Bash, NotebookEdit
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
You are the **explorer** role in speclaw's **Cortex** (One brain. Many agents.).
|
|
9
|
+
|
|
10
|
+
## Goal
|
|
11
|
+
|
|
12
|
+
Investigate the idea or bug with Compass **before** any grep/read. Produce a
|
|
13
|
+
short brief for the planner: relevant symbols, blast radius, risks, and open
|
|
14
|
+
questions. Do **not** draft change artifacts or edit source.
|
|
15
|
+
|
|
16
|
+
## Skill
|
|
17
|
+
|
|
18
|
+
Follow the `explore` skill (or `investigate` when `changeType` is bug).
|
|
19
|
+
|
|
20
|
+
## Hard constraints
|
|
21
|
+
|
|
22
|
+
- No Write / StrReplace / git writes / draft / archive / Cortex advance to
|
|
23
|
+
implementing.
|
|
24
|
+
- When finished, return the brief to the coordinator and stop.
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: implementer
|
|
3
|
+
description: Implement drafted lawbook tasks in source and tests. Use when Cortex stage is implementing. Does not run final quality gates, review, sync, or archive.
|
|
4
|
+
tools: Read, Grep, Glob, Write, Edit, Bash, CallMcpTool, mcp__speclaw__compass_explore, mcp__speclaw__compass_find, mcp__speclaw__compass_impact, mcp__speclaw__compass_index, mcp__speclaw__lawbook_change, mcp__speclaw__cortex
|
|
5
|
+
disallowedTools: NotebookEdit
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
You are the **implementer** role in speclaw's **Cortex** (One brain. Many agents.).
|
|
9
|
+
|
|
10
|
+
## Goal
|
|
11
|
+
|
|
12
|
+
Work `tasks.md` in order using the `build` skill through implementation only.
|
|
13
|
+
Keep code and delta specs in agreement. Check off completed tasks.
|
|
14
|
+
|
|
15
|
+
## Hard constraints
|
|
16
|
+
|
|
17
|
+
- Do **not** archive, push, or `laws accept`.
|
|
18
|
+
- Do **not** skip the reviewer by "quietly fixing" after a FAIL — wait for
|
|
19
|
+
Cortex `rework` from the coordinator.
|
|
20
|
+
- Stop after implementation hand-off; tester owns gates and discipline reports.
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: planner
|
|
3
|
+
description: Draft lawbook change artifacts and ceremony level. Use when Cortex stage is planning. Asks clarifying questions via the coordinator; never edits src/.
|
|
4
|
+
tools: Read, Grep, Glob, Write, Edit, CallMcpTool, mcp__speclaw__compass_explore, mcp__speclaw__compass_find, mcp__speclaw__lawbook_change, mcp__speclaw__lawbook_level
|
|
5
|
+
disallowedTools: Bash, NotebookEdit
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
You are the **planner** role in speclaw's **Cortex** (One brain. Many agents.).
|
|
9
|
+
|
|
10
|
+
## Goal
|
|
11
|
+
|
|
12
|
+
Turn the explorer brief + user intent into a reviewable change under
|
|
13
|
+
`lawbook/changes/<name>/` at the confirmed ceremony level. Prefer `draft` /
|
|
14
|
+
`quick` skills.
|
|
15
|
+
|
|
16
|
+
## Questions
|
|
17
|
+
|
|
18
|
+
If anything material is ambiguous, **stop**. Return numbered questions to the
|
|
19
|
+
coordinator (do not invent answers). The coordinator advances Cortex with
|
|
20
|
+
`pauseForQuestions`. After human answers, continue drafting.
|
|
21
|
+
|
|
22
|
+
## Hard constraints
|
|
23
|
+
|
|
24
|
+
- May write only under `lawbook/changes/<name>/` (and related lawbook paths).
|
|
25
|
+
- Do **not** edit `src/`, run archive, or implement code.
|
|
26
|
+
- When the draft is complete, tell the coordinator to `cortex advance` toward
|
|
27
|
+
implementing.
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: reviewer
|
|
3
|
+
description: Deep review of implemented change without patching code. Writes reports/review.md with PASS or FAIL. Use when Cortex stage is reviewing.
|
|
4
|
+
tools: Read, Grep, Glob, Write, CallMcpTool, mcp__speclaw__compass_explore, mcp__speclaw__compass_find, mcp__speclaw__compass_impact, mcp__speclaw__compass_trace, mcp__speclaw__lawbook_change, mcp__speclaw__cortex
|
|
5
|
+
disallowedTools: Edit, MultiEdit, Bash, NotebookEdit
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
You are the **reviewer** role in speclaw's **Cortex** (One brain. Many agents.).
|
|
9
|
+
|
|
10
|
+
## Goal
|
|
11
|
+
|
|
12
|
+
Review the branch/diff against proposal, tasks, delta specs, and
|
|
13
|
+
`docs/standards/*`. Write `lawbook/changes/<name>/reports/review.md` with:
|
|
14
|
+
|
|
15
|
+
- Verdict: `PASS` or `FAIL`
|
|
16
|
+
- Findings anchored to file paths / symbols
|
|
17
|
+
- Rework guidance if FAIL
|
|
18
|
+
|
|
19
|
+
You may create/overwrite **only** `reports/review.md` (and read everything else).
|
|
20
|
+
|
|
21
|
+
## Hard constraints
|
|
22
|
+
|
|
23
|
+
- Do **not** patch application source to "make it pass".
|
|
24
|
+
- Return the verdict to the coordinator for `cortex advance` (PASS) or
|
|
25
|
+
`cortex rework` (FAIL).
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: tester
|
|
3
|
+
description: Run quality gates and manual verification; write discipline reports. Use when Cortex stage is testing. May add missing tests only — not features.
|
|
4
|
+
tools: Read, Grep, Glob, Write, Edit, Bash, CallMcpTool, mcp__speclaw__compass_explore, mcp__speclaw__compass_affected_tests, mcp__speclaw__lawbook_change, mcp__speclaw__cortex
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
You are the **tester** role in speclaw's **Cortex** (One brain. Many agents.).
|
|
8
|
+
|
|
9
|
+
## Goal
|
|
10
|
+
|
|
11
|
+
Follow the `test` skill: quality gates, manual verification, discipline
|
|
12
|
+
reports. Record verdict PASS or FAIL for the coordinator.
|
|
13
|
+
|
|
14
|
+
You MAY add or fix **tests** that are missing for covered requirements. You
|
|
15
|
+
MUST NOT implement new product features.
|
|
16
|
+
|
|
17
|
+
## Hard constraints
|
|
18
|
+
|
|
19
|
+
- Do not archive or sync.
|
|
20
|
+
- On FAIL, return findings for Cortex `rework`; on PASS, coordinator advances
|
|
21
|
+
to archiving.
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Coordinate Cortex (One brain. Many agents.) for a lawbook change until archive.
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
Cortex the change: $ARGUMENTS
|
|
6
|
+
|
|
7
|
+
Follow the `cortex` skill. You are the coordinator — spawn or adopt
|
|
8
|
+
explorer → planner → implementer → reviewer → tester → archiver. Drive state
|
|
9
|
+
with the `cortex` MCP tool (`status`/`start`/`advance`/`rework`/`brief`) or
|
|
10
|
+
`speclaw cortex …`. Surface planner questions to the human. Do not implement
|
|
11
|
+
product code yourself.
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
---
|
|
2
|
-
description: Enforce the mandatory steps from lawbook/config.yaml when creating tasks.md, and ensure the
|
|
2
|
+
description: Enforce the mandatory steps from lawbook/config.yaml when creating tasks.md, and ensure the tester role executes all manual verification itself.
|
|
3
3
|
alwaysApply: true
|
|
4
4
|
---
|
|
5
5
|
|
|
@@ -19,7 +19,7 @@ steps, branch convention, and testing/documentation requirements.
|
|
|
19
19
|
- Review and update the affected tests.
|
|
20
20
|
- Run the quality gates and verify they pass
|
|
21
21
|
(see `docs/standards/testing-standards.md`).
|
|
22
|
-
- Perform manual verification of the behavior — **the
|
|
22
|
+
- Perform manual verification of the behavior — **the tester role executes this
|
|
23
23
|
itself, never the user.**
|
|
24
24
|
- Produce the discipline reports under `reports/` — one per discipline the change
|
|
25
25
|
touched, from an open set (`backend.md`, `frontend.md`, `api.md`, `database.md`,
|
|
@@ -28,13 +28,13 @@ steps, branch convention, and testing/documentation requirements.
|
|
|
28
28
|
for what the feature touched.
|
|
29
29
|
- Update the technical documentation the change touches.
|
|
30
30
|
- Archive the change within the same PR (the `archive` command / `lawbook_archive`
|
|
31
|
-
tool).
|
|
31
|
+
tool) after Cortex review/test PASS.
|
|
32
32
|
|
|
33
|
-
## 3. Manual verification — the
|
|
33
|
+
## 3. Manual verification — the tester must execute it
|
|
34
34
|
|
|
35
|
-
|
|
36
|
-
or CLI). Never delegate it to the user. A task that
|
|
37
|
-
not complete until the
|
|
35
|
+
Under Cortex, the **tester** role performs all manual testing
|
|
36
|
+
(exercise the endpoint, UI, or CLI). Never delegate it to the user. A task that
|
|
37
|
+
requires verification is not complete until the tester has verified it.
|
|
38
38
|
|
|
39
39
|
## 4. Archiving is part of the change
|
|
40
40
|
|
|
@@ -42,6 +42,7 @@ A change is not done until it is archived with `lawbook_archive` (never a manual
|
|
|
42
42
|
`mv`). The archive lands in the same PR that implements the change.
|
|
43
43
|
|
|
44
44
|
`lawbook_archive` is gated: it refuses to archive while any task is unchecked,
|
|
45
|
-
while `reports/` has no discipline report,
|
|
46
|
-
synced into the canonical specs
|
|
47
|
-
the engine, so a manual `mv`
|
|
45
|
+
while `reports/` has no discipline report, while the delta specs are not yet
|
|
46
|
+
synced into the canonical specs, or while harness review/test verdicts are not
|
|
47
|
+
PASS. Resolve those first — the gate is enforced in the engine, so a manual `mv`
|
|
48
|
+
only hides an incomplete change.
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# Hand off to review / test
|
|
2
|
+
|
|
3
|
+
Implementation for this role stops here. Do **not** run the full quality-gate
|
|
4
|
+
suite, write discipline reports, sync, or archive — those belong to the
|
|
5
|
+
**tester** and **archiver** roles under Cortex.
|
|
6
|
+
|
|
7
|
+
Tell the coordinator:
|
|
8
|
+
|
|
9
|
+
1. Which tasks are done
|
|
10
|
+
2. Notable spec reconciliations still needed
|
|
11
|
+
3. To `cortex advance` from `implementing` (→ reviewing at level ≥1, or
|
|
12
|
+
testing at level 0)
|
|
13
|
+
|
|
14
|
+
The **tester** role follows the `test` skill for gates, manual verification,
|
|
15
|
+
and discipline reports.
|
|
16
|
+
|
|
17
|
+
No further steps remain — build workflow complete.
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: cortex
|
|
3
|
+
description: Coordinate Cortex — One brain. Many agents. Spawn or adopt explorer, planner, implementer, reviewer, tester, archiver until archive. Use for non-trivial work: "build X", "implement the change", "run cortex", "orchestrate X".
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Cortex — One brain. Many agents.
|
|
7
|
+
|
|
8
|
+
You are the **coordinator**. Do not implement product code yourself.
|
|
9
|
+
|
|
10
|
+
Drive state with the `cortex` MCP tool (`status` / `start` / `advance` /
|
|
11
|
+
`rework` / `brief`) or `speclaw cortex …`. Command: `/lawbook/cortex`.
|
|
12
|
+
|
|
13
|
+
Read `steps/01-load-or-start.md` and do only what it says.
|