@erclx/canon 4.69.0 → 4.71.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/README.md +1 -1
- package/claude/.claude-plugin/plugin.json +1 -1
- package/claude/skills/auto-ship/SKILL.md +1 -1
- package/claude/skills/canon-cli/SKILL.md +4 -0
- package/claude/skills/deploy-cloudflare/REQUIREMENT.md +37 -0
- package/claude/skills/deploy-cloudflare/SKILL.md +61 -0
- package/claude/skills/draft-and-pick/REQUIREMENT.md +1 -1
- package/claude/skills/draft-and-pick/SKILL.md +1 -1
- package/claude/skills/{identity → draft-identity}/REQUIREMENT.md +2 -2
- package/claude/skills/{identity → draft-identity}/SKILL.md +2 -2
- package/claude/skills/{canon-screencast → draft-screencast}/REQUIREMENT.md +4 -4
- package/claude/skills/{canon-screencast → draft-screencast}/SKILL.md +4 -4
- package/claude/skills/{canon-slides-draft → draft-slides}/REQUIREMENT.md +3 -3
- package/claude/skills/{canon-slides-draft → draft-slides}/SKILL.md +2 -2
- package/claude/skills/{canon-frames-read → read-frames}/REQUIREMENT.md +2 -2
- package/claude/skills/{canon-frames-read → read-frames}/SKILL.md +3 -3
- package/claude/skills/{canon-record → record-screencast}/REQUIREMENT.md +5 -5
- package/claude/skills/{canon-record → record-screencast}/SKILL.md +4 -4
- package/claude/skills/review-pr/SKILL.md +55 -7
- package/claude/skills/role-orchestrator/SKILL.md +2 -1
- package/claude/skills/role-orchestrator/references/orchestrator-poll.md +7 -3
- package/claude/skills/role-orchestrator/scripts/poll.sh +79 -34
- package/claude/skills/role-worker/SKILL.md +2 -1
- package/claude/skills/ux-audit/SKILL.md +3 -0
- package/docs/agents/commands.md +2 -0
- package/docs/agents/demo.md +3 -3
- package/docs/agents/index.md +1 -1
- package/docs/agents/pr-reads.md +47 -12
- package/docs/agents/tasks.md +46 -3
- package/docs/workflow/ai-workflow.md +19 -19
- package/docs/workflow/visual-design-workflow.md +1 -1
- package/package.json +3 -2
- package/scripts/core/regen-web-previews.ts +94 -0
- package/src/claude/cases/misc.ts +5 -1
- package/src/claude/cases/workflow.ts +4 -4
- package/src/commands/demo.ts +1 -1
- package/src/commands/pr.ts +130 -1
- package/src/commands/tasks.ts +178 -1
- package/src/commands/teach.ts +2 -0
- package/src/demo/beats.ts +1 -1
- package/src/gate/measures.ts +66 -0
- package/src/gate/stages.ts +11 -0
- package/src/migrate/skill-names.ts +25 -1
- package/src/pr/review-scope.ts +177 -0
- package/src/tasks/archive.ts +206 -3
- package/src/tasks/label.ts +14 -6
- package/src/tasks/validate.ts +22 -0
- package/src/teach/workspace.ts +60 -23
- package/standards/plan.md +1 -1
- package/standards/tasks.md +15 -1
- package/tooling/cloudflare/configs/.github/workflows/deploy.yml +103 -0
- package/tooling/cloudflare/manifest.toml +5 -0
- package/tooling/cloudflare/reference.md +24 -0
package/src/teach/workspace.ts
CHANGED
|
@@ -42,9 +42,20 @@ export const TEACH_ASSETS = 'assets'
|
|
|
42
42
|
* The one stylesheet every lesson in a workspace links. The name is fixed here
|
|
43
43
|
* rather than chosen per lesson, because the second lesson has to reach the
|
|
44
44
|
* file the first one wrote and a name composed twice is a name that can differ.
|
|
45
|
+
* This file is workspace-owned: `writeStylesheet` writes it once and leaves it
|
|
46
|
+
* alone, since a lesson adds its own rules here. It imports `TEACH_STYLESHEET_BASE`
|
|
47
|
+
* for the tokens and components, which is the half a resync may always replace.
|
|
45
48
|
*/
|
|
46
49
|
export const TEACH_STYLESHEET = 'course.css'
|
|
47
50
|
|
|
51
|
+
/**
|
|
52
|
+
* The seeded half of a workspace's styling, imported by `TEACH_STYLESHEET`.
|
|
53
|
+
* `writeStylesheet` rewrites this file from the design source on every call,
|
|
54
|
+
* since nothing workspace-authored lives in it, which is what lets a design
|
|
55
|
+
* token move without a `--force` flag discarding a workspace's lesson rules.
|
|
56
|
+
*/
|
|
57
|
+
export const TEACH_STYLESHEET_BASE = 'base.css'
|
|
58
|
+
|
|
48
59
|
/**
|
|
49
60
|
* The mission heading whose list a session reads as exit criteria. The writer,
|
|
50
61
|
* the reader below, and the record validator all match this one spelling, so a
|
|
@@ -1001,31 +1012,40 @@ export interface StylesheetWritten {
|
|
|
1001
1012
|
readonly slug: string
|
|
1002
1013
|
/** Relative to the root, so a caller prints a path a reader can open. */
|
|
1003
1014
|
readonly path: string
|
|
1004
|
-
/**
|
|
1015
|
+
/** Relative to the root, the re-synced seed `path` imports. */
|
|
1016
|
+
readonly basePath: string
|
|
1017
|
+
/** False when the workspace already held `path` and this call left it alone. */
|
|
1005
1018
|
readonly written: boolean
|
|
1006
1019
|
}
|
|
1007
1020
|
|
|
1008
1021
|
export type StylesheetOutcome = StylesheetWritten | TeachRefused
|
|
1009
1022
|
|
|
1010
|
-
const
|
|
1023
|
+
const STYLESHEET_BASE_BANNER = [
|
|
1011
1024
|
'Seeded by `canon teach stylesheet` from the design source in',
|
|
1012
|
-
'src/design/tokens.ts. The tokens and the two
|
|
1013
|
-
'system this workspace renders in.
|
|
1014
|
-
'
|
|
1015
|
-
'
|
|
1025
|
+
'src/design/tokens.ts, and rewritten on every call. The tokens and the two',
|
|
1026
|
+
'components below are the system this workspace renders in. Read a value',
|
|
1027
|
+
'through its custom property rather than restating the hex, which is what',
|
|
1028
|
+
'let one workspace fork the palette from every other. Add lesson rules to',
|
|
1029
|
+
`${TEACH_STYLESHEET} instead, which imports this file and is never rewritten.`,
|
|
1016
1030
|
].join('\n ')
|
|
1017
1031
|
|
|
1032
|
+
const stylesheetSeed = (): string =>
|
|
1033
|
+
`@import url('./${TEACH_STYLESHEET_BASE}');\n`
|
|
1034
|
+
|
|
1018
1035
|
/**
|
|
1019
|
-
* Writes a workspace's
|
|
1036
|
+
* Writes a workspace's stylesheet pair from the design source.
|
|
1020
1037
|
*
|
|
1021
|
-
* Every workspace used to carry
|
|
1022
|
-
* palette forked once per workspace. The
|
|
1023
|
-
* and
|
|
1024
|
-
*
|
|
1038
|
+
* Every workspace used to carry one hand-authored file, which is how the
|
|
1039
|
+
* course palette forked once per workspace. The names are fixed at
|
|
1040
|
+
* `TEACH_STYLESHEET` and `TEACH_STYLESHEET_BASE`, and the folder at
|
|
1041
|
+
* `TEACH_ASSETS`, for the same reason a second lesson has to reach the files
|
|
1042
|
+
* the first one wrote and names composed twice can differ.
|
|
1025
1043
|
*
|
|
1026
|
-
*
|
|
1027
|
-
*
|
|
1028
|
-
*
|
|
1044
|
+
* `TEACH_STYLESHEET_BASE` carries the tokens and the components and is
|
|
1045
|
+
* rewritten every call, since nothing workspace-authored lives in it.
|
|
1046
|
+
* `TEACH_STYLESHEET` imports it, carries a workspace's own lesson rules, and
|
|
1047
|
+
* is written once and left alone, since overwriting it would discard them.
|
|
1048
|
+
* `--force` is the caller saying it wants that file's seed back too.
|
|
1029
1049
|
*/
|
|
1030
1050
|
export async function writeStylesheet(
|
|
1031
1051
|
root: string,
|
|
@@ -1036,22 +1056,39 @@ export async function writeStylesheet(
|
|
|
1036
1056
|
if (!found.ok) return found
|
|
1037
1057
|
|
|
1038
1058
|
const workspace = found.workspace
|
|
1039
|
-
const
|
|
1059
|
+
const assetsDir = join(workspace.path, TEACH_ASSETS)
|
|
1060
|
+
const baseRel = join(assetsDir, TEACH_STYLESHEET_BASE)
|
|
1061
|
+
const rel = join(assetsDir, TEACH_STYLESHEET)
|
|
1062
|
+
const basePath = join(root, baseRel)
|
|
1040
1063
|
const path = join(root, rel)
|
|
1041
1064
|
|
|
1042
|
-
|
|
1043
|
-
return { ok: true, slug: workspace.slug, path: rel, written: false }
|
|
1044
|
-
}
|
|
1045
|
-
|
|
1046
|
-
await mkdir(join(root, workspace.path, TEACH_ASSETS), { recursive: true })
|
|
1065
|
+
await mkdir(join(root, assetsDir), { recursive: true })
|
|
1047
1066
|
await writeFile(
|
|
1048
|
-
|
|
1067
|
+
basePath,
|
|
1049
1068
|
buildDesignCss(undefined, {
|
|
1050
|
-
banner:
|
|
1069
|
+
banner: STYLESHEET_BASE_BANNER,
|
|
1051
1070
|
embedFonts: TEACH_FONT_FACES,
|
|
1052
1071
|
components: TEACH_STYLESHEET_COMPONENTS,
|
|
1053
1072
|
}),
|
|
1054
1073
|
)
|
|
1055
1074
|
|
|
1056
|
-
|
|
1075
|
+
if (existsSync(path) && !force) {
|
|
1076
|
+
return {
|
|
1077
|
+
ok: true,
|
|
1078
|
+
slug: workspace.slug,
|
|
1079
|
+
path: rel,
|
|
1080
|
+
basePath: baseRel,
|
|
1081
|
+
written: false,
|
|
1082
|
+
}
|
|
1083
|
+
}
|
|
1084
|
+
|
|
1085
|
+
await writeFile(path, stylesheetSeed())
|
|
1086
|
+
|
|
1087
|
+
return {
|
|
1088
|
+
ok: true,
|
|
1089
|
+
slug: workspace.slug,
|
|
1090
|
+
path: rel,
|
|
1091
|
+
basePath: baseRel,
|
|
1092
|
+
written: true,
|
|
1093
|
+
}
|
|
1057
1094
|
}
|
package/standards/plan.md
CHANGED
|
@@ -124,7 +124,7 @@ This contract inverts the one an intake folder keeps, where an empty slot means
|
|
|
124
124
|
- Write the plan before implementation starts, and treat it as the scope of the run that executes it.
|
|
125
125
|
- Keep every plan at one root. A plan copied into each parallel working tree forks, and the copies answer the same question differently.
|
|
126
126
|
- Amend the plan in place when a decision changes mid-flight. Do not append a second passage narrating the change, which leaves a reader to work out which of two answers is current. An execution-time deviation from a suggestion is one such amendment, and the contract above fixes which line takes it.
|
|
127
|
-
- Move the plan to `.canon/plans/archive/` when the work it describes ships. Never delete it, because the plan is where the rejected alternative is written down and nothing else records it. The archive nests inside `.canon/plans/` rather than sitting beside it as `.claude/plans-archive/`, so an older layout is a flat sibling by that name and a current one is not.
|
|
127
|
+
- Move the plan to `.canon/plans/archive/` when the work it describes ships or is declined. Never delete it, because the plan is where the rejected alternative is written down and nothing else records it. The archive nests inside `.canon/plans/` rather than sitting beside it as `.claude/plans-archive/`, so an older layout is a flat sibling by that name and a current one is not.
|
|
128
128
|
- Write the plan in the same session that opens the task it serves. The session executing it later inherits reasoning it would otherwise re-derive.
|
|
129
129
|
|
|
130
130
|
## Anti-patterns
|
package/standards/tasks.md
CHANGED
|
@@ -31,6 +31,8 @@ Does not govern:
|
|
|
31
31
|
├── priority.md ← hand-maintained execution order
|
|
32
32
|
├── backlog.md ← unordered, what is not being scheduled
|
|
33
33
|
├── session-<slug>.md ← optional, what a compaction is about to destroy
|
|
34
|
+
├── archive/ ← shipped tasks, moved by canon tasks archive
|
|
35
|
+
├── declined/ ← decided-against tasks, moved by canon tasks decline
|
|
34
36
|
├── v09.0-sync-paths.md # canon-allow-reference: illustrates the vXX.Y-slug filename this section defines
|
|
35
37
|
└── v13.0-toolkit-drift.md # canon-allow-reference: illustrates the vXX.Y-slug filename this section defines
|
|
36
38
|
```
|
|
@@ -207,7 +209,7 @@ Every task names where it came from, through a `Plan:`, `Groundwork:`, `Intake:`
|
|
|
207
209
|
|
|
208
210
|
A task with no origin is either lost context or work nobody decided to do. The invariant runs both ways, and the second direction is the one that bites: a groundwork track, an intake folder, or an open issue that no task points at is work already decided and on its way to being forgotten.
|
|
209
211
|
|
|
210
|
-
An intake folder answers that direction at folder scope rather than item scope, since one dump dispositions many items and most close without ever becoming a task. What names a folder is every item answered and no task citing it, on the board or in the archive. That is a dump nobody acted on. Counting the archive beside the board is what separates it from one already promoted and
|
|
212
|
+
An intake folder answers that direction at folder scope rather than item scope, since one dump dispositions many items and most close without ever becoming a task. What names a folder is every item answered and no task citing it, on the board or in the archive. That is a dump nobody acted on. Counting the archive and declined folder beside the board is what separates it from one already promoted and settled, and a check reading the board alone calls every finished folder abandoned.
|
|
211
213
|
|
|
212
214
|
`Plan:`, `Groundwork:`, and `Intake:` name their target as a markdown link whose text is the file or folder stem, so the line resolves on a ctrl-click the way `priority.md` rows already do. Write the path relative to `.canon/tasks/`, which makes it `../plans/`, `../groundwork/`, and `../intake/`. A path written from the project root renders as a link and resolves to nothing in an editor rooted at the project. `Issue:` stays a bare `#NNN`, since an issue number is not a path and a full URL would write the remote into a gitignored file.
|
|
213
215
|
|
|
@@ -258,3 +260,15 @@ Archiving a task archives its plan alongside it, when the closing task is that p
|
|
|
258
260
|
One act rather than two is what makes the pair safe. The merge is the event that settles a plan, and a `post-merge` hook reaching the archive with nobody watching cannot act on a warning, so a second call after it would be a second failure point leaving the task archived and the plan live.
|
|
259
261
|
|
|
260
262
|
A task with an open outcome stays on the board, and so does its plan. Close it, or cut it from the task when the work is being abandoned, so what was dropped is recorded rather than inferred from an archived file. Cutting means striking the outcome's body: `- ~~<outcome>~~ <why>`. `archiveTask` reads a struck body as cut whatever its checkbox holds, so a task carrying only cut outcomes still archives and a mixed task carries both counts on its success record.
|
|
263
|
+
|
|
264
|
+
## Declining
|
|
265
|
+
|
|
266
|
+
A task decided against moves to `.canon/tasks/declined/` rather than `.canon/tasks/archive/`. The two folders answer different questions: archive means the work shipped, declined means somebody decided against doing it. Neither reading fits a task that is merely unscheduled, which stays on `backlog.md` rather than moving anywhere, since nobody has decided against it and it may still rise when the board has room.
|
|
267
|
+
|
|
268
|
+
`canon tasks decline` carries no outcome-state gate. A task can be decided against at any outcome state, open outcomes included, which is what separates its refusal set from archive's: the two never share one, since a shared gate would let one archive a task that cannot yet ship or decline one that already has.
|
|
269
|
+
|
|
270
|
+
The decision is recorded on the task itself with a `Declined:` line, in the `Plan:`/`Pull request:` family: `Declined: <reason>, <who> on <YYYY-MM-DD>`. It anchors the same way `Pull request:` does, after the last origin line the task carries. The line is free prose after the colon, since it names no file to link.
|
|
271
|
+
|
|
272
|
+
Declining a task moves its plan alongside it the same way archiving does, when the declining task is that plan's last live citation. A plan several tasks share stays where it is, and a declined task's plan lands in `.canon/plans/archive/` indistinguishable from a shipped one by folder alone. The task file under `.canon/tasks/declined/` is what records which it was.
|
|
273
|
+
|
|
274
|
+
The move clears whichever of `priority.md` or `backlog.md` holds the task's row, since a decided-against task most often comes off the backlog before anyone plans it, but a row already promoted to the ordering file is cleared the same way archive clears it.
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
name: Deploy
|
|
2
|
+
# canon-no-seed: a stack-specific deploy workflow with no root counterpart by design, parameterized per project on sync rather than mirrored from one.
|
|
3
|
+
|
|
4
|
+
on:
|
|
5
|
+
push:
|
|
6
|
+
branches:
|
|
7
|
+
- main
|
|
8
|
+
paths:
|
|
9
|
+
# Fill in this project's own build inputs. A single glob covering the
|
|
10
|
+
# source tree is enough for most projects; widen it to cover every
|
|
11
|
+
# path the build reads when it draws on content living elsewhere.
|
|
12
|
+
- 'src/**'
|
|
13
|
+
workflow_dispatch:
|
|
14
|
+
|
|
15
|
+
# Fill in a concurrency group name unique to this project. A second push
|
|
16
|
+
# while a deploy is in flight publishes the newer commit rather than racing
|
|
17
|
+
# it, and the running job is left to finish so a half-uploaded site is never
|
|
18
|
+
# what a reader lands on.
|
|
19
|
+
concurrency:
|
|
20
|
+
group: deploy
|
|
21
|
+
cancel-in-progress: false
|
|
22
|
+
|
|
23
|
+
permissions:
|
|
24
|
+
contents: read
|
|
25
|
+
|
|
26
|
+
jobs:
|
|
27
|
+
build:
|
|
28
|
+
name: 📦 Build
|
|
29
|
+
runs-on: ubuntu-latest
|
|
30
|
+
steps:
|
|
31
|
+
- name: Checkout
|
|
32
|
+
uses: actions/checkout@v4
|
|
33
|
+
|
|
34
|
+
- name: Setup Bun
|
|
35
|
+
uses: oven-sh/setup-bun@v2
|
|
36
|
+
with:
|
|
37
|
+
bun-version: latest
|
|
38
|
+
|
|
39
|
+
- name: Install Dependencies
|
|
40
|
+
run: bun install --frozen-lockfile
|
|
41
|
+
|
|
42
|
+
- name: Build
|
|
43
|
+
run: bun run build
|
|
44
|
+
|
|
45
|
+
- name: Upload artifact
|
|
46
|
+
uses: actions/upload-artifact@v4
|
|
47
|
+
with:
|
|
48
|
+
name: dist
|
|
49
|
+
path: dist
|
|
50
|
+
retention-days: 1
|
|
51
|
+
|
|
52
|
+
deploy:
|
|
53
|
+
name: 🚀 Deploy
|
|
54
|
+
needs: build
|
|
55
|
+
runs-on: ubuntu-latest
|
|
56
|
+
steps:
|
|
57
|
+
- name: Check for Cloudflare secrets
|
|
58
|
+
id: secrets-check
|
|
59
|
+
env:
|
|
60
|
+
CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
|
|
61
|
+
CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
|
|
62
|
+
run: |
|
|
63
|
+
if [ -z "$CLOUDFLARE_API_TOKEN" ] || [ -z "$CLOUDFLARE_ACCOUNT_ID" ]; then
|
|
64
|
+
echo "skip=true" >> "$GITHUB_OUTPUT"
|
|
65
|
+
else
|
|
66
|
+
echo "skip=false" >> "$GITHUB_OUTPUT"
|
|
67
|
+
fi
|
|
68
|
+
|
|
69
|
+
- name: Skip notice
|
|
70
|
+
if: steps.secrets-check.outputs.skip == 'true'
|
|
71
|
+
run: echo "::notice::CLOUDFLARE_API_TOKEN or CLOUDFLARE_ACCOUNT_ID is not set, skipping deploy. Run the deploy-cloudflare skill to finish setup."
|
|
72
|
+
|
|
73
|
+
# This job builds nothing and still needs a toolchain. The checkout
|
|
74
|
+
# below brings bun.lock, wrangler-action reads it, picks bun on the
|
|
75
|
+
# strength of it, and installs wrangler with that, so a deploy with no
|
|
76
|
+
# bun on PATH fails before it reaches Cloudflare. Dropping these steps
|
|
77
|
+
# as dead weight breaks the deploy on every push.
|
|
78
|
+
- name: Checkout
|
|
79
|
+
if: steps.secrets-check.outputs.skip == 'false'
|
|
80
|
+
uses: actions/checkout@v4
|
|
81
|
+
|
|
82
|
+
- name: Setup Bun
|
|
83
|
+
if: steps.secrets-check.outputs.skip == 'false'
|
|
84
|
+
uses: oven-sh/setup-bun@v2
|
|
85
|
+
with:
|
|
86
|
+
bun-version: latest
|
|
87
|
+
|
|
88
|
+
- name: Download artifact
|
|
89
|
+
if: steps.secrets-check.outputs.skip == 'false'
|
|
90
|
+
uses: actions/download-artifact@v4
|
|
91
|
+
with:
|
|
92
|
+
name: dist
|
|
93
|
+
path: dist
|
|
94
|
+
|
|
95
|
+
- name: Publish
|
|
96
|
+
if: steps.secrets-check.outputs.skip == 'false'
|
|
97
|
+
uses: cloudflare/wrangler-action@v3
|
|
98
|
+
with:
|
|
99
|
+
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
|
|
100
|
+
accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
|
|
101
|
+
# Fill in this project's Pages project name, set when the
|
|
102
|
+
# deploy-cloudflare skill runs wrangler pages project create.
|
|
103
|
+
command: pages deploy dist --project-name=REPLACE_WITH_PROJECT_NAME --branch=${{ github.ref_name }}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# Tooling cloudflare reference
|
|
2
|
+
|
|
3
|
+
## Overview
|
|
4
|
+
|
|
5
|
+
The cloudflare stack ships one parameterized GitHub Actions workflow that deploys a static build to Cloudflare Pages. It is keyed on host rather than on framework and carries no dependencies, seeds, or scripts of its own, so it syncs onto a project already carrying `astro`, `vite-react`, or any other stack at the same root without colliding.
|
|
6
|
+
|
|
7
|
+
## Deploy workflow
|
|
8
|
+
|
|
9
|
+
- Separate `build` and `deploy` jobs, connected by an uploaded artifact, converged on the shape already running in the closest prior art rather than invented fresh.
|
|
10
|
+
- The deploy job checks for `CLOUDFLARE_API_TOKEN` and `CLOUDFLARE_ACCOUNT_ID` before doing anything else, and skips with a `::notice::` rather than failing when either is absent. A first merge lands ahead of the Pages project existing, and this guard is what keeps that push green.
|
|
11
|
+
- `--branch=${{ github.ref_name }}` on every deploy, which gives every branch a Cloudflare preview URL rather than only `main`.
|
|
12
|
+
- The deploy job checks out the repository and installs Bun even though it builds nothing. `wrangler-action` resolves its package manager from `bun.lock` in the checkout, and dropping that step breaks the deploy on every push once the toolchain it relies on silently goes missing.
|
|
13
|
+
|
|
14
|
+
## Parameters a project fills in by hand
|
|
15
|
+
|
|
16
|
+
Three values in the seeded file have no generic default and are set once, right after sync:
|
|
17
|
+
|
|
18
|
+
- The `paths:` filter under the `push` trigger. The shipped default is a single `src/**` glob. Widen it to cover every path the build reads when the build draws on catalogs or generated content living outside `src/`.
|
|
19
|
+
- The `concurrency.group` name. The shipped default is the bare string `deploy`, which is enough for a single-deploy project. Give it a project-specific name only if the project runs more than one deploy workflow.
|
|
20
|
+
- The `--project-name` flag on the `wrangler pages deploy` command. Set it to the name the `deploy-cloudflare` skill creates the Pages project under.
|
|
21
|
+
|
|
22
|
+
## Re-sync
|
|
23
|
+
|
|
24
|
+
`configs/` is golden and always overwrites. Re-running `canon tooling sync cloudflare . --write` after hand-editing any of the three parameters above discards that edit and restores the shipped defaults. Re-fill the parameters after any re-sync rather than expecting the file to preserve them.
|