@esneiderbravo/speclaw 0.1.12 → 0.1.14
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 +3 -2
- package/dist/cli/commands/update.js +53 -4
- package/dist/modules/foundation/assets/AGENTS.template.md +4 -1
- package/dist/modules/foundation/assets/CLAUDE.template.md +4 -2
- package/dist/modules/foundation/assets/docs/standards/base-standards.template.md +3 -2
- package/dist/modules/foundation/assets/docs/standards/testing-standards.template.md +36 -0
- package/dist/modules/foundation/scaffold.js +6 -2
- package/dist/modules/lawbook/assets/skills/build/SKILL.md +43 -6
- package/dist/modules/lawbook/assets/skills/draft/SKILL.md +4 -2
- package/dist/shared/install.js +16 -4
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -189,8 +189,9 @@ without a re-init, splitting files by who owns them:
|
|
|
189
189
|
|
|
190
190
|
- **Managed files** (speclaw's workflow machinery — the skills, commands, rules,
|
|
191
191
|
and agent packs under `ai-specs/`) are **refreshed** to the new version, so
|
|
192
|
-
improvements actually reach your project. If you edited one locally,
|
|
193
|
-
|
|
192
|
+
improvements actually reach your project. If you edited one locally, `update`
|
|
193
|
+
reports the overwrite so you can recover your copy from git; pass `--backup` to
|
|
194
|
+
also keep a `<file>.bak`. Any `*.bak` is gitignored.
|
|
194
195
|
- **Personalized files** (your constitution and standards — `CLAUDE.md`,
|
|
195
196
|
`AGENTS.md`, `LAWS.md`, `docs/standards/*`, `docs/compass.md`,
|
|
196
197
|
`lawbook/config.yaml`) are **never auto-edited**. When a release changes their
|
|
@@ -28,6 +28,37 @@ const MIGRATIONS = [
|
|
|
28
28
|
'the feature touched)."\n' +
|
|
29
29
|
"- Preserve all project-specific wording; only apply these speclaw-authored changes.",
|
|
30
30
|
},
|
|
31
|
+
{
|
|
32
|
+
version: "0.1.13",
|
|
33
|
+
describe: "Required report structure in the testing standard (personalized file)",
|
|
34
|
+
agentPrompt: "- In docs/standards/testing-standards.md, strengthen the 'Reports' section so a " +
|
|
35
|
+
"discipline report MUST follow a fixed structure (evidence reproducible, not improvised): " +
|
|
36
|
+
"(1) a header — discipline, change, date, branch, and the environment/working directory " +
|
|
37
|
+
"the commands ran in; (2) a gates-and-results table with each check, the exact command, " +
|
|
38
|
+
"and the real result including pass/fail counts; (3) the tests added or updated and what " +
|
|
39
|
+
"each asserts; (4) a spec-scenario coverage table mapping every '#### Scenario' in the " +
|
|
40
|
+
"change's delta specs to how it was verified (a test, a gate, or a manual step); (5) an " +
|
|
41
|
+
"honest declaration of any pre-existing or unrelated failures with proof, or 'none'; " +
|
|
42
|
+
"(6) the manual steps not automated, or 'none'; (7) a one-line verdict. Keep the existing " +
|
|
43
|
+
"'test kind does not yet apply' escape hatch. If the section is missing, add it.\n" +
|
|
44
|
+
"- Preserve all project-specific wording; only apply these speclaw-authored changes.",
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
version: "0.1.14",
|
|
48
|
+
describe: "Real-data-write stop condition in the constitution (personalized files)",
|
|
49
|
+
agentPrompt: "- Add a stop condition, at the same tier as deletes/force-push, that verification " +
|
|
50
|
+
"must NOT create/update/delete the user's real data (a production or development " +
|
|
51
|
+
"database, or files holding real data) — including setting up or tearing down test " +
|
|
52
|
+
"data or running raw store commands (e.g. direct SQL) against a live store. " +
|
|
53
|
+
"Verification runs against an isolated/throwaway store (a temporary copy, an in-memory " +
|
|
54
|
+
"database, a dedicated test store, or a rolled-back transaction); snapshot-and-restore " +
|
|
55
|
+
"is not sanctioned. If a real-store write is genuinely unavoidable, stop and get " +
|
|
56
|
+
"explicit authorization first — a backup is not a substitute.\n" +
|
|
57
|
+
"- Apply it in CLAUDE.md and AGENTS.md (the Rule 6 / stop-conditions list), in " +
|
|
58
|
+
"docs/standards/base-standards.md (the 'ask before irreversible actions' bullet), and " +
|
|
59
|
+
"in docs/standards/testing-standards.md (the 'Manual & end-to-end verification' section).\n" +
|
|
60
|
+
"- Preserve all project-specific wording; only apply these speclaw-authored changes.",
|
|
61
|
+
},
|
|
31
62
|
];
|
|
32
63
|
/**
|
|
33
64
|
* Update speclaw and bring the current project up to date without a full re-init:
|
|
@@ -42,6 +73,7 @@ export async function runUpdate(flags) {
|
|
|
42
73
|
const cwd = process.cwd();
|
|
43
74
|
const migrateOnly = Boolean(flags["migrate-only"]);
|
|
44
75
|
const checkOnly = Boolean(flags.check);
|
|
76
|
+
const backup = Boolean(flags.backup);
|
|
45
77
|
const winShell = process.platform === "win32";
|
|
46
78
|
if (!migrateOnly) {
|
|
47
79
|
ui.step("Checking for updates");
|
|
@@ -66,8 +98,10 @@ export async function runUpdate(flags) {
|
|
|
66
98
|
}
|
|
67
99
|
ui.ok(`Updated to ${latest}`);
|
|
68
100
|
// Re-exec the NEWLY installed binary so migrations run with the new assets
|
|
69
|
-
// and any new feature steps — not this (now-stale) process.
|
|
70
|
-
|
|
101
|
+
// and any new feature steps — not this (now-stale) process. Carry --backup
|
|
102
|
+
// through so the refresh honors it after the upgrade.
|
|
103
|
+
const reArgs = ["update", "--migrate-only", ...(backup ? ["--backup"] : [])];
|
|
104
|
+
const re = spawnSync("speclaw", reArgs, {
|
|
71
105
|
stdio: "inherit",
|
|
72
106
|
shell: winShell,
|
|
73
107
|
});
|
|
@@ -83,13 +117,18 @@ export async function runUpdate(flags) {
|
|
|
83
117
|
return;
|
|
84
118
|
}
|
|
85
119
|
}
|
|
86
|
-
applyProjectMigrations(cwd);
|
|
120
|
+
applyProjectMigrations(cwd, backup);
|
|
87
121
|
}
|
|
88
122
|
/**
|
|
89
123
|
* Additively apply the current version's content and feature steps to a project.
|
|
90
124
|
* No-op with a hint when the directory isn't a speclaw project.
|
|
125
|
+
*
|
|
126
|
+
* @param cwd - Project root to update.
|
|
127
|
+
* @param backup - When true, a locally edited managed file is copied to
|
|
128
|
+
* `<file>.bak` before it is refreshed; the default overwrites it in place
|
|
129
|
+
* (recoverable from git) and only reports the overwrite.
|
|
91
130
|
*/
|
|
92
|
-
function applyProjectMigrations(cwd) {
|
|
131
|
+
function applyProjectMigrations(cwd, backup) {
|
|
93
132
|
const initialized = fs.existsSync(path.join(cwd, "ai-specs")) || fs.existsSync(path.join(cwd, "LAWS.md"));
|
|
94
133
|
if (!initialized) {
|
|
95
134
|
ui.step("Project");
|
|
@@ -106,6 +145,7 @@ function applyProjectMigrations(cwd) {
|
|
|
106
145
|
// rewritten — those changes are handed to the user's agent below.
|
|
107
146
|
const report = scaffold(cwd, { project_name: detectProjectName(cwd) }, packs, agents, {
|
|
108
147
|
refreshManaged: true,
|
|
148
|
+
backup,
|
|
109
149
|
});
|
|
110
150
|
const changed = report.written.filter((w) => !w.includes(".gitignore"));
|
|
111
151
|
if (changed.length) {
|
|
@@ -116,10 +156,19 @@ function applyProjectMigrations(cwd) {
|
|
|
116
156
|
else {
|
|
117
157
|
ui.ok("Managed content already up to date — nothing to refresh.");
|
|
118
158
|
}
|
|
159
|
+
// A diverged managed file is always reported so the user can recover their
|
|
160
|
+
// edits; with --backup it is also kept as a `.bak`, otherwise it is overwritten
|
|
161
|
+
// in place (git holds the prior content).
|
|
119
162
|
for (const b of report.backedUp) {
|
|
120
163
|
const rel = path.relative(cwd, b);
|
|
121
164
|
ui.warn(`${c.cream(rel)} had local edits — saved as ${rel}.bak before refreshing.`);
|
|
122
165
|
}
|
|
166
|
+
const overwritten = report.refreshedDiverged.filter((f) => !report.backedUp.includes(f));
|
|
167
|
+
for (const f of overwritten) {
|
|
168
|
+
const rel = path.relative(cwd, f);
|
|
169
|
+
ui.warn(`${c.cream(rel)} had local edits — overwritten with the current version. ` +
|
|
170
|
+
`Recover from git, or re-run with ${ui.code("--backup")} to keep a .bak.`);
|
|
171
|
+
}
|
|
123
172
|
// A project several releases behind jumps straight to @latest, so apply EVERY
|
|
124
173
|
// migration newer than its recorded version — not just the next one — oldest
|
|
125
174
|
// first (sorted, so array order can't matter). Entries are cumulative: never
|
|
@@ -35,7 +35,10 @@ Claude-specific notes: [`CLAUDE.md`](CLAUDE.md). The law: [`LAWS.md`](LAWS.md).
|
|
|
35
35
|
[`docs/standards/conventions.md`](docs/standards/conventions.md).
|
|
36
36
|
6. **Use the skills.** `ai-specs/` is the canonical home for skills, commands,
|
|
37
37
|
and subagents, mirrored to each IDE directory via symlinks.
|
|
38
|
-
7. **Ask before irreversible or outward-facing actions
|
|
38
|
+
7. **Ask before irreversible or outward-facing actions** — destructive commands;
|
|
39
|
+
writing to a real data store (DB rows or files with real user data, including
|
|
40
|
+
for tests — verify against an isolated/throwaway store); publishing
|
|
41
|
+
reviews/tickets/comments.
|
|
39
42
|
|
|
40
43
|
## The standards (the law, in detail)
|
|
41
44
|
|
|
@@ -81,5 +81,7 @@ task, use it — do not improvise a parallel process.
|
|
|
81
81
|
## Rule 6 — Stop conditions
|
|
82
82
|
|
|
83
83
|
Stop and ask the user before: destructive operations (deletes, force-push,
|
|
84
|
-
schema drops),
|
|
85
|
-
|
|
84
|
+
schema drops), writing to a real data store (DB rows or files holding real user
|
|
85
|
+
data — including to set up or tear down test data; verification runs against an
|
|
86
|
+
isolated/throwaway store instead), publishing anything outward-facing (PR
|
|
87
|
+
reviews, tickets, comments), or any action that contradicts a standard.
|
|
@@ -45,8 +45,9 @@ bodies. Match what the repo does; do not impose a language it doesn't use.
|
|
|
45
45
|
- Code reads like its neighbors (naming, structure, idioms).
|
|
46
46
|
- Report outcomes faithfully: if a gate fails, say so with the output; never
|
|
47
47
|
claim a success you did not observe.
|
|
48
|
-
- Ask before irreversible or outward-facing actions (destructive commands
|
|
49
|
-
|
|
48
|
+
- Ask before irreversible or outward-facing actions (destructive commands;
|
|
49
|
+
writing to a real data store — DB rows or files holding real user data,
|
|
50
|
+
including to set up or tear down test data; publishing reviews/tickets/comments).
|
|
50
51
|
|
|
51
52
|
<!-- speclaw init: add any project-specific base rules the analysis surfaced. -->
|
|
52
53
|
{{base_standards_extra}}
|
|
@@ -29,6 +29,42 @@ suppressing a linter or deleting a test.
|
|
|
29
29
|
|
|
30
30
|
- When a change affects runtime behavior and it's feasible, verify it works by
|
|
31
31
|
exercising the endpoint/UI — don't assume green CI covers everything.
|
|
32
|
+
- **Verification never touches real data.** Run it against an isolated store — a
|
|
33
|
+
temporary copy, an in-memory database, a dedicated test store, or a rolled-back
|
|
34
|
+
transaction — or against pure logic with fixtures. Never create/update/delete
|
|
35
|
+
the user's real data (a production or development database, or files holding
|
|
36
|
+
real data), and never run raw store commands (e.g. direct SQL) against a live
|
|
37
|
+
store, to prove a change. Snapshot-and-restore is not sanctioned. If a
|
|
38
|
+
real-store write is genuinely unavoidable, stop and get explicit authorization
|
|
39
|
+
first — a backup is not a substitute (see the stop conditions in the
|
|
40
|
+
constitution).
|
|
32
41
|
- The mandatory spec task steps
|
|
33
42
|
([`lawbook.md`](lawbook.md)) define which manual checks
|
|
34
43
|
the agent must execute itself.
|
|
44
|
+
|
|
45
|
+
## Reports — evidence travels with the change
|
|
46
|
+
|
|
47
|
+
Every change records its testing under `lawbook/changes/<name>/reports/`, one
|
|
48
|
+
file per discipline it touched (`backend.md`, `frontend.md`, …). `build`
|
|
49
|
+
produces them; archiving is blocked until the change has at least one discipline
|
|
50
|
+
report.
|
|
51
|
+
|
|
52
|
+
Each report MUST follow a fixed structure, so the evidence is reproducible rather
|
|
53
|
+
than improvised:
|
|
54
|
+
|
|
55
|
+
1. a **header** — discipline, change, date, branch, and the environment or
|
|
56
|
+
working directory the commands ran in;
|
|
57
|
+
2. a **gates-and-results table** — each check, the exact command, and its real
|
|
58
|
+
result with pass/fail counts;
|
|
59
|
+
3. the **tests added or updated** and what each asserts (TDD evidence where it
|
|
60
|
+
applies);
|
|
61
|
+
4. a **spec-scenario coverage table** mapping every `#### Scenario` in the
|
|
62
|
+
change's delta specs to how it was verified (a test, a gate, or a manual step);
|
|
63
|
+
5. an honest declaration of any **pre-existing or unrelated failures** with proof
|
|
64
|
+
they are not caused by the change — or "none";
|
|
65
|
+
6. the **manual steps not automated** — or "none";
|
|
66
|
+
7. a one-line **verdict**.
|
|
67
|
+
|
|
68
|
+
When a test kind does not yet apply (e.g. no unit runner), the report says so in
|
|
69
|
+
place of that evidence and records the gates and manual verification that stood
|
|
70
|
+
in.
|
|
@@ -71,8 +71,10 @@ function renderFoundation(projectPath, vars, report) {
|
|
|
71
71
|
* @param packNames - Tool pack names to install (the spec workflow is always installed).
|
|
72
72
|
* @param agents - Agent ids to configure with symlinks + MCP; empty writes content only.
|
|
73
73
|
* @param opts - `refreshManaged: true` overwrites the managed trees (skills,
|
|
74
|
-
* commands, rules, agents) with the current version
|
|
75
|
-
* `<file>.bak
|
|
74
|
+
* commands, rules, agents) with the current version; `backup: true` also keeps
|
|
75
|
+
* a `<file>.bak` of any locally edited managed file before overwriting it (the
|
|
76
|
+
* default overwrites in place — git preserves the prior content). Default
|
|
77
|
+
* (init) is additive — existing files are kept.
|
|
76
78
|
* @returns The install report augmented with the ordered next steps to run.
|
|
77
79
|
* @throws If `projectPath` does not exist, or any pack name is unknown.
|
|
78
80
|
*/
|
|
@@ -91,6 +93,7 @@ export function scaffold(projectPath, profile, packNames, agents = [], opts = {}
|
|
|
91
93
|
const record = {};
|
|
92
94
|
const managedOpts = {
|
|
93
95
|
overwrite: Boolean(opts.refreshManaged),
|
|
96
|
+
backup: Boolean(opts.backup),
|
|
94
97
|
projectPath,
|
|
95
98
|
baselines: readManifest(projectPath)?.baselines ?? {},
|
|
96
99
|
record,
|
|
@@ -100,6 +103,7 @@ export function scaffold(projectPath, profile, packNames, agents = [], opts = {}
|
|
|
100
103
|
for (const name of packNames)
|
|
101
104
|
installPack(projectPath, name, vars, report, managedOpts); // managed
|
|
102
105
|
ensureGitignore(projectPath, ".speclaw/", "speclaw local code Compass (never commit)", report);
|
|
106
|
+
ensureGitignore(projectPath, "*.bak", "speclaw managed-file refresh backups", report);
|
|
103
107
|
for (const id of agents)
|
|
104
108
|
configureAgent(projectPath, id, report); // only the chosen agents
|
|
105
109
|
// Record what was installed so `speclaw update` can re-apply these packs and
|
|
@@ -43,15 +43,52 @@ Run them yourself and report real output. A red gate blocks completion.
|
|
|
43
43
|
Exercise the behavior (endpoint/UI/CLI) yourself where feasible — do not
|
|
44
44
|
delegate manual testing to the user. Record what you verified.
|
|
45
45
|
|
|
46
|
+
**Verification is isolated by construction — it never touches real data.** Run it
|
|
47
|
+
against an ephemeral or throwaway store: a temporary copy, an in-memory database
|
|
48
|
+
(`:memory:`), a dedicated test store, or inside a transaction that is rolled
|
|
49
|
+
back — best of all, verify pure/domain logic with fixtures and no store at all.
|
|
50
|
+
Do **not** create, update, or delete the user's real data (a production or
|
|
51
|
+
development database, or files holding real data) as a side effect of proving a
|
|
52
|
+
change, and do **not** run raw store commands (e.g. direct SQL) against a live
|
|
53
|
+
store. Snapshot-and-restore is not a sanctioned method — a stray write slips past
|
|
54
|
+
the restore.
|
|
55
|
+
|
|
56
|
+
If isolation is genuinely impossible and a real-store write is unavoidable,
|
|
57
|
+
**stop and ask first** — state exactly what you will write and to which store —
|
|
58
|
+
and proceed only after explicit authorization. A backup is not a substitute for
|
|
59
|
+
authorization. Record in the report how verification stayed isolated (or the
|
|
60
|
+
authorization you obtained).
|
|
61
|
+
|
|
46
62
|
## Step 5 — Write the discipline reports (mandatory)
|
|
47
63
|
|
|
48
64
|
Record the evidence of testing under `lawbook/changes/<name>/reports/`, one file
|
|
49
|
-
per discipline the change touched (`backend.md`, `frontend.md`, …).
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
65
|
+
per discipline the change touched (`backend.md`, `frontend.md`, …). Omit
|
|
66
|
+
disciplines the change did not touch; the archive is blocked until at least one
|
|
67
|
+
discipline report exists.
|
|
68
|
+
|
|
69
|
+
Each report MUST follow this structure, in order — the fixed shape is what makes
|
|
70
|
+
the evidence trustworthy and reproducible, rather than left to improvisation:
|
|
71
|
+
|
|
72
|
+
1. **Title + header** — `# <Discipline> checks — <change> (<date>)`, then a line
|
|
73
|
+
`Date · Branch · Environment/cwd` naming where the commands ran.
|
|
74
|
+
2. **Gates & results** — a `| Check | Command | Result |` table: each gate, the
|
|
75
|
+
exact command, and its real result with pass/fail counts (e.g. "62 files, 434
|
|
76
|
+
passed") and ✅/⚠️/❌. Quote real output — never paraphrase a green you did
|
|
77
|
+
not see.
|
|
78
|
+
3. **Tests added / updated** — each new or changed test and what it asserts; note
|
|
79
|
+
TDD evidence ("failed before the fix, passes after") where it applies.
|
|
80
|
+
4. **Spec-scenario coverage** — a table mapping each `#### Scenario` in this
|
|
81
|
+
change's delta specs to how it was verified (a test id, a gate, or a manual
|
|
82
|
+
step). Every scenario must appear.
|
|
83
|
+
5. **Pre-existing / unrelated failures** — any failing check not caused by this
|
|
84
|
+
change, with proof it is pre-existing (e.g. it reproduces with the change
|
|
85
|
+
stashed) — or state "none".
|
|
86
|
+
6. **Pending manual steps** — anything not automated, stated plainly — or "none".
|
|
87
|
+
7. **Verdict** — one line.
|
|
88
|
+
|
|
89
|
+
If a test kind does not yet apply (e.g. no unit runner), the report says so in
|
|
90
|
+
place of that evidence and records the gates and manual verification that stood
|
|
91
|
+
in.
|
|
55
92
|
|
|
56
93
|
## Step 6 — Hand off
|
|
57
94
|
|
|
@@ -54,8 +54,10 @@ Create under `lawbook/changes/<name>/`:
|
|
|
54
54
|
updated; archive within the PR).
|
|
55
55
|
- **reports/** — create the folder with a short `reports/README.md` naming the
|
|
56
56
|
discipline reports (`backend.md`, `frontend.md`, … as relevant) that `build`
|
|
57
|
-
will fill
|
|
58
|
-
|
|
57
|
+
will fill, following the required report structure (header · gates table ·
|
|
58
|
+
tests added · spec-scenario coverage · pre-existing failures · pending manual ·
|
|
59
|
+
verdict — see the `build` skill, Step 5). Every change ships this folder;
|
|
60
|
+
archive is blocked until it holds at least one discipline report.
|
|
59
61
|
|
|
60
62
|
## Step 4 — Validate
|
|
61
63
|
|
package/dist/shared/install.js
CHANGED
|
@@ -4,7 +4,14 @@ import path from "node:path";
|
|
|
4
4
|
import { render } from "./render.js";
|
|
5
5
|
/** Create a fresh, empty {@link InstallReport} to accumulate results into. */
|
|
6
6
|
export function emptyReport() {
|
|
7
|
-
return {
|
|
7
|
+
return {
|
|
8
|
+
written: [],
|
|
9
|
+
skipped: [],
|
|
10
|
+
refreshedDiverged: [],
|
|
11
|
+
backedUp: [],
|
|
12
|
+
symlinks: [],
|
|
13
|
+
unresolvedVars: [],
|
|
14
|
+
};
|
|
8
15
|
}
|
|
9
16
|
/** SHA-256 of a file's intended content, used to track managed-file baselines. */
|
|
10
17
|
export function sha256(content) {
|
|
@@ -60,9 +67,14 @@ export function copyRendered(srcDir, destDir, vars, report, opts) {
|
|
|
60
67
|
}
|
|
61
68
|
const baseline = opts.baselines?.[rel];
|
|
62
69
|
if (!baseline || sha256(current) !== baseline) {
|
|
63
|
-
// Diverged from what we last wrote (or unknown)
|
|
64
|
-
|
|
65
|
-
|
|
70
|
+
// Diverged from what we last wrote (or unknown). Record it so update can
|
|
71
|
+
// report it; keep a `.bak` only when asked — git already preserves the
|
|
72
|
+
// prior content, so the backup is opt-in, not the default.
|
|
73
|
+
if (opts.backup) {
|
|
74
|
+
fs.copyFileSync(dest, dest + ".bak");
|
|
75
|
+
report.backedUp.push(dest);
|
|
76
|
+
}
|
|
77
|
+
report.refreshedDiverged.push(dest);
|
|
66
78
|
}
|
|
67
79
|
fs.writeFileSync(dest, content);
|
|
68
80
|
}
|