@erclx/canon 5.11.0 → 5.13.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/claude/.claude-plugin/plugin.json +1 -1
- package/claude/skills/api-design/REQUIREMENT.md +1 -1
- package/claude/skills/api-design/SKILL.md +1 -1
- package/claude/skills/api-design/references/adopted.md +1 -1
- package/claude/skills/auto-ship/REQUIREMENT.md +2 -0
- package/claude/skills/auto-ship/SKILL.md +7 -10
- package/claude/skills/auto-ship/references/failure-recovery.md +0 -1
- package/claude/skills/auto-ship/references/ui-checklist.md +21 -0
- package/claude/skills/build-in-slices/REQUIREMENT.md +45 -0
- package/claude/skills/build-in-slices/SKILL.md +94 -0
- package/claude/skills/build-in-slices/references/adopted.md +41 -0
- package/claude/skills/code-craft/REQUIREMENT.md +48 -0
- package/claude/skills/code-craft/SKILL.md +107 -0
- package/claude/skills/code-craft/references/adopted.md +52 -0
- package/claude/skills/codebase-layout/REQUIREMENT.md +1 -1
- package/claude/skills/git-pr/references/evidence.md +7 -7
- package/claude/skills/git-ship/REQUIREMENT.md +2 -1
- package/claude/skills/git-ship/SKILL.md +2 -2
- package/claude/skills/review-craft/REQUIREMENT.md +3 -0
- package/claude/skills/review-craft/SKILL.md +1 -1
- package/claude/skills/review-craft/references/adopted.md +8 -0
- package/claude/skills/review-craft/references/security.md +5 -0
- package/claude/skills/review-craft/references/ui.md +1 -2
- package/claude/skills/review-pr/SKILL.md +1 -1
- package/claude/skills/review-ui/REQUIREMENT.md +1 -0
- package/claude/skills/review-ui/SKILL.md +11 -0
- package/claude/skills/role-worker/REQUIREMENT.md +1 -0
- package/claude/skills/role-worker/SKILL.md +4 -0
- package/claude/skills/test-first/REQUIREMENT.md +3 -0
- package/claude/skills/test-first/SKILL.md +2 -1
- package/claude/skills/ui-checklist/SKILL.md +1 -18
- package/docs/agents/gate.md +1 -1
- package/docs/agents/hooks.md +2 -0
- package/docs/agents/install-and-sync.md +2 -2
- package/docs/agents/pr-evidence.md +26 -25
- package/docs/agents/pr-preview.md +40 -12
- package/docs/agents/tasks-validate.md +3 -3
- package/docs/target/migrations.md +1 -1
- package/docs/workflow/ai-workflow.md +2 -1
- package/docs/workflow/parallel-features.md +3 -3
- package/docs/workflow/skill-map.md +2 -0
- package/governance/rules/code/000-code.md +5 -1
- package/package.json +3 -4
- package/scripts/lib/sandbox-path.sh +1 -1
- package/src/claude/cases/misc.ts +10 -0
- package/src/claude/skills-audit.ts +2 -0
- package/src/commands/pr.ts +123 -21
- package/src/commands/tooling.ts +39 -16
- package/src/gate/stages.ts +10 -2
- package/src/pr/preview.ts +66 -0
- package/src/tasks/validate.ts +37 -27
- package/src/tooling/manifest.ts +16 -0
- package/src/tooling/verify-stack.ts +249 -0
- package/standards/tasks.md +1 -1
- package/tooling/base/configs/.husky/post-merge +30 -77
- package/tooling/base/configs/.husky/post-rewrite +3 -3
- package/tooling/base/reference.md +1 -1
- package/tooling/base/seeds/.cspell/tech-stack.txt +2 -0
- package/tooling/go/manifest.toml +3 -0
- package/tooling/go/reference.md +1 -0
- package/tooling/php/manifest.toml +3 -0
- package/tooling/php/reference.md +1 -0
- package/tooling/python/manifest.toml +3 -0
- package/tooling/python/reference.md +1 -1
- package/tooling/python/seeds/.cspell/tech-stack.txt +0 -1
- package/scripts/core/clean.sh +0 -36
- package/scripts/core/regen-agent-fixture.sh +0 -235
- package/scripts/core/regen-claude-copies.sh +0 -10
- package/scripts/core/regen-indexes.sh +0 -10
- package/scripts/core/snapshot.sh +0 -134
- package/scripts/core/update.sh +0 -35
- package/scripts/tooling/verify.sh +0 -184
package/scripts/core/clean.sh
DELETED
|
@@ -1,36 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env bash
|
|
2
|
-
set -e
|
|
3
|
-
set -o pipefail
|
|
4
|
-
|
|
5
|
-
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
6
|
-
PROJECT_ROOT="${PROJECT_ROOT:-$(cd "$SCRIPT_DIR/../.." && pwd)}"
|
|
7
|
-
|
|
8
|
-
source "$PROJECT_ROOT/scripts/lib/ui.sh"
|
|
9
|
-
|
|
10
|
-
check_dependencies() {
|
|
11
|
-
command -v bun >/dev/null 2>&1 || log_error "bun is not installed"
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
main() {
|
|
15
|
-
check_dependencies
|
|
16
|
-
|
|
17
|
-
echo -e "${GREY}┌${NC}"
|
|
18
|
-
|
|
19
|
-
echo -e "${GREY}├${NC} ${WHITE}Cleaning artifacts${NC}"
|
|
20
|
-
rm -rf node_modules
|
|
21
|
-
|
|
22
|
-
log_rem "node_modules/"
|
|
23
|
-
|
|
24
|
-
log_step "Cleaning cache"
|
|
25
|
-
bun pm cache rm
|
|
26
|
-
log_info "Package manager cache cleared"
|
|
27
|
-
|
|
28
|
-
log_step "Rehydrating project"
|
|
29
|
-
bun install
|
|
30
|
-
log_info "Dependencies installed"
|
|
31
|
-
|
|
32
|
-
echo -e "${GREY}└${NC}\n"
|
|
33
|
-
echo -e "${GREEN}✓ Clean complete${NC}"
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
main "$@"
|
|
@@ -1,235 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env bash
|
|
2
|
-
# Writes web/src/fixtures/agent-view.json from a real `canon sessions list
|
|
3
|
-
# --json` read of this repository's own sessions.
|
|
4
|
-
#
|
|
5
|
-
# The page it feeds re-creates an orchestrator dispatching workers, which is the
|
|
6
|
-
# one thing this toolkit does that no other surface here shows. A re-creation
|
|
7
|
-
# earns nothing if the rows behind it are invented, so the session half is a
|
|
8
|
-
# live listing and never a literal.
|
|
9
|
-
#
|
|
10
|
-
# Every role in the roster appears, not the workers alone. The controlling
|
|
11
|
-
# session is pinned above the bands and planners sit in them beside workers,
|
|
12
|
-
# which is what the surface being re-created does. An earlier version of this
|
|
13
|
-
# script filtered the listing down to sessions holding a feature branch, and
|
|
14
|
-
# that dropped the orchestrator and every planner, since a controlling session
|
|
15
|
-
# and a planning session both sit on the default branch. A page about a dispatch
|
|
16
|
-
# that excludes the dispatcher is the defect that filter shipped.
|
|
17
|
-
#
|
|
18
|
-
# Two fields cannot come from the listing and are transcribed by hand into the
|
|
19
|
-
# table below. The activity text is written by Claude Code into its own status
|
|
20
|
-
# line and no verb here reports it. The pull request number sits on a task file
|
|
21
|
-
# with nothing joining a session record to it, which the plan behind this script
|
|
22
|
-
# took as a trade rather than building the join. Both are recorded as
|
|
23
|
-
# transcribed in the fixture's own header, so a reader of the committed file can
|
|
24
|
-
# tell which half was measured and which was typed.
|
|
25
|
-
#
|
|
26
|
-
# The honesty cost this leaves open is the one assets/captures/install.html.tmpl
|
|
27
|
-
# already documents for the terminal frames: a hand-edit after generation
|
|
28
|
-
# defeats the discipline silently, and nothing here detects one.
|
|
29
|
-
#
|
|
30
|
-
# Clone-only. It reads this repository's own sessions, and a registry install
|
|
31
|
-
# carries no such listing.
|
|
32
|
-
set -e
|
|
33
|
-
set -o pipefail
|
|
34
|
-
|
|
35
|
-
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
36
|
-
PROJECT_ROOT="${PROJECT_ROOT:-$(cd "$SCRIPT_DIR/../.." && pwd)}"
|
|
37
|
-
|
|
38
|
-
OUT="$PROJECT_ROOT/web/src/fixtures/agent-view.json"
|
|
39
|
-
|
|
40
|
-
# Transcribed half, keyed by session name. The name is the only key that reaches
|
|
41
|
-
# every row: a controlling session and a planning session both sit on the
|
|
42
|
-
# default branch, so a branch cannot tell them apart or from each other. A
|
|
43
|
-
# dispatched session is named `<role>-<project>-<slug>` after the work it was
|
|
44
|
-
# given rather than after what it turned out to be doing, which is what makes
|
|
45
|
-
# the name stable enough to key on here.
|
|
46
|
-
#
|
|
47
|
-
# Each row is `name|activity|pullRequest|state|age`. The state is `pinned`,
|
|
48
|
-
# `working`, or `completed`, and it is where the row renders. An empty pull
|
|
49
|
-
# request field is a session that had not reached its ship step when the
|
|
50
|
-
# snapshot was taken.
|
|
51
|
-
#
|
|
52
|
-
# A pinned or working row has to appear in the live listing or this refuses, and
|
|
53
|
-
# its age comes from the listing rather than the table, so the trailing field is
|
|
54
|
-
# empty for those. A completed row names a session that has already exited, so
|
|
55
|
-
# no listing carries it, its age is transcribed with the rest of the row, and
|
|
56
|
-
# leaving that field blank would print an empty column on half the list.
|
|
57
|
-
# Re-transcribe this table against a fresh listing rather than editing the
|
|
58
|
-
# generated file.
|
|
59
|
-
#
|
|
60
|
-
# A planner belongs in this table as much as a worker does. `role-planner`
|
|
61
|
-
# forbids entering a worktree, so a planner registers on the default branch at
|
|
62
|
-
# launch and stays there for its whole life, which is why the branch filter this
|
|
63
|
-
# script used to carry dropped every one of them permanently rather than
|
|
64
|
-
# occasionally.
|
|
65
|
-
ROWS=(
|
|
66
|
-
"orchestrator-canon-lead|dispatching the planners that need a plan||pinned|"
|
|
67
|
-
"worker-canon-agent-view-and-deploy|PR #1510 open (landing page)|1510|working|" # canon-allow-reference: transcribed status text
|
|
68
|
-
"planner-canon-context-wireframe-draft|drafting the context and wireframe plan||working|"
|
|
69
|
-
"planner-canon-skill-coverage-pointers|reading the skill coverage pointers||working|"
|
|
70
|
-
"planner-canon-screenshot-trap-guard|planning the screenshot trap guard||working|"
|
|
71
|
-
"planner-canon-write-route-hook-bypass|plan written; tested write probe||completed|32m"
|
|
72
|
-
"planner-canon-indexes-list-lookup|plan and task board consistent||completed|13m"
|
|
73
|
-
"worker-canon-labels-scan-body-file|address-review pass finished|1508|completed|21m"
|
|
74
|
-
"worker-canon-web-context-entry|PR #1507 addressed with commit|1507|completed|20m" # canon-allow-reference: transcribed status text
|
|
75
|
-
)
|
|
76
|
-
|
|
77
|
-
# The listing reports a session's repository as the main `.git` directory, which
|
|
78
|
-
# is what this resolves whether the script runs from the main checkout or from a
|
|
79
|
-
# linked worktree. Comparing against PROJECT_ROOT instead would match nothing
|
|
80
|
-
# from a worktree, where the two differ.
|
|
81
|
-
MAIN_GIT_DIR="$(cd "$PROJECT_ROOT" && git rev-parse --path-format=absolute --git-common-dir)"
|
|
82
|
-
|
|
83
|
-
# `bun src/cli.ts` rather than `canon`, since a globally linked binary resolves
|
|
84
|
-
# to the main checkout no matter which worktree is running, and this script is
|
|
85
|
-
# run from a worktree as often as not.
|
|
86
|
-
SESSIONS_JSON="$(cd "$PROJECT_ROOT" && CANON_NON_INTERACTIVE=1 bun src/cli.ts sessions list --json 2>/dev/null)"
|
|
87
|
-
|
|
88
|
-
if [ -z "$SESSIONS_JSON" ]; then
|
|
89
|
-
echo "regen-agent-fixture: the session listing returned nothing, refusing to write an empty fixture" >&2
|
|
90
|
-
exit 1
|
|
91
|
-
fi
|
|
92
|
-
|
|
93
|
-
# The listing crosses into the eval as a file rather than an environment entry,
|
|
94
|
-
# since Linux caps a single env string at 128KB and a machine running many
|
|
95
|
-
# sessions can cross it, which fails the exec with E2BIG before any check can
|
|
96
|
-
# report a thing.
|
|
97
|
-
SESSIONS_FILE="$(mktemp)"
|
|
98
|
-
trap 'rm -f "$SESSIONS_FILE"' EXIT
|
|
99
|
-
printf '%s' "$SESSIONS_JSON" >"$SESSIONS_FILE"
|
|
100
|
-
|
|
101
|
-
TRANSCRIBED_ROWS="$(printf '%s\n' "${ROWS[@]}")"
|
|
102
|
-
READ_AT="$(date -u +%Y-%m-%dT%H:%M:%SZ)"
|
|
103
|
-
|
|
104
|
-
export SESSIONS_FILE OUT PROJECT_ROOT READ_AT TRANSCRIBED_ROWS MAIN_GIT_DIR
|
|
105
|
-
|
|
106
|
-
bun --eval '
|
|
107
|
-
const { readFileSync } = require("node:fs")
|
|
108
|
-
|
|
109
|
-
const { SESSIONS_FILE, OUT, PROJECT_ROOT, READ_AT, TRANSCRIBED_ROWS, MAIN_GIT_DIR } =
|
|
110
|
-
process.env
|
|
111
|
-
|
|
112
|
-
const listing = JSON.parse(readFileSync(SESSIONS_FILE, "utf8"))
|
|
113
|
-
|
|
114
|
-
const rows = TRANSCRIBED_ROWS.split("\n")
|
|
115
|
-
.map((line) => line.replace(/\s*#\s*canon-allow-reference:.*$/, "").trim())
|
|
116
|
-
.filter((line) => line !== "")
|
|
117
|
-
.map((line) => {
|
|
118
|
-
const [name, activity, pullRequest, state, age] = line.split("|")
|
|
119
|
-
return {
|
|
120
|
-
name,
|
|
121
|
-
activity,
|
|
122
|
-
pullRequest: pullRequest ? Number(pullRequest) : null,
|
|
123
|
-
state,
|
|
124
|
-
age: age || null,
|
|
125
|
-
}
|
|
126
|
-
})
|
|
127
|
-
|
|
128
|
-
// Narrowed to this repository, and the reason is disclosure rather than scope.
|
|
129
|
-
// `canon sessions list` is machine-wide, so an unfiltered read carries the
|
|
130
|
-
// session names and worktree paths of every other project on the machine, which
|
|
131
|
-
// here includes paths under a folder named `private` and several client names.
|
|
132
|
-
// This fixture renders on a public page, so the filter is what keeps them off
|
|
133
|
-
// it. The cost is a fidelity gap stated plainly: the real surface is
|
|
134
|
-
// machine-wide and the depiction is one repository, and that is a deliberate
|
|
135
|
-
// narrowing rather than an oversight.
|
|
136
|
-
//
|
|
137
|
-
// Keyed by name for the reason the shell comment gives: a session on the
|
|
138
|
-
// default branch has no other identity, and the orchestrator and every planner
|
|
139
|
-
// sit there.
|
|
140
|
-
const live = new Map(
|
|
141
|
-
listing.sessions
|
|
142
|
-
.filter((session) => session.repository === MAIN_GIT_DIR)
|
|
143
|
-
.map((session) => [session.name, session]),
|
|
144
|
-
)
|
|
145
|
-
|
|
146
|
-
// The real surface shows an age per row, so the fixture carries one. It is
|
|
147
|
-
// frozen at the read rather than live, which the rendered provenance line dates
|
|
148
|
-
// so a reader is never told a stale figure is current.
|
|
149
|
-
const ageLabel = (startedAt) => {
|
|
150
|
-
const minutes = Math.round(
|
|
151
|
-
(Date.parse(READ_AT) - Date.parse(startedAt)) / 60000,
|
|
152
|
-
)
|
|
153
|
-
if (!Number.isFinite(minutes) || minutes < 1) return "now"
|
|
154
|
-
if (minutes < 60) return `${minutes}m`
|
|
155
|
-
const hours = Math.round(minutes / 60)
|
|
156
|
-
return hours < 24 ? `${hours}h` : `${Math.round(hours / 24)}d`
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
// A row the page shows as running has to be running. Transcribing one that is
|
|
160
|
-
// not is the failure the live half exists to prevent, so it refuses rather than
|
|
161
|
-
// rendering.
|
|
162
|
-
const absent = rows.filter(
|
|
163
|
-
(row) => row.state !== "completed" && !live.has(row.name),
|
|
164
|
-
)
|
|
165
|
-
if (absent.length > 0) {
|
|
166
|
-
console.error(
|
|
167
|
-
"regen-agent-fixture: no live session for " +
|
|
168
|
-
absent.map((row) => row.name).join(", ") +
|
|
169
|
-
". Re-transcribe the table in this script against a fresh listing rather than editing the generated fixture.",
|
|
170
|
-
)
|
|
171
|
-
process.exit(1)
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
const sessions = rows.map((row) => {
|
|
175
|
-
const session = live.get(row.name)
|
|
176
|
-
return {
|
|
177
|
-
name: row.name,
|
|
178
|
-
branch: session ? session.branch : null,
|
|
179
|
-
state: row.state,
|
|
180
|
-
activity: row.activity,
|
|
181
|
-
pullRequest: row.pullRequest,
|
|
182
|
-
age: session ? ageLabel(session.startedAt) : row.age,
|
|
183
|
-
}
|
|
184
|
-
})
|
|
185
|
-
|
|
186
|
-
const ageless = sessions.filter((entry) => !entry.age)
|
|
187
|
-
if (ageless.length > 0) {
|
|
188
|
-
console.error(
|
|
189
|
-
"regen-agent-fixture: no age for " +
|
|
190
|
-
ageless.map((entry) => entry.name).join(", ") +
|
|
191
|
-
". A completed row carries its age in the table, since the session has exited and no listing reports one.",
|
|
192
|
-
)
|
|
193
|
-
process.exit(1)
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
// What the real surface prints above its own list. The remainder is every
|
|
197
|
-
// session on the machine this page has no room to show, which is a real count.
|
|
198
|
-
const summary = {
|
|
199
|
-
working: sessions.filter((entry) => entry.state === "working").length,
|
|
200
|
-
completed: sessions.filter((entry) => entry.state === "completed").length,
|
|
201
|
-
more: Math.max(0, listing.sessions.length - live.size),
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
const fixture = {
|
|
205
|
-
// Read by web/src/components/agent-view.astro. Every field below is either a
|
|
206
|
-
// live read or a hand transcription, and this header is what says which.
|
|
207
|
-
generatedBy: "scripts/core/regen-agent-fixture.sh",
|
|
208
|
-
readAt: READ_AT,
|
|
209
|
-
source: {
|
|
210
|
-
command: "canon sessions list --json",
|
|
211
|
-
live: [
|
|
212
|
-
"name",
|
|
213
|
-
"branch",
|
|
214
|
-
"age",
|
|
215
|
-
"that every pinned and working row is running",
|
|
216
|
-
"the remainder count",
|
|
217
|
-
],
|
|
218
|
-
transcribed: {
|
|
219
|
-
activity:
|
|
220
|
-
"written by Claude Code into its own status line, which no verb here reports",
|
|
221
|
-
pullRequest:
|
|
222
|
-
"read off a task file by hand, since nothing joins a session record to one",
|
|
223
|
-
state:
|
|
224
|
-
"a completed session has already exited, so no listing carries the row at all",
|
|
225
|
-
},
|
|
226
|
-
},
|
|
227
|
-
summary,
|
|
228
|
-
sessions,
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
await Bun.write(OUT, JSON.stringify(fixture, null, 2) + "\n")
|
|
232
|
-
console.error(
|
|
233
|
-
`regen-agent-fixture: wrote ${sessions.length} session rows to ${OUT.replace(PROJECT_ROOT + "/", "")}`,
|
|
234
|
-
)
|
|
235
|
-
'
|
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env bash
|
|
2
|
-
set -e
|
|
3
|
-
set -o pipefail
|
|
4
|
-
|
|
5
|
-
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
6
|
-
PROJECT_ROOT="${PROJECT_ROOT:-$(cd "$SCRIPT_DIR/../.." && pwd)}"
|
|
7
|
-
|
|
8
|
-
# `.claude/rules/` is a subset rather than a mirror, so it resolves through the
|
|
9
|
-
# stack machinery. The record naming the subset is `internal/governance.toml`.
|
|
10
|
-
bun "$PROJECT_ROOT/src/cli.ts" gov regen --root "$PROJECT_ROOT"
|
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env bash
|
|
2
|
-
set -e
|
|
3
|
-
set -o pipefail
|
|
4
|
-
|
|
5
|
-
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
6
|
-
PROJECT_ROOT="${PROJECT_ROOT:-$(cd "$SCRIPT_DIR/../.." && pwd)}"
|
|
7
|
-
|
|
8
|
-
source "$PROJECT_ROOT/scripts/lib/ui.sh"
|
|
9
|
-
|
|
10
|
-
bun "$PROJECT_ROOT/src/cli.ts" indexes regen --root "$PROJECT_ROOT" --json >/dev/null
|
package/scripts/core/snapshot.sh
DELETED
|
@@ -1,134 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env bash
|
|
2
|
-
set -e
|
|
3
|
-
set -o pipefail
|
|
4
|
-
|
|
5
|
-
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
6
|
-
PROJECT_ROOT="${PROJECT_ROOT:-$(cd "$SCRIPT_DIR/../.." && pwd)}"
|
|
7
|
-
|
|
8
|
-
# shellcheck source=/dev/null
|
|
9
|
-
source "$PROJECT_ROOT/scripts/lib/ui.sh"
|
|
10
|
-
|
|
11
|
-
NESTED="${VERIFY_NESTED:-false}"
|
|
12
|
-
|
|
13
|
-
OUTPUT_FILE=".canon/tmp/project/PROJECT-SNAPSHOT.md"
|
|
14
|
-
|
|
15
|
-
check_dependencies() {
|
|
16
|
-
command -v find >/dev/null 2>&1 || log_error "find not installed"
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
load_gitignore_patterns() {
|
|
20
|
-
if [ ! -f ".gitignore" ]; then
|
|
21
|
-
echo ""
|
|
22
|
-
return
|
|
23
|
-
fi
|
|
24
|
-
grep -v '^\s*#' .gitignore | grep -v '^\s*$' | sed 's|/$||'
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
is_ignored() {
|
|
28
|
-
local name="$1"
|
|
29
|
-
local patterns="$2"
|
|
30
|
-
|
|
31
|
-
[ "$name" = ".git" ] && return 0
|
|
32
|
-
[ "$name" = "_" ] && return 0
|
|
33
|
-
|
|
34
|
-
while IFS= read -r pattern; do
|
|
35
|
-
[ -z "$pattern" ] && continue
|
|
36
|
-
# shellcheck disable=SC2053
|
|
37
|
-
if [[ "$name" == $pattern ]]; then
|
|
38
|
-
return 0
|
|
39
|
-
fi
|
|
40
|
-
done <<<"$patterns"
|
|
41
|
-
|
|
42
|
-
return 1
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
build_tree() {
|
|
46
|
-
local dir="${1:-.}"
|
|
47
|
-
local prefix="${2:-}"
|
|
48
|
-
local patterns="${3:-}"
|
|
49
|
-
local entries=()
|
|
50
|
-
|
|
51
|
-
while IFS= read -r entry; do
|
|
52
|
-
local name
|
|
53
|
-
name=$(basename "$entry")
|
|
54
|
-
is_ignored "$name" "$patterns" && continue
|
|
55
|
-
entries+=("$entry")
|
|
56
|
-
done < <(find "$dir" -maxdepth 1 -mindepth 1 | sort)
|
|
57
|
-
|
|
58
|
-
local count=${#entries[@]}
|
|
59
|
-
local i=0
|
|
60
|
-
|
|
61
|
-
for entry in "${entries[@]}"; do
|
|
62
|
-
i=$((i + 1))
|
|
63
|
-
local name
|
|
64
|
-
name=$(basename "$entry")
|
|
65
|
-
local connector="├──"
|
|
66
|
-
local child_prefix="${prefix}│ "
|
|
67
|
-
|
|
68
|
-
if [ "$i" -eq "$count" ]; then
|
|
69
|
-
connector="└──"
|
|
70
|
-
child_prefix="${prefix} "
|
|
71
|
-
fi
|
|
72
|
-
|
|
73
|
-
if [ -d "$entry" ]; then
|
|
74
|
-
echo "${prefix}${connector} ${name}/"
|
|
75
|
-
build_tree "$entry" "$child_prefix" "$patterns"
|
|
76
|
-
else
|
|
77
|
-
echo "${prefix}${connector} ${name}"
|
|
78
|
-
fi
|
|
79
|
-
done
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
write_snapshot() {
|
|
83
|
-
local project_name
|
|
84
|
-
project_name=$(basename "$PWD")
|
|
85
|
-
local patterns
|
|
86
|
-
patterns=$(load_gitignore_patterns)
|
|
87
|
-
|
|
88
|
-
mkdir -p "$(dirname "$OUTPUT_FILE")"
|
|
89
|
-
|
|
90
|
-
{
|
|
91
|
-
echo "# Project Snapshot: $project_name"
|
|
92
|
-
echo ""
|
|
93
|
-
echo "## Structure"
|
|
94
|
-
echo ""
|
|
95
|
-
echo '```'
|
|
96
|
-
build_tree "." "" "$patterns"
|
|
97
|
-
echo '```'
|
|
98
|
-
echo ""
|
|
99
|
-
|
|
100
|
-
if [ -f "package.json" ]; then
|
|
101
|
-
echo "## package.json"
|
|
102
|
-
echo ""
|
|
103
|
-
echo '```json'
|
|
104
|
-
cat package.json
|
|
105
|
-
echo '```'
|
|
106
|
-
echo ""
|
|
107
|
-
fi
|
|
108
|
-
} >"$OUTPUT_FILE"
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
main() {
|
|
112
|
-
check_dependencies
|
|
113
|
-
|
|
114
|
-
if [ "$NESTED" = false ]; then open_timeline; fi
|
|
115
|
-
|
|
116
|
-
log_step "Snapshot"
|
|
117
|
-
write_snapshot
|
|
118
|
-
log_info "Written to $OUTPUT_FILE"
|
|
119
|
-
|
|
120
|
-
# The whole frame goes to stderr, since the run writes its document to a file
|
|
121
|
-
# and leaves stdout carrying nothing. Edges on stdout with the body on stderr
|
|
122
|
-
# would capture as a frame with no body inside it.
|
|
123
|
-
if [ "$NESTED" = false ]; then
|
|
124
|
-
close_timeline
|
|
125
|
-
# All six are declared to scope what `set_palette` assigns, so the four this
|
|
126
|
-
# line never spells stay out of the globals a sourcing script reads.
|
|
127
|
-
# shellcheck disable=SC2034
|
|
128
|
-
local GREEN RED YELLOW WHITE GREY NC
|
|
129
|
-
set_palette 2
|
|
130
|
-
echo -e "\n${GREEN}✓ Snapshot complete${NC}" >&2
|
|
131
|
-
fi
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
main "$@"
|
package/scripts/core/update.sh
DELETED
|
@@ -1,35 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env bash
|
|
2
|
-
set -e
|
|
3
|
-
set -o pipefail
|
|
4
|
-
|
|
5
|
-
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
6
|
-
PROJECT_ROOT="${PROJECT_ROOT:-$(cd "$SCRIPT_DIR/../.." && pwd)}"
|
|
7
|
-
|
|
8
|
-
source "$PROJECT_ROOT/scripts/lib/ui.sh"
|
|
9
|
-
|
|
10
|
-
check_dependencies() {
|
|
11
|
-
command -v bun >/dev/null 2>&1 || log_error "bun is not installed"
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
main() {
|
|
15
|
-
check_dependencies
|
|
16
|
-
|
|
17
|
-
echo -e "${GREY}┌${NC}"
|
|
18
|
-
|
|
19
|
-
echo -e "${GREY}├${NC} ${WHITE}Interactive dependency update${NC}"
|
|
20
|
-
echo -e "${GREY}│${NC}"
|
|
21
|
-
bun update --interactive
|
|
22
|
-
|
|
23
|
-
log_step "Verifying project health"
|
|
24
|
-
if [ -f "$PROJECT_ROOT/src/cli.ts" ]; then
|
|
25
|
-
bun "$PROJECT_ROOT/src/cli.ts" gate run --nested
|
|
26
|
-
log_info "All checks passed"
|
|
27
|
-
else
|
|
28
|
-
log_warn "Verification entry point not found, skipping."
|
|
29
|
-
fi
|
|
30
|
-
|
|
31
|
-
echo -e "${GREY}└${NC}\n"
|
|
32
|
-
echo -e "${GREEN}✓ Update complete${NC}"
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
main "$@"
|
|
@@ -1,184 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env bash
|
|
2
|
-
set -e
|
|
3
|
-
set -o pipefail
|
|
4
|
-
|
|
5
|
-
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
6
|
-
PROJECT_ROOT="${PROJECT_ROOT:-$(dirname "$(dirname "$SCRIPT_DIR")")}"
|
|
7
|
-
|
|
8
|
-
source "$PROJECT_ROOT/scripts/lib/ui.sh"
|
|
9
|
-
source "$PROJECT_ROOT/scripts/lib/tooling.sh"
|
|
10
|
-
|
|
11
|
-
show_help() {
|
|
12
|
-
echo -e "${GREY}┌${NC}"
|
|
13
|
-
echo -e "${GREY}├${NC} ${WHITE}Usage:${NC} canon tooling verify <stack> [options]"
|
|
14
|
-
echo -e "${GREY}│${NC}"
|
|
15
|
-
echo -e "${GREY}│${NC} ${WHITE}Options:${NC}"
|
|
16
|
-
echo -e "${GREY}│${NC} --keep ${GREY}# Keep tmp dir after run for inspection${NC}"
|
|
17
|
-
echo -e "${GREY}│${NC} -h, --help ${GREY}# Show this help message${NC}"
|
|
18
|
-
echo -e "${GREY}│${NC}"
|
|
19
|
-
echo -e "${GREY}│${NC} Scaffolds <stack> into .canon/tmp/runs/verify-<stack>/, syncs the full"
|
|
20
|
-
echo -e "${GREY}│${NC} layer chain, and runs check + test:e2e + screenshot."
|
|
21
|
-
echo -e "${GREY}└${NC}"
|
|
22
|
-
exit 0
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
read_manifest_field() {
|
|
26
|
-
local toml="$1"
|
|
27
|
-
local section="$2"
|
|
28
|
-
local field="$3"
|
|
29
|
-
awk -v s="$section" -v f="$field" '
|
|
30
|
-
$0 == "[" s "]" { in_section = 1; next }
|
|
31
|
-
in_section && /^\[/ { exit }
|
|
32
|
-
in_section && match($0, "^" f "[[:space:]]*=") {
|
|
33
|
-
val = substr($0, RLENGTH + 1)
|
|
34
|
-
sub(/^[[:space:]]+/, "", val)
|
|
35
|
-
sub(/^"/, "", val)
|
|
36
|
-
sub(/"$/, "", val)
|
|
37
|
-
print val
|
|
38
|
-
exit
|
|
39
|
-
}
|
|
40
|
-
' "$toml"
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
run_phase() {
|
|
44
|
-
local name="$1"
|
|
45
|
-
shift
|
|
46
|
-
log_step "$name"
|
|
47
|
-
if "$@" >/tmp/verify-phase.log 2>&1; then
|
|
48
|
-
log_info "$name passed"
|
|
49
|
-
RESULTS+=("$name:pass")
|
|
50
|
-
return 0
|
|
51
|
-
fi
|
|
52
|
-
log_warn "$name failed"
|
|
53
|
-
tail -20 /tmp/verify-phase.log >&2
|
|
54
|
-
RESULTS+=("$name:fail")
|
|
55
|
-
FAILED=1
|
|
56
|
-
return 1
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
main() {
|
|
60
|
-
local stack=""
|
|
61
|
-
local keep=0
|
|
62
|
-
|
|
63
|
-
while [[ $# -gt 0 ]]; do
|
|
64
|
-
case "$1" in
|
|
65
|
-
-h | --help) show_help ;;
|
|
66
|
-
--keep)
|
|
67
|
-
keep=1
|
|
68
|
-
shift
|
|
69
|
-
;;
|
|
70
|
-
-*)
|
|
71
|
-
log_error "Unknown option: $1"
|
|
72
|
-
;;
|
|
73
|
-
*)
|
|
74
|
-
if [ -z "$stack" ]; then
|
|
75
|
-
stack="$1"
|
|
76
|
-
shift
|
|
77
|
-
else
|
|
78
|
-
log_error "Unexpected argument: $1"
|
|
79
|
-
fi
|
|
80
|
-
;;
|
|
81
|
-
esac
|
|
82
|
-
done
|
|
83
|
-
|
|
84
|
-
[ -z "$stack" ] && log_error "Stack required. See --help."
|
|
85
|
-
|
|
86
|
-
export CI=1
|
|
87
|
-
|
|
88
|
-
if is_tooling_stack_excluded "$stack"; then
|
|
89
|
-
log_error "Stack '$stack' is excluded from tooling."
|
|
90
|
-
fi
|
|
91
|
-
|
|
92
|
-
local manifest="$PROJECT_ROOT/tooling/$stack/manifest.toml"
|
|
93
|
-
[ ! -f "$manifest" ] && log_error "No manifest at $manifest"
|
|
94
|
-
|
|
95
|
-
local scaffold
|
|
96
|
-
scaffold=$(read_manifest_field "$manifest" "stack" "scaffold")
|
|
97
|
-
[ -z "$scaffold" ] && log_error "Stack '$stack' has no scaffold command in manifest."
|
|
98
|
-
|
|
99
|
-
local prepare
|
|
100
|
-
prepare=$(read_manifest_field "$manifest" "verify" "prepare")
|
|
101
|
-
|
|
102
|
-
open_timeline "canon tooling verify $stack"
|
|
103
|
-
trap close_timeline EXIT
|
|
104
|
-
|
|
105
|
-
local tmp_root="$PROJECT_ROOT/.canon/tmp/runs"
|
|
106
|
-
local tmp_dir="$tmp_root/verify-$stack"
|
|
107
|
-
|
|
108
|
-
mkdir -p "$tmp_root"
|
|
109
|
-
rm -rf "$tmp_dir"
|
|
110
|
-
|
|
111
|
-
local project_name="verify-$stack"
|
|
112
|
-
local scaffold_cmd="${scaffold//\{\{name\}\}/$project_name}"
|
|
113
|
-
|
|
114
|
-
RESULTS=()
|
|
115
|
-
FAILED=0
|
|
116
|
-
|
|
117
|
-
log_step "Scaffolding $stack"
|
|
118
|
-
(cd "$tmp_root" && eval "$scaffold_cmd") >/tmp/verify-scaffold.log 2>&1 || {
|
|
119
|
-
log_warn "Scaffold failed"
|
|
120
|
-
tail -20 /tmp/verify-scaffold.log >&2
|
|
121
|
-
FAILED=1
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
if [ ! -d "$tmp_dir" ]; then
|
|
125
|
-
log_error "Scaffold did not produce $tmp_dir"
|
|
126
|
-
fi
|
|
127
|
-
|
|
128
|
-
(cd "$tmp_dir" && git init -q && git add . && git commit -m "chore: verify scaffold" -q --no-verify)
|
|
129
|
-
|
|
130
|
-
if [ -n "$prepare" ]; then
|
|
131
|
-
run_phase "Prepare" bash -c "cd '$tmp_dir' && $prepare"
|
|
132
|
-
fi
|
|
133
|
-
|
|
134
|
-
log_info "Sync resolves via bun $PROJECT_ROOT/src/cli.ts, not PATH canon"
|
|
135
|
-
run_phase "Sync" bash -c "cd '$tmp_dir' && CANON_NON_INTERACTIVE=1 bun '$PROJECT_ROOT/src/cli.ts' tooling sync $stack . --write"
|
|
136
|
-
|
|
137
|
-
if [ -f "$tmp_dir/package.json" ]; then
|
|
138
|
-
run_phase "lint:fix" bash -c "cd '$tmp_dir' && bun run lint:fix"
|
|
139
|
-
run_phase "check" bash -c "cd '$tmp_dir' && bun run check"
|
|
140
|
-
|
|
141
|
-
if grep -q '"test:e2e"' "$tmp_dir/package.json"; then
|
|
142
|
-
run_phase "test:e2e" bash -c "cd '$tmp_dir' && bun run test:e2e"
|
|
143
|
-
fi
|
|
144
|
-
|
|
145
|
-
if grep -q '"screenshot"' "$tmp_dir/package.json"; then
|
|
146
|
-
if run_phase "screenshot" bash -c "cd '$tmp_dir' && timeout 60 bun run screenshot"; then
|
|
147
|
-
local shots
|
|
148
|
-
shots=$(find "$tmp_dir/screenshots" -type f -name '*.png' 2>/dev/null | wc -l)
|
|
149
|
-
if [ "$shots" -eq 0 ]; then
|
|
150
|
-
log_warn "screenshot produced no png files"
|
|
151
|
-
RESULTS+=("screenshot-artifacts:fail")
|
|
152
|
-
FAILED=1
|
|
153
|
-
else
|
|
154
|
-
RESULTS+=("screenshot-artifacts:pass")
|
|
155
|
-
fi
|
|
156
|
-
fi
|
|
157
|
-
fi
|
|
158
|
-
fi
|
|
159
|
-
|
|
160
|
-
log_step "Results"
|
|
161
|
-
for r in "${RESULTS[@]}"; do
|
|
162
|
-
local name="${r%:*}"
|
|
163
|
-
local status="${r##*:}"
|
|
164
|
-
if [ "$status" = "pass" ]; then
|
|
165
|
-
log_info "$name"
|
|
166
|
-
else
|
|
167
|
-
log_warn "$name"
|
|
168
|
-
fi
|
|
169
|
-
done
|
|
170
|
-
|
|
171
|
-
if [ "$keep" -eq 0 ] && [ "$FAILED" -eq 0 ]; then
|
|
172
|
-
rm -rf "$tmp_dir"
|
|
173
|
-
else
|
|
174
|
-
log_info "Kept $tmp_dir"
|
|
175
|
-
fi
|
|
176
|
-
|
|
177
|
-
if [ "$FAILED" -ne 0 ]; then
|
|
178
|
-
exit 1
|
|
179
|
-
fi
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
|
|
183
|
-
main "$@"
|
|
184
|
-
fi
|