@erclx/canon 5.54.0 → 5.55.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/CHANGELOG.md +8 -0
- package/claude/.claude-plugin/plugin.json +1 -1
- package/claude/skills/create-skill/SKILL.md +1 -1
- package/claude/skills/document-health/SKILL.md +6 -6
- package/claude/skills/document-repair/SKILL.md +5 -5
- package/claude/skills/target-check/SKILL.md +6 -8
- package/claude/skills/target-check/references/domains.md +1 -4
- package/claude/skills/target-setup/SKILL.md +8 -8
- package/claude/skills/target-update/SKILL.md +5 -5
- package/docs/agents/install-and-sync.md +2 -2
- package/docs/agents/output-shape.md +11 -10
- package/docs/target/sync.md +1 -1
- package/package.json +1 -1
- package/scripts/core/install-check.sh +1 -1
- package/scripts/lib/ui.sh +12 -65
- package/src/audits/run.ts +1 -1
- package/src/claude/routing.ts +1 -1
- package/src/cli/run.ts +5 -6
- package/src/cli/ui.ts +87 -28
- package/src/cli.ts +6 -0
- package/src/commands/claude.ts +2 -3
- package/src/commands/design.ts +8 -13
- package/src/commands/gate.ts +5 -3
- package/src/commands/gov.ts +11 -21
- package/src/commands/labels.ts +1 -1
- package/src/commands/pr.ts +1 -1
- package/src/commands/sync.ts +1 -1
- package/src/commands/tooling.ts +5 -8
- package/src/commands/transcripts.ts +6 -11
- package/src/gate/sequencer.ts +1 -1
- package/src/gate/stages.ts +0 -10
- package/src/hooks/post-merge.ts +2 -5
- package/src/process/harness.ts +3 -4
- package/src/sandbox/equivalence.ts +0 -1
- package/src/sandbox/provision.ts +8 -13
- package/src/sandbox/scenario.ts +6 -12
- package/src/sync/engine.ts +3 -8
- package/src/sync/workflow.ts +3 -5
- package/src/tooling/verify-stack.ts +1 -1
- package/tooling/base/configs/.husky/post-merge +6 -10
- package/scripts/core/check-color-source.sh +0 -51
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,13 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [5.55.0](https://github.com/erclx/canon/compare/v5.54.0...v5.55.0) (2026-10-21)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
### Features
|
|
7
|
+
|
|
8
|
+
* **cli:** decide headless from a missing terminal on stdin ([#2600](https://github.com/erclx/canon/issues/2600)) ([63e9af6](https://github.com/erclx/canon/commit/63e9af6fcd2b23c1aa4abe45e0fb818519b0e314))
|
|
9
|
+
* **cli:** quiet stderr on --json runs and drop the frame off a terminal ([#2599](https://github.com/erclx/canon/issues/2599)) ([824861b](https://github.com/erclx/canon/commit/824861b55ce9a1c92227b3b8c96f541684d2da8b))
|
|
10
|
+
|
|
3
11
|
## [5.54.0](https://github.com/erclx/canon/compare/v5.53.0...v5.54.0) (2026-10-21)
|
|
4
12
|
|
|
5
13
|
|
|
@@ -42,7 +42,7 @@ Read these in the same pass:
|
|
|
42
42
|
3. Draft the sibling `REQUIREMENT.md` from what the skill is for, in the shape the requirement standard states.
|
|
43
43
|
- Write the gaps from the user's description rather than from the drafted body, since a requirement derived from the body records whatever the draft overfitted to.
|
|
44
44
|
4. Confirm the skill name and both files with the user before writing, when a person can answer.
|
|
45
|
-
- Skip the confirmation when
|
|
45
|
+
- Skip the confirmation when another skill's run invoked this one, such as `auto-ship`, or when a dispatched worker runs it.
|
|
46
46
|
- Then put the three Step 1 answers and the two drafted paths in the output and continue to Step 5, since a question nobody reads parks the chain.
|
|
47
47
|
|
|
48
48
|
### Write and check
|
|
@@ -15,7 +15,7 @@ Three axes carry the report. Two answer from a verb and the third answers from a
|
|
|
15
15
|
|
|
16
16
|
- `canon` absent from the path: stop. `❌ canon is not on PATH, so neither measured axis can be read.`
|
|
17
17
|
- `git ls-files '*.md'` listing nothing: stop. `❌ No tracked markdown here, so there is no corpus to read.`
|
|
18
|
-
-
|
|
18
|
+
- Prompt at no point, since a caller reaching this from a rollout wave has nobody present to answer. A call with no terminal on stdin already runs headless, so no command takes a prefix.
|
|
19
19
|
- Branch on each record's own fields rather than on an exit status, since a shell profile may wrap `canon` in a function whose trailing command reports every refusal as success
|
|
20
20
|
- Repair nothing, write no file in the project, and open no pull request. Whoever read the report decides what to fix.
|
|
21
21
|
|
|
@@ -30,8 +30,8 @@ Drop what nobody hand-authors before reporting a finding against it: a generated
|
|
|
30
30
|
Run these from the project root, in parallel:
|
|
31
31
|
|
|
32
32
|
```bash
|
|
33
|
-
|
|
34
|
-
|
|
33
|
+
canon markdown audit --json
|
|
34
|
+
canon context audit --json
|
|
35
35
|
```
|
|
36
36
|
|
|
37
37
|
The first answers length, carrying the bullet, paragraph, and depth checkpoints and the per-file weights read against them. The second answers placement for the folders it resolves, carrying entry length, reference form, and index drift.
|
|
@@ -39,8 +39,8 @@ The first answers length, carrying the bullet, paragraph, and depth checkpoints
|
|
|
39
39
|
Then read which classifier would run, and deepen placement with the verdicts per section:
|
|
40
40
|
|
|
41
41
|
```bash
|
|
42
|
-
|
|
43
|
-
|
|
42
|
+
canon context classifier show
|
|
43
|
+
canon context classify sweep --json
|
|
44
44
|
```
|
|
45
45
|
|
|
46
46
|
The first names the backend, the model, and the source that decided both. Report which layer answered, since a project with the model layer off reads from the regex layer alone and the sweep says nothing about which one it used.
|
|
@@ -65,7 +65,7 @@ Mark every staleness finding as a reading. It is this session's judgment rather
|
|
|
65
65
|
When the scope reaches `canon/REQUIREMENTS.md`, read it for identity drift as well as for stale paths, and take the review point from the verb:
|
|
66
66
|
|
|
67
67
|
```bash
|
|
68
|
-
|
|
68
|
+
canon records stale canonical --json
|
|
69
69
|
```
|
|
70
70
|
|
|
71
71
|
The doc in the record carries `reviewed` and `releasesSince`. `${CLAUDE_SKILL_DIR}/references/axes.md` holds the identity claim shapes to test and how the record reads. Skip both the read and the verb when the scope excludes the doc.
|
|
@@ -16,14 +16,14 @@ The CLI holds every number. `canon markdown audit` measures, and `canon markdown
|
|
|
16
16
|
- `canon` absent from the path: stop. `❌ canon is not on PATH, so nothing can be measured or gated.`
|
|
17
17
|
- The audit refusing: stop and name its `reason`. A `malformed-config` refusal names the file to fix by hand, since the verb will not append to a file it cannot read.
|
|
18
18
|
- `gated add` refusing with `canon-checkout`: stop. Canon's own checkout gates through a constant in its source, which a code change edits.
|
|
19
|
-
-
|
|
19
|
+
- Branch on each record's own fields rather than on an exit status a shell wrapper can flatten. A call with no terminal on stdin already runs headless, so no command takes a prefix.
|
|
20
20
|
|
|
21
21
|
## Step 1: read the audit
|
|
22
22
|
|
|
23
23
|
Run the audit over the whole tree and take the density and reference shape findings from its record:
|
|
24
24
|
|
|
25
25
|
```bash
|
|
26
|
-
|
|
26
|
+
canon markdown audit --json
|
|
27
27
|
```
|
|
28
28
|
|
|
29
29
|
Take the candidate folders from the order table in `${CLAUDE_SKILL_DIR}/references/batches.md` rather than from the findings, since a clean folder carries no finding and would never be gated. Drop every folder `canon/config/markdown.toml` already lists, since a listed folder already gates.
|
|
@@ -41,7 +41,7 @@ Load `markdown-craft` and `write-human`, then rewrite each failing block by its
|
|
|
41
41
|
Re-run the audit on the folder alone after each file:
|
|
42
42
|
|
|
43
43
|
```bash
|
|
44
|
-
|
|
44
|
+
canon markdown audit <prefix> --json
|
|
45
45
|
```
|
|
46
46
|
|
|
47
47
|
Read `${CLAUDE_SKILL_DIR}/references/batches.md` for what a rewrite may cut. A stated reason survives every rewrite, and the audit reaching zero cannot see one that went missing.
|
|
@@ -49,7 +49,7 @@ Read `${CLAUDE_SKILL_DIR}/references/batches.md` for what a rewrite may cut. A s
|
|
|
49
49
|
## Step 4: gate the folder
|
|
50
50
|
|
|
51
51
|
```bash
|
|
52
|
-
|
|
52
|
+
canon markdown gated add <prefix> --json
|
|
53
53
|
```
|
|
54
54
|
|
|
55
55
|
Pass the prefix exactly as the batch named it: a folder with its one trailing slash, such as `docs/`, and a file by its bare path, such as `README.md`. The verb refuses a doubled slash and a slash after a file, since either matches nothing and would gate nothing.
|
|
@@ -61,7 +61,7 @@ Branch on the record. `action: appended` or `present` means the folder gates now
|
|
|
61
61
|
A skill's `REQUIREMENT.md` gates only on its own path, never through a folder prefix, so gating `.claude/skills/` leaves each requirement reporting. Gate each one that reaches zero by its path:
|
|
62
62
|
|
|
63
63
|
```bash
|
|
64
|
-
|
|
64
|
+
canon markdown gated add .claude/skills/<name>/REQUIREMENT.md --json
|
|
65
65
|
```
|
|
66
66
|
|
|
67
67
|
## Output
|
|
@@ -17,7 +17,7 @@ Six domains carry the report. Five answer from a verb that already exists and th
|
|
|
17
17
|
- No `.claude/` directory at the working root: stop. `❌ No .claude/ here, so nothing is installed to check. Run canon init first.`
|
|
18
18
|
- A `claude/skills/` folder at the root, no leading dot: stop. `❌ This is the toolkit itself rather than a target. Ask the orchestrator for a worker per repository to take a change out to the targets.`
|
|
19
19
|
- A target receives skills at `.claude/skills/` and authors none, so the spelling without the dot separates the source from what it installs into
|
|
20
|
-
-
|
|
20
|
+
- Prompt at no point, since every caller is a dispatched worker with nobody to answer. A call with no terminal on stdin already runs headless, so no command takes a prefix.
|
|
21
21
|
- Branch on each record's own fields rather than on an exit status. A shell function wrapping `canon` can report every refusal as success.
|
|
22
22
|
|
|
23
23
|
## Step 1: read the reports
|
|
@@ -25,25 +25,23 @@ Six domains carry the report. Five answer from a verb that already exists and th
|
|
|
25
25
|
Run these from the target root, in parallel:
|
|
26
26
|
|
|
27
27
|
```bash
|
|
28
|
-
|
|
29
|
-
|
|
28
|
+
canon sync --check . --json
|
|
29
|
+
canon context audit --json
|
|
30
30
|
```
|
|
31
31
|
|
|
32
32
|
The first answers five domains from one read, being `skew`, `domains`, `seeds`, `tooling`, and the layout fields `unmigrated`, `superseded`, `unmovedSurfaces`, and `reverse`. The second deepens a domain that first read only counts.
|
|
33
33
|
|
|
34
|
-
Then run `
|
|
34
|
+
Then run `canon records validate <kind> --json` once per record folder the target carries, taking `<kind>` from what is present under the record root rather than from a list held here.
|
|
35
35
|
|
|
36
36
|
### The tooling read takes a stack, always
|
|
37
37
|
|
|
38
38
|
The tooling comparison runs last, because it needs a value the first read returns. Take `<stack>` from the first name in the report's `tooling.chain`, which records the stack nearest the target, and pass it:
|
|
39
39
|
|
|
40
40
|
```bash
|
|
41
|
-
|
|
41
|
+
canon tooling sync --check <stack>
|
|
42
42
|
```
|
|
43
43
|
|
|
44
|
-
Refuse the domain and report it unread when `chain` names nothing, rather than running the command bare. A bare call reaches the stack prompt, which
|
|
45
|
-
|
|
46
|
-
Every target would then be compared against whichever stack sorts first, on every run, and the drift reported would be an artifact of the wrong comparison rather than anything the target did.
|
|
44
|
+
Refuse the domain and report it unread when `chain` names nothing, rather than running the command bare. A bare call reaches the stack prompt, which refuses a headless caller, so it would report nothing about the domain.
|
|
47
45
|
|
|
48
46
|
### Domains come from the report
|
|
49
47
|
|
|
@@ -72,10 +72,7 @@ Read the `tooling` block off the report first, because the comparison command ne
|
|
|
72
72
|
|
|
73
73
|
### The comparison
|
|
74
74
|
|
|
75
|
-
On a measured report, take `<stack>` from the first name in `chain` and run `canon tooling sync --check <stack>`. Never run it bare.
|
|
76
|
-
|
|
77
|
-
- Omitting the stack reaches a prompt the non-interactive variable resolves to the catalog's first entry rather than refusing
|
|
78
|
-
- That compares the target against a stack it does not use and reports the difference as drift
|
|
75
|
+
On a measured report, take `<stack>` from the first name in `chain` and run `canon tooling sync --check <stack>`. Never run it bare, since omitting the stack reaches a prompt that refuses a headless caller and compares nothing.
|
|
79
76
|
|
|
80
77
|
### Verdicts
|
|
81
78
|
|
|
@@ -33,9 +33,9 @@ rather than a step within it, so the full chain never runs it separately.
|
|
|
33
33
|
|
|
34
34
|
## Non-interactive path
|
|
35
35
|
|
|
36
|
-
Every phase runs
|
|
37
|
-
otherwise pause, and each takes an
|
|
38
|
-
time:
|
|
36
|
+
Every phase runs headless with no prefix, since a call with no terminal on
|
|
37
|
+
stdin is already headless. Three phases otherwise pause, and each takes an
|
|
38
|
+
argument that answers its prompt ahead of time:
|
|
39
39
|
|
|
40
40
|
| Phase | Pauses to | Answered ahead by | With neither and no TTY |
|
|
41
41
|
| ------------------- | ------------------------------------------ | ---------------------------------------------------------- | ----------------------------------------------------- |
|
|
@@ -91,7 +91,7 @@ target project's current directory. Do not cd into the toolkit source tree. The
|
|
|
91
91
|
Step 1: `canon init` installs base tooling, claude seeds, and governance rules.
|
|
92
92
|
|
|
93
93
|
```bash
|
|
94
|
-
|
|
94
|
+
canon init \
|
|
95
95
|
--stack <stack> \
|
|
96
96
|
--add <rules> \
|
|
97
97
|
<target>
|
|
@@ -106,7 +106,7 @@ Step 2: `canon tooling sync <tooling-stack> --write` installs stack deps, script
|
|
|
106
106
|
- A fresh target has nothing to lose, so pass `--write` directly rather than reading a report first
|
|
107
107
|
|
|
108
108
|
```bash
|
|
109
|
-
|
|
109
|
+
canon tooling sync <tooling-stack> <target> --write
|
|
110
110
|
```
|
|
111
111
|
|
|
112
112
|
### Monorepo with multiple language roots
|
|
@@ -114,8 +114,8 @@ CANON_NON_INTERACTIVE=1 canon tooling sync <tooling-stack> <target> --write
|
|
|
114
114
|
Run `canon init` once at the repo root so `base` (husky, oxfmt, commitlint, CI) lands once, then sync each subtree with `--skip base`. Take the subtree commands from the preview's per-root rows rather than picking folders by hand.
|
|
115
115
|
|
|
116
116
|
```bash
|
|
117
|
-
|
|
118
|
-
|
|
117
|
+
canon tooling sync vite-react ./frontend --skip base --write
|
|
118
|
+
canon tooling sync python ./backend --skip base --write
|
|
119
119
|
```
|
|
120
120
|
|
|
121
121
|
- Without `--skip base`, each subtree re-drops husky, and git honors only one `core.hooksPath`, so the extra hook dirs silently break
|
|
@@ -139,7 +139,7 @@ Resolve exactly as the detect reference states, confirm the proposed extras
|
|
|
139
139
|
there, then run:
|
|
140
140
|
|
|
141
141
|
```bash
|
|
142
|
-
|
|
142
|
+
canon gov install <stack> --add <extras> <target>
|
|
143
143
|
```
|
|
144
144
|
|
|
145
145
|
Report the rule count installed, the target path, and any rules that failed to
|
|
@@ -24,7 +24,7 @@ Stop with the line beside each state.
|
|
|
24
24
|
|
|
25
25
|
Then hold two rules for every verb this body runs.
|
|
26
26
|
|
|
27
|
-
- Run it
|
|
27
|
+
- Run it from the target root with no prefix, since a call with no terminal on stdin already runs headless
|
|
28
28
|
- Branch on its record's fields rather than its exit, which a shell function wrapping `canon` can flatten
|
|
29
29
|
|
|
30
30
|
## Step 1: bring the binary and plugin forward
|
|
@@ -38,12 +38,12 @@ Both write to the machine rather than the target, and the preview needs the verb
|
|
|
38
38
|
Run these in parallel from the target root:
|
|
39
39
|
|
|
40
40
|
```bash
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
41
|
+
canon changelog --json
|
|
42
|
+
canon migrate status --json
|
|
43
|
+
canon sync --check . --json
|
|
44
44
|
```
|
|
45
45
|
|
|
46
|
-
Then take `<stack>` from the first name in the report's `tooling.chain` and run `canon tooling diff <stack> . --json`. Never run it bare, since the stack prompt
|
|
46
|
+
Then take `<stack>` from the first name in the report's `tooling.chain` and run `canon tooling diff <stack> . --json`. Never run it bare, since the stack prompt refuses a headless caller. Report tooling unmeasured when `tooling.measured` is false or `chain` names nothing.
|
|
47
47
|
|
|
48
48
|
### When a read refuses
|
|
49
49
|
|
|
@@ -15,8 +15,8 @@ Any flag skips the confirmation prompt, but the `base` default of `--stack` does
|
|
|
15
15
|
|
|
16
16
|
## Governance install
|
|
17
17
|
|
|
18
|
-
- `canon gov install <stack> [target]` needs the stack
|
|
19
|
-
-
|
|
18
|
+
- `canon gov install <stack> [target]` needs the stack on a call with no terminal, else lists the valid names on stderr and exits 1
|
|
19
|
+
- A headless call takes Yes at the install confirm, and `--yes` answers it at a terminal too. Neither picks a stack.
|
|
20
20
|
- It refuses the toolkit root and a missing path
|
|
21
21
|
- `--add <rules>` with no stack installs into the one positional, or the current directory, keeping the recorded chain. Pass `./node` for a target named like a stack.
|
|
22
22
|
|
|
@@ -22,18 +22,19 @@ Data goes to stdout. UI and logs go to stderr. Help is the exception and prints
|
|
|
22
22
|
- `└` closes the frame on stderr
|
|
23
23
|
- An error renders as `│ ✗ message` inside the frame, never as a lone line
|
|
24
24
|
- Help skips the banner and puts `Usage:` directly on `├`
|
|
25
|
+
- A stderr that is not a terminal gets plain lines instead, with the banner and section titles bare and each item behind its mark alone
|
|
25
26
|
|
|
26
27
|
A help request naming an unknown subcommand, under a group taking no operand, writes its refusal to stderr and exits 1.
|
|
27
28
|
|
|
28
29
|
## JSON and names modes
|
|
29
30
|
|
|
30
|
-
`--json` and `--names` keep stdout pure data.
|
|
31
|
+
`--json` and `--names` keep stdout pure data. A `--json` run writes no progress to stderr, only its warnings and errors, since diagnostics reach stderr in every mode. A JSON refusal carries an action and a reason, so branch on the record rather than on stderr.
|
|
31
32
|
|
|
32
33
|
A shell profile may wrap `canon` in a function that takes its status from a later command. Where a verb documents a record field, read the field rather than the exit.
|
|
33
34
|
|
|
34
35
|
## Color
|
|
35
36
|
|
|
36
|
-
Escape sequences reach a destination that renders them and nowhere else, decided per stream. `NO_COLOR` set to any non-empty value turns color off, and so does a destination that is not a terminal. The
|
|
37
|
+
Escape sequences reach a destination that renders them and nowhere else, decided per stream. `NO_COLOR` set to any non-empty value turns color off, and so does a destination that is not a terminal. The `✓ ! + - ✗` marks survive both. The frame glyphs need a terminal.
|
|
37
38
|
|
|
38
39
|
## Exit discipline
|
|
39
40
|
|
|
@@ -59,16 +60,16 @@ Discover names at runtime rather than hardcoding them.
|
|
|
59
60
|
|
|
60
61
|
## Headless invocation
|
|
61
62
|
|
|
62
|
-
|
|
63
|
+
A call with no terminal on stdin is headless and needs no prefix. Each prompt takes its documented default or refuses and names the flag to pass.
|
|
63
64
|
|
|
64
65
|
```bash
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
66
|
+
canon init --stack node --skip records /path/to/project
|
|
67
|
+
canon sync /path/to/project
|
|
68
|
+
canon gov install node --add astro,shadcn /path/to/project
|
|
69
|
+
canon gov sync /path/to/project
|
|
70
|
+
canon gov build /path/to/project
|
|
71
|
+
canon tooling diff astro /path/to/project
|
|
72
|
+
canon tooling sync astro /path/to/project --write
|
|
72
73
|
canon tooling inject base /path/to/project --configs --seeds
|
|
73
74
|
canon tooling prune-gitignore base /path/to/project
|
|
74
75
|
canon tooling verify vite-react
|
package/docs/target/sync.md
CHANGED
|
@@ -51,4 +51,4 @@ Try a scenario from [sandbox](../../canon/context/development/sandbox.md) before
|
|
|
51
51
|
|
|
52
52
|
## Running sync from an agent session
|
|
53
53
|
|
|
54
|
-
`canon sync .` applies every domain sync, then offers a commit and pull request that needs a terminal.
|
|
54
|
+
`canon sync .` applies every domain sync, then offers a commit and pull request that needs a terminal. With no terminal on stdin the syncs apply, the git step is refused, and the command exits 0. Commit yourself. Sync refuses a dirty working tree, and `canon sync --check .` does not.
|
package/package.json
CHANGED
|
@@ -88,7 +88,7 @@ mkdir -p "$TARGET_DIR"
|
|
|
88
88
|
log_info "Initialized git in $TARGET_DIR"
|
|
89
89
|
|
|
90
90
|
log_step "Run canon init"
|
|
91
|
-
(cd "$TARGET_DIR" &&
|
|
91
|
+
(cd "$TARGET_DIR" && bun run "$EXTRACT_DIR/src/cli.ts" init --stack base </dev/null 2>&1 | pipe_output) || log_error "canon init failed"
|
|
92
92
|
log_info "canon init completed"
|
|
93
93
|
|
|
94
94
|
log_step "Assert scaffold"
|
package/scripts/lib/ui.sh
CHANGED
|
@@ -6,93 +6,40 @@ if [ "${BASH_VERSINFO[0]:-0}" -lt 4 ]; then
|
|
|
6
6
|
exit 1
|
|
7
7
|
fi
|
|
8
8
|
|
|
9
|
-
#
|
|
10
|
-
#
|
|
11
|
-
supports_color() {
|
|
12
|
-
[ -z "${NO_COLOR:-}" ] && [ -t "$1" ]
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
# Assigns the six palette names for one file descriptor. The blank palette keeps
|
|
16
|
-
# every frame character and drops only the escapes, so a captured run still
|
|
17
|
-
# reads as one block.
|
|
18
|
-
#
|
|
19
|
-
# Bash returns a string and nothing richer, so the palette arrives through the
|
|
20
|
-
# caller's own scope. A writer declaring all six `local` before calling this
|
|
21
|
-
# keeps the assignment to itself, which is what leaves the source-time answer
|
|
22
|
-
# below standing for anything that sources this file.
|
|
23
|
-
set_palette() {
|
|
24
|
-
if supports_color "$1"; then
|
|
25
|
-
GREEN='\033[0;32m'
|
|
26
|
-
RED='\033[0;31m'
|
|
27
|
-
YELLOW='\033[0;33m'
|
|
28
|
-
WHITE='\033[1;37m'
|
|
29
|
-
GREY='\033[0;90m'
|
|
30
|
-
NC='\033[0m'
|
|
31
|
-
else
|
|
32
|
-
GREEN=''
|
|
33
|
-
RED=''
|
|
34
|
-
YELLOW=''
|
|
35
|
-
WHITE=''
|
|
36
|
-
GREY=''
|
|
37
|
-
NC=''
|
|
38
|
-
fi
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
# Every writer below frames to stderr and asks about that stream at write time,
|
|
42
|
-
# so no answer is frozen before a caller's redirect is in place. This one answers
|
|
43
|
-
# for stdout instead, because it is read by a sourcing script writing its own
|
|
44
|
-
# frame with a bare `echo`, and that is the stream a bare `echo` reaches.
|
|
45
|
-
set_palette 1
|
|
9
|
+
# The frame writes no color. The scripts left on it run as setup and gate steps,
|
|
10
|
+
# where a pipe or a log reads the output more often than a terminal does.
|
|
46
11
|
|
|
47
12
|
log_info() {
|
|
48
|
-
|
|
49
|
-
set_palette 2
|
|
50
|
-
echo -e "${GREY}│${NC} ${GREEN}✓${NC} $1" >&2
|
|
13
|
+
echo -e "│ ✓ $1" >&2
|
|
51
14
|
}
|
|
52
15
|
log_warn() {
|
|
53
|
-
|
|
54
|
-
set_palette 2
|
|
55
|
-
echo -e "${GREY}│${NC} ${YELLOW}!${NC} $1" >&2
|
|
16
|
+
echo -e "│ ! $1" >&2
|
|
56
17
|
}
|
|
57
18
|
log_error() {
|
|
58
|
-
|
|
59
|
-
set_palette 2
|
|
60
|
-
echo -e "${GREY}│${NC} ${RED}✗${NC} $1" >&2
|
|
19
|
+
echo -e "│ ✗ $1" >&2
|
|
61
20
|
exit 1
|
|
62
21
|
}
|
|
63
22
|
log_step() {
|
|
64
|
-
|
|
65
|
-
set_palette 2
|
|
66
|
-
echo -e "${GREY}│${NC}\n${GREY}├${NC} ${WHITE}$1${NC}" >&2
|
|
23
|
+
echo -e "│\n├ $1" >&2
|
|
67
24
|
}
|
|
68
25
|
log_add() {
|
|
69
|
-
|
|
70
|
-
set_palette 2
|
|
71
|
-
echo -e "${GREY}│${NC} ${GREEN}+${NC} $1" >&2
|
|
26
|
+
echo -e "│ + $1" >&2
|
|
72
27
|
}
|
|
73
28
|
log_rem() {
|
|
74
|
-
|
|
75
|
-
set_palette 2
|
|
76
|
-
echo -e "${GREY}│${NC} ${RED}-${NC} $1" >&2
|
|
29
|
+
echo -e "│ - $1" >&2
|
|
77
30
|
}
|
|
78
31
|
|
|
79
32
|
pipe_output() {
|
|
80
|
-
|
|
81
|
-
set_palette 2
|
|
82
|
-
while IFS= read -r line; do echo -e "${GREY}│${NC} $line" >&2; done
|
|
33
|
+
while IFS= read -r line; do echo -e "│ $line" >&2; done
|
|
83
34
|
}
|
|
84
35
|
|
|
85
36
|
open_timeline() {
|
|
86
|
-
|
|
87
|
-
set_palette 2
|
|
88
|
-
echo -e "${GREY}┌${NC}" >&2
|
|
37
|
+
echo -e "┌" >&2
|
|
89
38
|
if [ -n "${1:-}" ]; then
|
|
90
|
-
echo -e "
|
|
39
|
+
echo -e "│ $1" >&2
|
|
91
40
|
fi
|
|
92
41
|
}
|
|
93
42
|
|
|
94
43
|
close_timeline() {
|
|
95
|
-
|
|
96
|
-
set_palette 2
|
|
97
|
-
echo -e "${GREY}└${NC}" >&2
|
|
44
|
+
echo -e "└" >&2
|
|
98
45
|
}
|
package/src/audits/run.ts
CHANGED
|
@@ -74,7 +74,7 @@ export function spawnAudit(root: string): Spawn {
|
|
|
74
74
|
// extending it. Spreading `process.env` alongside would put back the very
|
|
75
75
|
// variables it strips, and a hook's `GIT_DIR` would then point every
|
|
76
76
|
// history-reading verb at a repository nobody asked about.
|
|
77
|
-
env:
|
|
77
|
+
env: gitEnv(),
|
|
78
78
|
extendEnv: false,
|
|
79
79
|
})
|
|
80
80
|
|
package/src/claude/routing.ts
CHANGED
|
@@ -9,7 +9,7 @@ const TOKEN = /`([^`\s]+)`/g
|
|
|
9
9
|
|
|
10
10
|
/**
|
|
11
11
|
* A path a reader could open. Admits no `$`, `*`, or `=`, so an assignment
|
|
12
|
-
* (`
|
|
12
|
+
* (`NAME=value`) and a variable are both excluded.
|
|
13
13
|
*/
|
|
14
14
|
const PATH_TOKEN = /^[.A-Za-z0-9_][A-Za-z0-9._/-]*$/
|
|
15
15
|
|
package/src/cli/run.ts
CHANGED
|
@@ -2,7 +2,7 @@ import { join } from 'node:path'
|
|
|
2
2
|
import { execa } from 'execa'
|
|
3
3
|
|
|
4
4
|
export interface CliRunOptions {
|
|
5
|
-
/**
|
|
5
|
+
/** Closes the child's stdin, which makes it headless. Defaults to false. */
|
|
6
6
|
readonly nonInteractive?: boolean
|
|
7
7
|
/** Defaults to `inherit`, so a child prompt still reaches the operator. */
|
|
8
8
|
readonly stdin?: 'ignore' | 'inherit'
|
|
@@ -28,13 +28,12 @@ export function cliRun(
|
|
|
28
28
|
options: CliRunOptions = {},
|
|
29
29
|
): () => Promise<boolean> {
|
|
30
30
|
return async () => {
|
|
31
|
-
const
|
|
32
|
-
?
|
|
33
|
-
:
|
|
31
|
+
const stdin = options.nonInteractive
|
|
32
|
+
? 'ignore'
|
|
33
|
+
: (options.stdin ?? 'inherit')
|
|
34
34
|
|
|
35
35
|
const result = await execa('bun', [path, ...args], {
|
|
36
|
-
stdio: [
|
|
37
|
-
env,
|
|
36
|
+
stdio: [stdin, 'inherit', 'inherit'],
|
|
38
37
|
reject: false,
|
|
39
38
|
})
|
|
40
39
|
|