@esneiderbravo/speclaw 0.1.13 → 0.1.15
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/dist/cli/commands/update.js +16 -0
- package/dist/cli/commands/version.js +31 -0
- package/dist/cli/index.js +39 -1
- package/dist/cli/lib/ui.js +92 -14
- package/dist/cli/lib/update-check.js +33 -16
- 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 +9 -0
- package/dist/modules/lawbook/assets/skills/build/SKILL.md +16 -0
- package/package.json +3 -1
|
@@ -43,6 +43,22 @@ const MIGRATIONS = [
|
|
|
43
43
|
"'test kind does not yet apply' escape hatch. If the section is missing, add it.\n" +
|
|
44
44
|
"- Preserve all project-specific wording; only apply these speclaw-authored changes.",
|
|
45
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
|
+
},
|
|
46
62
|
];
|
|
47
63
|
/**
|
|
48
64
|
* Update speclaw and bring the current project up to date without a full re-init:
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { pkgVersion } from "../../shared/version.js";
|
|
2
|
+
import { checkForUpdates, upgradeNotice } from "../lib/update-check.js";
|
|
3
|
+
/**
|
|
4
|
+
* Print the locally installed speclaw version and, when a newer version is
|
|
5
|
+
* published on npm, a clickable suggestion to upgrade.
|
|
6
|
+
*
|
|
7
|
+
* The installed version (from the package's own `package.json`) always goes to
|
|
8
|
+
* **stdout** as a bare line, so it stays script- and pipe-friendly
|
|
9
|
+
* (`v=$(speclaw --version)`). The upgrade suggestion — which requires a network
|
|
10
|
+
* lookup — goes to **stderr** and only when stderr is an interactive TTY and
|
|
11
|
+
* the notifier is not disabled, so scripts and CI pay no network cost and get
|
|
12
|
+
* clean output. The lookup is forced (bypasses the daily cache) so an explicit
|
|
13
|
+
* version query reflects npm right now, and every failure is swallowed: a
|
|
14
|
+
* flaky or offline registry must never break `--version`.
|
|
15
|
+
*/
|
|
16
|
+
export async function runVersion() {
|
|
17
|
+
console.log(pkgVersion());
|
|
18
|
+
if (process.env.NO_UPDATE_NOTIFIER || process.env.SPECLAW_NO_UPDATE_NOTIFIER)
|
|
19
|
+
return;
|
|
20
|
+
if (!process.stderr.isTTY)
|
|
21
|
+
return;
|
|
22
|
+
try {
|
|
23
|
+
const { current, latest, updateAvailable } = await checkForUpdates({ force: true });
|
|
24
|
+
if (updateAvailable && latest) {
|
|
25
|
+
process.stderr.write("\n" + upgradeNotice(current, latest) + "\n\n");
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
catch {
|
|
29
|
+
/* the update check is best-effort — never let it break `--version` */
|
|
30
|
+
}
|
|
31
|
+
}
|
package/dist/cli/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { parseFlags } from "./lib/args.js";
|
|
3
|
-
import { ui } from "./lib/ui.js";
|
|
3
|
+
import { ui, header } from "./lib/ui.js";
|
|
4
4
|
import { maybeNotifyUpdate } from "./lib/update-check.js";
|
|
5
5
|
const HELP = `speclaw — spec-driven, agent-ready projects (foundation + Compass + Lawbook)
|
|
6
6
|
|
|
@@ -36,7 +36,40 @@ Other
|
|
|
36
36
|
doctor Verify the installation
|
|
37
37
|
mcp Start the MCP server (used by your agent's config)
|
|
38
38
|
help Show this help
|
|
39
|
+
--version Print the installed speclaw version
|
|
39
40
|
`;
|
|
41
|
+
// Commands that open with the one-line branded header. These are the
|
|
42
|
+
// interactive, human-facing commands whose stdout is prose. Deliberately
|
|
43
|
+
// excluded: `version`/`--version`/`-v` (bare scriptable value), the Compass
|
|
44
|
+
// query family (`explore`/`search`/`recall`/`impact`/`trace`, machine-consumed
|
|
45
|
+
// output), `mcp` (a long-running stdio server), and `init` (already opens with
|
|
46
|
+
// the fuller `banner()`).
|
|
47
|
+
const HEADER_COMMANDS = new Set([
|
|
48
|
+
undefined,
|
|
49
|
+
"help",
|
|
50
|
+
"--help",
|
|
51
|
+
"-h",
|
|
52
|
+
"update",
|
|
53
|
+
"agent",
|
|
54
|
+
"doctor",
|
|
55
|
+
"index",
|
|
56
|
+
"watch",
|
|
57
|
+
"lawbook",
|
|
58
|
+
]);
|
|
59
|
+
/**
|
|
60
|
+
* Print the branded header once, ahead of a command's output, when it is a
|
|
61
|
+
* header-eligible command AND stdout is an interactive terminal (so pipes,
|
|
62
|
+
* redirection, and CI stay clean — mirroring the color gate in `ui.ts`). A
|
|
63
|
+
* forced-color signal counts as interactive so the header is exercisable in a
|
|
64
|
+
* child process.
|
|
65
|
+
*/
|
|
66
|
+
function maybeHeader(cmd) {
|
|
67
|
+
if (!process.stdout.isTTY && process.env.FORCE_COLOR !== "1")
|
|
68
|
+
return;
|
|
69
|
+
if (!HEADER_COMMANDS.has(cmd))
|
|
70
|
+
return;
|
|
71
|
+
header();
|
|
72
|
+
}
|
|
40
73
|
/** Run the handler for a single command. Returns when the command completes. */
|
|
41
74
|
async function dispatch(cmd, flags) {
|
|
42
75
|
switch (cmd) {
|
|
@@ -46,6 +79,10 @@ async function dispatch(cmd, flags) {
|
|
|
46
79
|
case "-h":
|
|
47
80
|
console.log(HELP);
|
|
48
81
|
return;
|
|
82
|
+
case "version":
|
|
83
|
+
case "--version":
|
|
84
|
+
case "-v":
|
|
85
|
+
return (await import("./commands/version.js")).runVersion();
|
|
49
86
|
case "mcp": {
|
|
50
87
|
const { startMcpServer } = await import("../server.js");
|
|
51
88
|
await startMcpServer();
|
|
@@ -83,6 +120,7 @@ async function dispatch(cmd, flags) {
|
|
|
83
120
|
async function main() {
|
|
84
121
|
const [cmd, ...rest] = process.argv.slice(2);
|
|
85
122
|
const flags = parseFlags(rest);
|
|
123
|
+
maybeHeader(cmd);
|
|
86
124
|
await dispatch(cmd, flags);
|
|
87
125
|
await maybeNotifyUpdate(cmd);
|
|
88
126
|
}
|
package/dist/cli/lib/ui.js
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
// = the "law", cream text, muted gray, green/amber for status) rendered as
|
|
3
3
|
// 24-bit truecolor ANSI — no dependency needed. Colors auto-disable when the
|
|
4
4
|
// output is not a TTY or NO_COLOR is set.
|
|
5
|
+
import { pkgVersion } from "../../shared/version.js";
|
|
5
6
|
const PALETTE = {
|
|
6
7
|
cyan: [46, 230, 230], // #2EE6E6 — the accent / "law"
|
|
7
8
|
cyanDim: [23, 193, 193], // #17C1C1
|
|
@@ -12,6 +13,44 @@ const PALETTE = {
|
|
|
12
13
|
red: [235, 90, 90],
|
|
13
14
|
};
|
|
14
15
|
const colorOn = (Boolean(process.stdout.isTTY) || process.env.FORCE_COLOR === "1") && !process.env.NO_COLOR;
|
|
16
|
+
// Whether the terminal reliably renders the unicode box/block glyphs the brand
|
|
17
|
+
// output uses. Non-Windows terminals are assumed capable; a Windows console is
|
|
18
|
+
// trusted only under a modern-terminal signal (Windows Terminal, an embedding
|
|
19
|
+
// program like VS Code, or CI) — a legacy conhost with a non-UTF-8 code page
|
|
20
|
+
// would otherwise show mojibake. No dependency; the check runs once at load.
|
|
21
|
+
const unicodeOn = process.platform !== "win32" ||
|
|
22
|
+
Boolean(process.env.WT_SESSION || process.env.TERM_PROGRAM || process.env.CI);
|
|
23
|
+
// The brand glyph set, resolved once against terminal capability. Every branded
|
|
24
|
+
// renderer (header, banner, box, progress) draws from this so unicode and ASCII
|
|
25
|
+
// terminals degrade together instead of one surface emitting unrenderable
|
|
26
|
+
// glyphs. The ASCII fallbacks are chosen to preserve each drawing's shape.
|
|
27
|
+
const G = unicodeOn
|
|
28
|
+
? {
|
|
29
|
+
diamond: "◈",
|
|
30
|
+
dot: "·",
|
|
31
|
+
boxTL: "╭",
|
|
32
|
+
boxTR: "╮",
|
|
33
|
+
boxBL: "╰",
|
|
34
|
+
boxBR: "╯",
|
|
35
|
+
boxV: "│",
|
|
36
|
+
boxH: "─",
|
|
37
|
+
bar: "▇",
|
|
38
|
+
fill: "█",
|
|
39
|
+
track: "░",
|
|
40
|
+
}
|
|
41
|
+
: {
|
|
42
|
+
diamond: ">",
|
|
43
|
+
dot: "-",
|
|
44
|
+
boxTL: "+",
|
|
45
|
+
boxTR: "+",
|
|
46
|
+
boxBL: "+",
|
|
47
|
+
boxBR: "+",
|
|
48
|
+
boxV: "|",
|
|
49
|
+
boxH: "-",
|
|
50
|
+
bar: "#",
|
|
51
|
+
fill: "#",
|
|
52
|
+
track: "-",
|
|
53
|
+
};
|
|
15
54
|
function paint(rgb, s) {
|
|
16
55
|
if (!colorOn)
|
|
17
56
|
return s;
|
|
@@ -20,6 +59,22 @@ function paint(rgb, s) {
|
|
|
20
59
|
function bold(s) {
|
|
21
60
|
return colorOn ? `\x1b[1m${s}\x1b[0m` : s;
|
|
22
61
|
}
|
|
62
|
+
/**
|
|
63
|
+
* Wrap `label` in an OSC 8 terminal hyperlink pointing at `url`, so a
|
|
64
|
+
* capable terminal renders it as a clickable link. Terminals that don't
|
|
65
|
+
* support OSC 8 simply ignore the escapes and show the label. Falls back to a
|
|
66
|
+
* plain `label (url)` when rich output is off (non-TTY / NO_COLOR) so piped and
|
|
67
|
+
* dumb-terminal output stays legible.
|
|
68
|
+
*
|
|
69
|
+
* @param label - The visible, clickable text.
|
|
70
|
+
* @param url - The target the terminal opens on click.
|
|
71
|
+
* @returns The label wrapped as a hyperlink, or `label (url)` when off.
|
|
72
|
+
*/
|
|
73
|
+
export function link(label, url) {
|
|
74
|
+
if (!colorOn)
|
|
75
|
+
return `${label} (${url})`;
|
|
76
|
+
return `\x1b]8;;${url}\x1b\\${label}\x1b]8;;\x1b\\`;
|
|
77
|
+
}
|
|
23
78
|
/** Brand color helpers for composing styled strings. */
|
|
24
79
|
export const c = {
|
|
25
80
|
cyan: (s) => paint(PALETTE.cyan, s),
|
|
@@ -42,21 +97,43 @@ export const ui = {
|
|
|
42
97
|
plain: (s = "") => console.log(s),
|
|
43
98
|
code: (s) => c.cyan(s),
|
|
44
99
|
};
|
|
100
|
+
/**
|
|
101
|
+
* A single-line branded header — mark · name · installed version · tagline —
|
|
102
|
+
* printed once at the top of interactive commands (see `src/cli/index.ts`). The
|
|
103
|
+
* version comes from the cached {@link pkgVersion}. Glyphs degrade to ASCII on
|
|
104
|
+
* terminals without reliable unicode, and the styling no-ops to plain text when
|
|
105
|
+
* color is off, so the line stays legible everywhere.
|
|
106
|
+
*
|
|
107
|
+
* Example: `◈ speclaw v0.1.15 · where specs become law`
|
|
108
|
+
*/
|
|
109
|
+
export function header() {
|
|
110
|
+
const mark = c.cyan(G.diamond);
|
|
111
|
+
const name = bold(c.cream("speclaw"));
|
|
112
|
+
const ver = c.muted("v" + pkgVersion());
|
|
113
|
+
const tag = c.muted(G.dot + " where specs become law");
|
|
114
|
+
console.log(`${mark} ${name} ${ver} ${tag}`);
|
|
115
|
+
}
|
|
45
116
|
/**
|
|
46
117
|
* The speclaw wordmark + logo mark (a document whose bottom line — the law — is
|
|
47
118
|
* highlighted in cyan). Printed at the top of `speclaw init`.
|
|
48
119
|
*/
|
|
49
120
|
export function banner() {
|
|
50
|
-
const
|
|
51
|
-
const
|
|
121
|
+
const H = G.boxH;
|
|
122
|
+
const bar = c.cyan(G.bar.repeat(6));
|
|
123
|
+
const line = c.muted(H.repeat(6));
|
|
52
124
|
const edge = c.muted;
|
|
53
125
|
console.log();
|
|
54
|
-
console.log(" " + edge(
|
|
55
|
-
console.log(" " + edge("
|
|
56
|
-
console.log(" " +
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
126
|
+
console.log(" " + edge(G.boxTL + H.repeat(8) + G.boxTR));
|
|
127
|
+
console.log(" " + edge(G.boxV + " ") + line + edge(" " + G.boxV) + " " + bold(c.cream("s p e c l a w")));
|
|
128
|
+
console.log(" " +
|
|
129
|
+
edge(G.boxV + " ") +
|
|
130
|
+
c.muted(H.repeat(4) + " ") +
|
|
131
|
+
edge(" " + G.boxV) +
|
|
132
|
+
" " +
|
|
133
|
+
c.muted("where specs become law"));
|
|
134
|
+
console.log(" " + edge(G.boxV + " ") + c.muted(H.repeat(5)) + " " + edge(" " + G.boxV));
|
|
135
|
+
console.log(" " + edge(G.boxV + " ") + bar + edge(" " + G.boxV));
|
|
136
|
+
console.log(" " + edge(G.boxBL + H.repeat(8) + G.boxBR));
|
|
60
137
|
console.log();
|
|
61
138
|
}
|
|
62
139
|
/** Render a single-line progress bar on stderr (so stdout stays clean). */
|
|
@@ -66,7 +143,7 @@ export function renderProgress(done, total, label) {
|
|
|
66
143
|
const width = 26;
|
|
67
144
|
const ratio = total > 0 ? done / total : 1;
|
|
68
145
|
const filled = Math.round(ratio * width);
|
|
69
|
-
const bar = c.cyan(
|
|
146
|
+
const bar = c.cyan(G.fill.repeat(filled)) + c.muted(G.track.repeat(width - filled));
|
|
70
147
|
const pct = c.cyanDim(String(Math.round(ratio * 100)).padStart(3) + "%");
|
|
71
148
|
const shortLabel = label.length > 38 ? "…" + label.slice(-37) : label;
|
|
72
149
|
process.stderr.write(`\r ${bar} ${pct} ${c.muted(shortLabel.padEnd(38))}`);
|
|
@@ -78,11 +155,12 @@ export function clearProgress() {
|
|
|
78
155
|
/** Draw a cyan-bordered block (used for the copy-paste agent prompt). */
|
|
79
156
|
export function box(lines, title) {
|
|
80
157
|
const width = Math.min(72, Math.max(...lines.map((l) => l.length), title?.length ?? 0) + 2);
|
|
158
|
+
const H = G.boxH;
|
|
81
159
|
const top = title
|
|
82
|
-
? "
|
|
83
|
-
:
|
|
84
|
-
console.log(" " + c.muted(top) + c.muted(
|
|
160
|
+
? G.boxTL + H + " " + c.cyanDim(title) + " " + H.repeat(Math.max(0, width - title.length - 3))
|
|
161
|
+
: G.boxTL + H.repeat(width);
|
|
162
|
+
console.log(" " + c.muted(top) + c.muted(G.boxTR));
|
|
85
163
|
for (const l of lines)
|
|
86
|
-
console.log(" " + c.muted("
|
|
87
|
-
console.log(" " + c.muted(
|
|
164
|
+
console.log(" " + c.muted(G.boxV + " ") + c.cream(l.padEnd(width - 2)) + c.muted(" " + G.boxV));
|
|
165
|
+
console.log(" " + c.muted(G.boxBL + H.repeat(width) + G.boxBR));
|
|
88
166
|
}
|
|
@@ -2,7 +2,7 @@ import fs from "node:fs";
|
|
|
2
2
|
import os from "node:os";
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
import { pkgName, pkgVersion } from "../../shared/version.js";
|
|
5
|
-
import { c } from "./ui.js";
|
|
5
|
+
import { c, link } from "./ui.js";
|
|
6
6
|
// A lightweight, best-effort update notifier. The registry is queried at most
|
|
7
7
|
// once a day (result cached under ~/.speclaw/), the lookup is time-boxed, and
|
|
8
8
|
// every failure is swallowed — checking for updates must never slow down or
|
|
@@ -95,9 +95,36 @@ export async function checkForUpdates(opts = {}) {
|
|
|
95
95
|
}
|
|
96
96
|
return { current, latest, updateAvailable: !!latest && isNewer(latest, current) };
|
|
97
97
|
}
|
|
98
|
+
/** The public npm page for a package, where an upgrade can be reviewed. */
|
|
99
|
+
export function npmPackageUrl(name) {
|
|
100
|
+
return `https://www.npmjs.com/package/${name}`;
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* Build the two-line "update available" notice. The latest version is rendered
|
|
104
|
+
* as a clickable link to the package's npm page, so a capable terminal lets the
|
|
105
|
+
* user open the release with a single click while `speclaw update` remains the
|
|
106
|
+
* command that performs the upgrade.
|
|
107
|
+
*
|
|
108
|
+
* @param current - The installed version.
|
|
109
|
+
* @param latest - The newest published version.
|
|
110
|
+
* @returns The formatted, styled notice (no leading/trailing blank lines).
|
|
111
|
+
*/
|
|
112
|
+
export function upgradeNotice(current, latest) {
|
|
113
|
+
const latestLink = c.cyan(link(latest, npmPackageUrl(pkgName())));
|
|
114
|
+
return (" " +
|
|
115
|
+
c.amber("⬆ speclaw ") +
|
|
116
|
+
c.muted(current + " → ") +
|
|
117
|
+
latestLink +
|
|
118
|
+
c.muted(" available") +
|
|
119
|
+
"\n" +
|
|
120
|
+
" " +
|
|
121
|
+
c.muted("run ") +
|
|
122
|
+
c.cyan("speclaw update") +
|
|
123
|
+
c.muted(" — upgrades and applies only what's new"));
|
|
124
|
+
}
|
|
98
125
|
/**
|
|
99
|
-
* Print
|
|
100
|
-
*
|
|
126
|
+
* Print the "update available" notice to stderr when a newer version exists.
|
|
127
|
+
* No-op for the `mcp`/`update`/`help`/`version` commands, on non-TTY stderr, or
|
|
101
128
|
* when NO_UPDATE_NOTIFIER / SPECLAW_NO_UPDATE_NOTIFIER is set. Never throws.
|
|
102
129
|
*
|
|
103
130
|
* @param cmd - The command that just ran (used to skip noisy contexts).
|
|
@@ -110,23 +137,13 @@ export async function maybeNotifyUpdate(cmd) {
|
|
|
110
137
|
return;
|
|
111
138
|
// `init` shows its own prominent up-front warning and ends on the clean
|
|
112
139
|
// copy-paste prompt — don't append a second notice after it.
|
|
113
|
-
if (!cmd ||
|
|
140
|
+
if (!cmd ||
|
|
141
|
+
["mcp", "update", "init", "help", "--help", "-h", "version", "--version", "-v"].includes(cmd))
|
|
114
142
|
return;
|
|
115
143
|
const { current, latest, updateAvailable } = await checkForUpdates();
|
|
116
144
|
if (!updateAvailable || !latest)
|
|
117
145
|
return;
|
|
118
|
-
process.stderr.write("\n" +
|
|
119
|
-
" " +
|
|
120
|
-
c.amber("⬆ speclaw ") +
|
|
121
|
-
c.muted(current + " → ") +
|
|
122
|
-
c.cyan(latest) +
|
|
123
|
-
c.muted(" available") +
|
|
124
|
-
"\n" +
|
|
125
|
-
" " +
|
|
126
|
-
c.muted("run ") +
|
|
127
|
-
c.cyan("speclaw update") +
|
|
128
|
-
c.muted(" — upgrades and applies only what's new") +
|
|
129
|
-
"\n\n");
|
|
146
|
+
process.stderr.write("\n" + upgradeNotice(current, latest) + "\n\n");
|
|
130
147
|
}
|
|
131
148
|
catch {
|
|
132
149
|
/* the notifier is best-effort — never let it break a command */
|
|
@@ -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,15 @@ 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.
|
|
@@ -43,6 +43,22 @@ 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
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@esneiderbravo/speclaw",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.15",
|
|
4
4
|
"publishConfig": {
|
|
5
5
|
"access": "public"
|
|
6
6
|
},
|
|
@@ -41,6 +41,8 @@
|
|
|
41
41
|
"lint": "eslint .",
|
|
42
42
|
"format": "prettier --write .",
|
|
43
43
|
"check": "prettier --check . && eslint .",
|
|
44
|
+
"pretest": "tsc -p tsconfig.test.json && node scripts/prep-test-assets.mjs",
|
|
45
|
+
"test": "node --test --experimental-test-coverage --test-coverage-lines=80 --test-coverage-functions=80 --test-coverage-branches=80 --test-coverage-exclude='dist-test/test/**' --test-coverage-exclude='dist/**' 'dist-test/test/**/*.test.js'",
|
|
44
46
|
"prepublishOnly": "npm run build"
|
|
45
47
|
},
|
|
46
48
|
"engines": {
|