@erclx/canon 5.39.0 → 5.41.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 +39 -22
- package/claude/.claude-plugin/plugin.json +1 -1
- package/claude/skills/auto-ship/REQUIREMENT.md +1 -0
- package/claude/skills/auto-ship/SKILL.md +6 -0
- package/claude/skills/auto-ship/references/fan-in.md +59 -0
- package/claude/skills/canvas-board/SKILL.md +1 -1
- package/claude/skills/create-skill/references/skill.md +1 -1
- package/claude/skills/decision-escalate/REQUIREMENT.md +1 -1
- package/claude/skills/decision-escalate/SKILL.md +1 -2
- package/claude/skills/draft-slides/SKILL.md +2 -3
- package/claude/skills/draft-slides/references/deck.md +183 -0
- package/claude/skills/git-branch/REQUIREMENT.md +17 -6
- package/claude/skills/git-branch/SKILL.md +63 -7
- package/claude/skills/git-pr/references/evidence.md +2 -2
- package/claude/skills/git-pr/references/final-command.md +1 -1
- package/claude/skills/git-pr/references/labels.md +1 -1
- package/claude/skills/git-pr/references/publish.md +1 -1
- package/claude/skills/plan-feature/references/batches.md +48 -1
- package/claude/skills/plan-feature/references/plan.md +2 -0
- package/claude/skills/plan-intake-answer/REQUIREMENT.md +1 -1
- package/claude/skills/plan-intake-answer/SKILL.md +1 -2
- package/claude/skills/review-address/references/rebase-conflicts.md +7 -0
- package/claude/skills/review-ui/SKILL.md +3 -0
- package/claude/skills/review-ui/references/close-out.md +47 -0
- package/claude/skills/review-ui/references/post.md +3 -1
- package/claude/skills/role-orchestrator/references/orchestrator-dispatch.md +1 -0
- package/claude/skills/role-orchestrator/references/orchestrator-handback.md +1 -1
- package/claude/skills/role-worker/REQUIREMENT.md +1 -0
- package/claude/skills/role-worker/SKILL.md +9 -0
- package/claude/skills/task-board/references/board.md +1 -1
- package/claude/skills/task-board/references/tasks.md +1 -1
- package/claude/skills/teach-workspace/REQUIREMENT.md +1 -1
- package/claude/skills/teach-workspace/SKILL.md +1 -2
- package/claude/skills/ux-measure/SKILL.md +1 -1
- package/docs/agents/audits.md +61 -60
- package/docs/agents/context-audit.md +40 -58
- package/docs/agents/index.md +5 -51
- package/docs/agents/install-and-sync.md +1 -0
- package/docs/agents/markdown-audit.md +53 -99
- package/docs/agents/records.md +37 -79
- package/docs/agents/sessions-transfer.md +26 -70
- package/docs/target/index.md +0 -1
- package/docs/target/migrations.md +57 -66
- package/docs/target/projects.md +36 -78
- package/docs/target/scenarios.md +9 -19
- package/docs/target/sync.md +27 -68
- package/docs/workflow/ai-workflow.md +22 -68
- package/docs/workflow/operating-model.md +32 -186
- package/docs/workflow/parallel-features.md +30 -66
- package/docs/workflow/skill-map-on-request.md +22 -24
- package/docs/workflow/skill-map.md +72 -76
- package/docs/workflow/visual-design-workflow.md +25 -118
- package/docs/workflow/zshrc-aliases.md +18 -37
- package/package.json +1 -1
- package/src/canvas/client/inspector/fields.css +4 -0
- package/src/canvas/index.html +6 -0
- package/src/claude/cases/git.ts +4 -0
- package/src/claude/skills-reach.ts +2 -2
- package/src/cli/ui.ts +3 -1
- package/src/commands/canvas.ts +23 -0
- package/src/commands/capture.ts +14 -0
- package/src/commands/claude.ts +13 -0
- package/src/commands/demo.ts +22 -0
- package/src/commands/deps.ts +6 -0
- package/src/commands/driver.ts +28 -2
- package/src/commands/gov.ts +52 -18
- package/src/commands/intake.ts +17 -0
- package/src/commands/labels.ts +38 -5
- package/src/commands/pr.ts +192 -52
- package/src/commands/records.ts +19 -3
- package/src/commands/secrets.ts +6 -0
- package/src/commands/sessions.ts +8 -0
- package/src/commands/tasks.ts +52 -0
- package/src/commands/teach.ts +13 -0
- package/src/commands/upstream.ts +25 -1
- package/src/commands/worktrees.ts +5 -0
- package/src/context/audit.ts +22 -4
- package/src/context/folders.ts +1 -1
- package/src/context/shape-gated.ts +5 -2
- package/src/counts/scan.ts +1 -1
- package/src/design/components.ts +1 -1
- package/src/design/document.ts +1 -1
- package/src/design/tokens.ts +1 -1
- package/src/gate/boundaries.ts +4 -1
- package/src/gate/measures.ts +1 -1
- package/src/gate/stages.ts +7 -7
- package/src/git/env.ts +6 -0
- package/src/markdown/scope.ts +11 -19
- package/src/pr/frames.ts +53 -9
- package/src/pr/lift.ts +153 -19
- package/src/pr/painted.ts +87 -0
- package/src/records/validate.ts +1 -0
- package/src/roots/record.ts +18 -1
- package/src/sandbox/census.ts +1 -1
- package/src/sandbox/headless/run.ts +3 -2
- package/src/sandbox/headless/watch.ts +3 -4
- package/src/slides/convert/master.ts +3 -0
- package/src/sync/format.ts +46 -0
- package/src/sync/stamp.ts +6 -0
- package/src/tasks/answers.ts +79 -2
- package/src/tasks/fan-in.ts +176 -0
- package/src/tasks/stack.ts +1 -1
- package/tooling/astro/reference.md +80 -39
- package/tooling/base/reference.md +63 -32
- package/tooling/base/seeds/canon/context/ci.md +7 -15
- package/tooling/base/seeds/canon/context/development.md +14 -14
- package/tooling/claude/reference.md +40 -22
- package/tooling/cloudflare/reference.md +31 -12
- package/tooling/go/reference.md +47 -23
- package/tooling/nextjs/reference.md +52 -19
- package/tooling/php/reference.md +35 -16
- package/tooling/python/reference.md +47 -30
- package/tooling/shell/reference.md +6 -5
- package/tooling/vite-react/reference.md +49 -21
- package/tooling/web/reference.md +131 -71
- package/docs/agents/canvas.md +0 -86
- package/docs/agents/capture.md +0 -65
- package/docs/agents/census.md +0 -23
- package/docs/agents/comments.md +0 -34
- package/docs/agents/context-audit-checks.md +0 -108
- package/docs/agents/context-classify.md +0 -101
- package/docs/agents/counts.md +0 -74
- package/docs/agents/demo.md +0 -140
- package/docs/agents/driver.md +0 -103
- package/docs/agents/gate.md +0 -90
- package/docs/agents/hooks.md +0 -78
- package/docs/agents/intake.md +0 -81
- package/docs/agents/key-changes.md +0 -116
- package/docs/agents/label-coverage.md +0 -86
- package/docs/agents/markdown-audit-density.md +0 -39
- package/docs/agents/markdown-audit-weight.md +0 -83
- package/docs/agents/pr-evidence-layout.md +0 -70
- package/docs/agents/pr-evidence.md +0 -273
- package/docs/agents/pr-frames.md +0 -131
- package/docs/agents/pr-lift.md +0 -132
- package/docs/agents/pr-local.md +0 -111
- package/docs/agents/pr-preview.md +0 -155
- package/docs/agents/pr-reads.md +0 -111
- package/docs/agents/pr-tick.md +0 -72
- package/docs/agents/records-push.md +0 -61
- package/docs/agents/records-stale.md +0 -40
- package/docs/agents/records-validate.md +0 -57
- package/docs/agents/restated.md +0 -83
- package/docs/agents/routing.md +0 -61
- package/docs/agents/rule-citations.md +0 -90
- package/docs/agents/sandbox.md +0 -126
- package/docs/agents/sessions.md +0 -128
- package/docs/agents/skills-audit.md +0 -120
- package/docs/agents/skills-reach.md +0 -64
- package/docs/agents/slides.md +0 -176
- package/docs/agents/state-scoped-risk.md +0 -105
- package/docs/agents/superseded.md +0 -85
- package/docs/agents/tasks-archive.md +0 -83
- package/docs/agents/tasks-plans.md +0 -152
- package/docs/agents/tasks-validate.md +0 -133
- package/docs/agents/tasks.md +0 -123
- package/docs/agents/teach-authoring.md +0 -118
- package/docs/agents/teach.md +0 -157
- package/docs/agents/test-order.md +0 -58
- package/docs/agents/upstream.md +0 -94
- package/docs/agents/worktrees.md +0 -118
- package/docs/target/standards-moved.md +0 -34
package/src/commands/gov.ts
CHANGED
|
@@ -84,6 +84,7 @@ const CANON_RULES_REL = join('.claude', 'rules', 'canon')
|
|
|
84
84
|
|
|
85
85
|
interface InstallOptions {
|
|
86
86
|
readonly add?: string
|
|
87
|
+
readonly yes?: boolean
|
|
87
88
|
}
|
|
88
89
|
|
|
89
90
|
interface RegenOptions {
|
|
@@ -147,6 +148,7 @@ export function register(program: Command): void {
|
|
|
147
148
|
.argument('[target]', 'Target directory', '.')
|
|
148
149
|
.helpOption('-h, --help', 'Show this help message')
|
|
149
150
|
.option('--add <rules>', 'Comma-separated rules to layer on the stack')
|
|
151
|
+
.option('--yes', 'Answer the install confirm without a prompt')
|
|
150
152
|
.addHelpText(
|
|
151
153
|
'after',
|
|
152
154
|
[
|
|
@@ -156,6 +158,7 @@ export function register(program: Command): void {
|
|
|
156
158
|
' canon gov install node ../my-app',
|
|
157
159
|
' canon gov install node --add astro,shadcn ../my-app',
|
|
158
160
|
' canon gov install --add tasks,plan ../my-app',
|
|
161
|
+
' canon gov install node --yes ../my-app',
|
|
159
162
|
'',
|
|
160
163
|
'With --add and no stack, the named rules install alone. A first',
|
|
161
164
|
'positional naming a stack is read as the stack, so pass . or a path',
|
|
@@ -164,6 +167,10 @@ export function register(program: Command): void {
|
|
|
164
167
|
'Copies each rule into .claude/rules/canon/, replacing a copy already',
|
|
165
168
|
'there. Rules under .claude/rules/project/ are never read.',
|
|
166
169
|
'',
|
|
170
|
+
'The install confirm needs a TTY. --yes answers it, so a headless call',
|
|
171
|
+
'installs without setting CANON_NON_INTERACTIVE=1. It does not pick a',
|
|
172
|
+
'stack, so a headless call still names one.',
|
|
173
|
+
'',
|
|
167
174
|
'Exit codes:',
|
|
168
175
|
' 0 installed, the stack held no rules, or the prompt was cancelled',
|
|
169
176
|
' 1 refused: no stack named headlessly, an unknown stack, no --add',
|
|
@@ -349,6 +356,10 @@ export function register(program: Command): void {
|
|
|
349
356
|
' 1 refused, with the reason on stderr or in the JSON record',
|
|
350
357
|
' 2 at least one stated figure disagrees with the tree',
|
|
351
358
|
'',
|
|
359
|
+
'Reasons:',
|
|
360
|
+
' no-git git could not list the tree',
|
|
361
|
+
' no-markdown the tree holds no markdown file',
|
|
362
|
+
'',
|
|
352
363
|
'It reports and never gates. The false-positive rate is read off the',
|
|
353
364
|
'first run rather than assumed, and a push failing on a stated figure',
|
|
354
365
|
'that reads correctly to a person would teach a contributor to route',
|
|
@@ -415,6 +426,9 @@ export function register(program: Command): void {
|
|
|
415
426
|
' 1 refused, with the reason on stderr or in the JSON record',
|
|
416
427
|
' 2 at least one declaration asserts either',
|
|
417
428
|
'',
|
|
429
|
+
'Refuses an empty superseded value, a superseded value equal to its',
|
|
430
|
+
'replacement, and a tree git cannot list. An empty replacement is allowed.',
|
|
431
|
+
'',
|
|
418
432
|
'Examples:',
|
|
419
433
|
' canon gov superseded feature-feat- feature-',
|
|
420
434
|
' canon gov superseded feature-feat- feature- --json',
|
|
@@ -496,6 +510,9 @@ export function register(program: Command): void {
|
|
|
496
510
|
' 2 at least one cited path resolves to nothing, or one glob matches',
|
|
497
511
|
' nothing in this tree',
|
|
498
512
|
'',
|
|
513
|
+
'Refuses a tree holding neither rule corpus, and a `git check-ignore`',
|
|
514
|
+
'read that fails.',
|
|
515
|
+
'',
|
|
499
516
|
'Examples:',
|
|
500
517
|
' canon gov citations',
|
|
501
518
|
' canon gov citations --json',
|
|
@@ -543,6 +560,10 @@ export function register(program: Command): void {
|
|
|
543
560
|
' 1 refused, with the reason on stderr or in the JSON record',
|
|
544
561
|
' 2 at least one instruction is restated outside a declared mirror',
|
|
545
562
|
'',
|
|
563
|
+
'Reasons:',
|
|
564
|
+
' no-instructions the tree holds no always-loaded file or seed to read',
|
|
565
|
+
' no-surfaces the tree holds no skills tree or rules tree to match against',
|
|
566
|
+
'',
|
|
546
567
|
'Examples:',
|
|
547
568
|
' canon gov restated',
|
|
548
569
|
' canon gov restated --json',
|
|
@@ -1268,8 +1289,9 @@ async function runInstall(
|
|
|
1268
1289
|
intro('canon gov install')
|
|
1269
1290
|
|
|
1270
1291
|
const add = opts.add ?? ''
|
|
1292
|
+
const yes = opts.yes === true
|
|
1271
1293
|
if (add !== '' && isAddWithoutStack(stack, target)) {
|
|
1272
|
-
return installExtras(add, stack ?? target)
|
|
1294
|
+
return installExtras(add, stack ?? target, yes)
|
|
1273
1295
|
}
|
|
1274
1296
|
|
|
1275
1297
|
const resolved = resolveTarget(target, PROJECT_ROOT)
|
|
@@ -1314,14 +1336,7 @@ async function runInstall(
|
|
|
1314
1336
|
for (const rule of missing) logWarn(`${rule} (source not found, skipping)`)
|
|
1315
1337
|
for (const source of found) logInfo(source.rule)
|
|
1316
1338
|
|
|
1317
|
-
const shouldInstall = await
|
|
1318
|
-
message: `Install ${found.length} rules to ${displayPath(target, CANON_RULES_REL)}?`,
|
|
1319
|
-
options: [
|
|
1320
|
-
{ value: true, label: 'Yes' },
|
|
1321
|
-
{ value: false, label: 'No' },
|
|
1322
|
-
],
|
|
1323
|
-
nonInteractiveDefault: true,
|
|
1324
|
-
})
|
|
1339
|
+
const shouldInstall = await confirmInstall(found.length, target, yes)
|
|
1325
1340
|
|
|
1326
1341
|
if (!shouldInstall) {
|
|
1327
1342
|
logWarn('Cancelled')
|
|
@@ -1347,6 +1362,28 @@ async function runInstall(
|
|
|
1347
1362
|
return 0
|
|
1348
1363
|
}
|
|
1349
1364
|
|
|
1365
|
+
/**
|
|
1366
|
+
* The install confirm. `--yes` answers it without a prompt, so the line it
|
|
1367
|
+
* would have printed is skipped rather than duplicated under
|
|
1368
|
+
* `CANON_NON_INTERACTIVE=1`.
|
|
1369
|
+
*/
|
|
1370
|
+
async function confirmInstall(
|
|
1371
|
+
count: number,
|
|
1372
|
+
target: string,
|
|
1373
|
+
yes: boolean,
|
|
1374
|
+
): Promise<boolean> {
|
|
1375
|
+
if (yes) return true
|
|
1376
|
+
return select({
|
|
1377
|
+
message: `Install ${count} rules to ${displayPath(target, CANON_RULES_REL)}?`,
|
|
1378
|
+
options: [
|
|
1379
|
+
{ value: true, label: 'Yes' },
|
|
1380
|
+
{ value: false, label: 'No' },
|
|
1381
|
+
],
|
|
1382
|
+
nonInteractiveDefault: true,
|
|
1383
|
+
ttyHint: 'Pass --yes',
|
|
1384
|
+
})
|
|
1385
|
+
}
|
|
1386
|
+
|
|
1350
1387
|
/**
|
|
1351
1388
|
* Whether `--add` runs with no stack under it. Commander fills the first
|
|
1352
1389
|
* positional before the second, so `--add a,b ../my-app` arrives with the
|
|
@@ -1366,7 +1403,11 @@ function isAddWithoutStack(stack: string | undefined, target: string): boolean {
|
|
|
1366
1403
|
* extras are not a stack, and overwriting the chain would cost a target the
|
|
1367
1404
|
* missing-rule report its stack install recorded.
|
|
1368
1405
|
*/
|
|
1369
|
-
async function installExtras(
|
|
1406
|
+
async function installExtras(
|
|
1407
|
+
add: string,
|
|
1408
|
+
target: string,
|
|
1409
|
+
yes: boolean,
|
|
1410
|
+
): Promise<number> {
|
|
1370
1411
|
const resolved = resolveTarget(target, PROJECT_ROOT)
|
|
1371
1412
|
if (typeof resolved === 'number') return resolved
|
|
1372
1413
|
|
|
@@ -1382,14 +1423,7 @@ async function installExtras(add: string, target: string): Promise<number> {
|
|
|
1382
1423
|
return 1
|
|
1383
1424
|
}
|
|
1384
1425
|
|
|
1385
|
-
const shouldInstall = await
|
|
1386
|
-
message: `Install ${found.length} rules to ${displayPath(target, CANON_RULES_REL)}?`,
|
|
1387
|
-
options: [
|
|
1388
|
-
{ value: true, label: 'Yes' },
|
|
1389
|
-
{ value: false, label: 'No' },
|
|
1390
|
-
],
|
|
1391
|
-
nonInteractiveDefault: true,
|
|
1392
|
-
})
|
|
1426
|
+
const shouldInstall = await confirmInstall(found.length, target, yes)
|
|
1393
1427
|
|
|
1394
1428
|
if (!shouldInstall) {
|
|
1395
1429
|
logWarn('Cancelled')
|
package/src/commands/intake.ts
CHANGED
|
@@ -64,6 +64,12 @@ export function register(program: Command): void {
|
|
|
64
64
|
.addHelpText(
|
|
65
65
|
'after',
|
|
66
66
|
[
|
|
67
|
+
'',
|
|
68
|
+
'Refusals, read off `reason`:',
|
|
69
|
+
' no-intake the project holds no intake folder',
|
|
70
|
+
' no-folder no folder matches the slug',
|
|
71
|
+
' ambiguous-slug a bare slug matches more than one ordinal-prefixed',
|
|
72
|
+
' folder, all named in `detail`',
|
|
67
73
|
'',
|
|
68
74
|
'Exit codes:',
|
|
69
75
|
' 0 the folders or items were listed',
|
|
@@ -105,6 +111,17 @@ export function register(program: Command): void {
|
|
|
105
111
|
.addHelpText(
|
|
106
112
|
'after',
|
|
107
113
|
[
|
|
114
|
+
'',
|
|
115
|
+
'Refusals, read off `reason`:',
|
|
116
|
+
' no-intake the project holds no intake folder',
|
|
117
|
+
' no-folder no folder matches the slug',
|
|
118
|
+
' ambiguous-slug a bare slug matches more than one ordinal-prefixed',
|
|
119
|
+
' folder, all named in `detail`',
|
|
120
|
+
' no-cluster the cluster file named does not exist',
|
|
121
|
+
' no-item the item number named does not exist in the cluster',
|
|
122
|
+
' answered the item already carries an answer',
|
|
123
|
+
' bad-input a malformed command line, such as no selection, an empty',
|
|
124
|
+
' answer, or two answers for one item',
|
|
108
125
|
'',
|
|
109
126
|
'Exit codes:',
|
|
110
127
|
' 0 every named item now carries its answer',
|
package/src/commands/labels.ts
CHANGED
|
@@ -111,6 +111,10 @@ export function register(program: Command): void {
|
|
|
111
111
|
' 1 refused, with the reason on stderr or in the JSON record',
|
|
112
112
|
' 2 at least one changed path is reached by no row',
|
|
113
113
|
'',
|
|
114
|
+
'Reasons:',
|
|
115
|
+
' no-map the project declares no label map, so it labels nothing. An',
|
|
116
|
+
' expected absence for `canon audits run`, not a fault',
|
|
117
|
+
'',
|
|
114
118
|
'Examples:',
|
|
115
119
|
' canon labels audit',
|
|
116
120
|
' canon labels audit --json',
|
|
@@ -130,7 +134,10 @@ export function register(program: Command): void {
|
|
|
130
134
|
)
|
|
131
135
|
.argument('<number>', "This branch's open pull request")
|
|
132
136
|
.helpOption('-h, --help', 'Show this help message')
|
|
133
|
-
.option(
|
|
137
|
+
.option(
|
|
138
|
+
'--base <ref>',
|
|
139
|
+
"Read the git range back to this ref instead of the pull request's files",
|
|
140
|
+
)
|
|
134
141
|
.option('--root <path>', 'Repository to read, defaulting to the cwd')
|
|
135
142
|
.option('--check', 'Report the labels owed and write nothing')
|
|
136
143
|
.option('--json', 'Add a machine-readable record on stdout')
|
|
@@ -138,9 +145,10 @@ export function register(program: Command): void {
|
|
|
138
145
|
'after',
|
|
139
146
|
[
|
|
140
147
|
'',
|
|
141
|
-
|
|
142
|
-
'
|
|
143
|
-
'
|
|
148
|
+
"Resolves the earned set from the pull request's own files listing, or",
|
|
149
|
+
'from the git range when --base is passed, binds to the pull request only',
|
|
150
|
+
'when its head is the current branch and it is open, and adds each earned',
|
|
151
|
+
'label the pull request lacks over REST.',
|
|
144
152
|
'',
|
|
145
153
|
'It adds and never removes, so a label a person applied by hand stays.',
|
|
146
154
|
'A label the remote does not carry is reported under missingOnRemote and',
|
|
@@ -155,6 +163,12 @@ export function register(program: Command): void {
|
|
|
155
163
|
' 2 labels are still owed, being any under --check, or a label the',
|
|
156
164
|
' remote lacks or a failed add otherwise',
|
|
157
165
|
'',
|
|
166
|
+
'Reasons:',
|
|
167
|
+
' head-mismatch the pull request is not open on the current branch, under',
|
|
168
|
+
' --check as well',
|
|
169
|
+
' gh-failed the files listing or the labels could not be read, and the',
|
|
170
|
+
' run does not fall back to git',
|
|
171
|
+
'',
|
|
158
172
|
'Examples:',
|
|
159
173
|
' canon labels apply 123 --json',
|
|
160
174
|
' canon labels apply 123 --check',
|
|
@@ -396,7 +410,26 @@ async function runApply(number: string, opts: ApplyOptions): Promise<number> {
|
|
|
396
410
|
if (target === null) return refuse('gh-failed')
|
|
397
411
|
if (target.trim() !== `${head} open`) return refuse('head-mismatch')
|
|
398
412
|
|
|
399
|
-
|
|
413
|
+
// An explicit --base is an explicit request for the git route. Otherwise the
|
|
414
|
+
// pull request's own file listing is the diff the labels describe, and a
|
|
415
|
+
// failed read refuses rather than falling back, since binding needed gh too.
|
|
416
|
+
let paths: string[] | undefined
|
|
417
|
+
if (opts.base === undefined) {
|
|
418
|
+
const listing = await gh(root, [
|
|
419
|
+
'api',
|
|
420
|
+
'--paginate',
|
|
421
|
+
`repos/{owner}/{repo}/pulls/${number}/files`,
|
|
422
|
+
'--jq',
|
|
423
|
+
'.[].filename',
|
|
424
|
+
])
|
|
425
|
+
if (listing === null) return refuse('gh-failed')
|
|
426
|
+
paths = nonEmptyLines(listing)
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
const report = await auditLabels(root, {
|
|
430
|
+
base: opts.base,
|
|
431
|
+
...(paths !== undefined && { paths }),
|
|
432
|
+
})
|
|
400
433
|
|
|
401
434
|
// A project declaring no map earns nothing and is labelled silently, which
|
|
402
435
|
// is an answer and exits clean rather than reading as a break.
|