@esneiderbravo/speclaw 2.0.18 → 2.0.19
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/index.js +6 -4
- package/dist/cli/lib/ui.js +3 -0
- package/dist/modules/lawbook/ship.js +41 -12
- package/package.json +1 -1
package/dist/cli/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { parseFlags, REPEATABLE_FLAGS } from "./lib/args.js";
|
|
3
|
-
import { ui, header } from "./lib/ui.js";
|
|
3
|
+
import { ui, header, detectTheme } from "./lib/ui.js";
|
|
4
4
|
import { maybeNotifyUpdate } from "./lib/update-check.js";
|
|
5
5
|
import { GLOBAL_HELP as HELP, helpFor, knownCommands, wantsHelp } from "./lib/help.js";
|
|
6
6
|
// Commands that open with the one-line branded header. These are the
|
|
@@ -38,13 +38,15 @@ const HEADER_COMMANDS = new Set([
|
|
|
38
38
|
* child process. `budget --json`, `doctor --json`, and `coverage` when emitting
|
|
39
39
|
* TAP/JSON (or when stdout is not a TTY) are machine-consumed and suppress the
|
|
40
40
|
* header. `session-start` and `reindex-file` are not header-eligible: they must print
|
|
41
|
-
* nothing.
|
|
41
|
+
* nothing. The palette is matched to the terminal background first, so every
|
|
42
|
+
* styled line after it — not only `init`'s — stays legible on a light theme.
|
|
42
43
|
*/
|
|
43
|
-
function maybeHeader(cmd, flags) {
|
|
44
|
+
async function maybeHeader(cmd, flags) {
|
|
44
45
|
if (!process.stdout.isTTY && process.env.FORCE_COLOR !== "1")
|
|
45
46
|
return;
|
|
46
47
|
if (!HEADER_COMMANDS.has(cmd))
|
|
47
48
|
return;
|
|
49
|
+
await detectTheme();
|
|
48
50
|
// A self-update child re-runs `update`; the parent already printed the header.
|
|
49
51
|
if (cmd === "update" && process.env.SPECLAW_SELF_UPDATED)
|
|
50
52
|
return;
|
|
@@ -172,7 +174,7 @@ async function main() {
|
|
|
172
174
|
return;
|
|
173
175
|
}
|
|
174
176
|
const flags = parseFlags(rest, REPEATABLE_FLAGS);
|
|
175
|
-
maybeHeader(cmd, flags);
|
|
177
|
+
await maybeHeader(cmd, flags);
|
|
176
178
|
await dispatch(cmd, flags);
|
|
177
179
|
// The SessionStart and edit hooks must stay silent: no update notice either.
|
|
178
180
|
if (cmd === "session-start" || cmd === "reindex-file" || cmd === "ship-on-stop")
|
package/dist/cli/lib/ui.js
CHANGED
|
@@ -118,6 +118,9 @@ export async function detectTheme() {
|
|
|
118
118
|
theme = themeFromOsc11(await queryBackground()) ?? (await macAppearance()) ?? "dark";
|
|
119
119
|
}
|
|
120
120
|
setTheme(theme);
|
|
121
|
+
// Child speclaw processes (a self-update re-run) inherit the answer instead
|
|
122
|
+
// of querying the terminal again.
|
|
123
|
+
process.env.SPECLAW_THEME ??= theme;
|
|
121
124
|
return theme;
|
|
122
125
|
}
|
|
123
126
|
const colorOn = (Boolean(process.stdout.isTTY) || process.env.FORCE_COLOR === "1") && !process.env.NO_COLOR;
|
|
@@ -79,10 +79,28 @@ function branchFiles(projectPath) {
|
|
|
79
79
|
return [];
|
|
80
80
|
const base = mergeBase(projectPath, "main") ?? mergeBase(projectPath, "master");
|
|
81
81
|
const committed = base ? changedFiles(projectPath, base) : [];
|
|
82
|
-
return [
|
|
82
|
+
return [
|
|
83
|
+
...new Set([
|
|
84
|
+
...committed,
|
|
85
|
+
...worktreeChangedFiles(projectPath),
|
|
86
|
+
...untrackedFiles(projectPath),
|
|
87
|
+
]),
|
|
88
|
+
]
|
|
83
89
|
.filter((f) => !f.startsWith("lawbook/changes/"))
|
|
84
90
|
.sort();
|
|
85
91
|
}
|
|
92
|
+
/** New files not yet added, outside ship's own output (`lawbook/changes/`, `.speclaw/`). */
|
|
93
|
+
function untrackedFiles(projectPath) {
|
|
94
|
+
return git(projectPath, [
|
|
95
|
+
"-c",
|
|
96
|
+
"core.quotePath=false",
|
|
97
|
+
"ls-files",
|
|
98
|
+
"--others",
|
|
99
|
+
"--exclude-standard",
|
|
100
|
+
])
|
|
101
|
+
.split("\n")
|
|
102
|
+
.filter((f) => f && !f.startsWith("lawbook/changes/") && !f.startsWith(".speclaw/"));
|
|
103
|
+
}
|
|
86
104
|
function secs(ms) {
|
|
87
105
|
return `${(ms / 1000).toFixed(1)} s`;
|
|
88
106
|
}
|
|
@@ -311,30 +329,41 @@ function readMarker(projectPath) {
|
|
|
311
329
|
return { branch: "", change: "", fingerprint: raw };
|
|
312
330
|
}
|
|
313
331
|
/**
|
|
314
|
-
* Fingerprint of the branch's work outside `lawbook/changes/`:
|
|
315
|
-
*
|
|
316
|
-
*
|
|
317
|
-
*
|
|
318
|
-
*
|
|
332
|
+
* Fingerprint of the branch's work outside `lawbook/changes/` and `.speclaw/`: every path that
|
|
333
|
+
* differs from the merge base with `main`/`master` or is untracked, with its
|
|
334
|
+
* working-tree contents. A file hashes the same whether it is untracked, staged
|
|
335
|
+
* or committed, so committing work — new files included — is not new work; ship
|
|
336
|
+
* writes under `lawbook/changes/`, so its own output never changes it either.
|
|
337
|
+
* Without a merge base it falls back to the paths changed against HEAD plus HEAD itself.
|
|
319
338
|
*/
|
|
320
339
|
function workFingerprint(projectPath) {
|
|
321
340
|
const base = mergeBase(projectPath, "main") ?? mergeBase(projectPath, "master");
|
|
322
|
-
const
|
|
341
|
+
const tracked = git(projectPath, [
|
|
342
|
+
"-c",
|
|
343
|
+
"core.quotePath=false",
|
|
344
|
+
"diff",
|
|
345
|
+
"--name-only",
|
|
346
|
+
base ?? "HEAD",
|
|
347
|
+
"--",
|
|
348
|
+
".",
|
|
349
|
+
":(exclude)lawbook/changes",
|
|
350
|
+
":(exclude).speclaw",
|
|
351
|
+
]).split("\n");
|
|
323
352
|
const hash = createHash("sha256");
|
|
324
|
-
for (const f of
|
|
325
|
-
if (!f
|
|
353
|
+
for (const f of [...new Set([...tracked, ...untrackedFiles(projectPath)])].sort()) {
|
|
354
|
+
if (!f)
|
|
326
355
|
continue;
|
|
327
|
-
// Contents, not just names: editing a new, not-yet-added file is new work.
|
|
328
356
|
hash.update(`${f}\n`);
|
|
329
357
|
try {
|
|
330
358
|
hash.update(fs.readFileSync(path.join(projectPath, f)));
|
|
331
359
|
}
|
|
332
360
|
catch {
|
|
333
|
-
//
|
|
361
|
+
// Deleted, vanished or unreadable: its name stands in.
|
|
362
|
+
hash.update("\0");
|
|
334
363
|
}
|
|
335
364
|
}
|
|
336
365
|
const anchor = base ?? git(projectPath, ["rev-parse", "HEAD"]).trim();
|
|
337
|
-
return hash.update(`\n${anchor}\n
|
|
366
|
+
return hash.update(`\n${anchor}\n`).digest("hex");
|
|
338
367
|
}
|
|
339
368
|
/**
|
|
340
369
|
* The `Stop` hook body: when the agent ends a turn on a feature branch with
|