@erclx/canon 5.27.0 → 5.28.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/draft-ready/SKILL.md +3 -1
- package/claude/skills/git-pr/references/evidence.md +1 -1
- package/claude/skills/review-address/REQUIREMENT.md +4 -2
- package/claude/skills/review-address/SKILL.md +16 -7
- package/claude/skills/review-address/references/rebase-conflicts.md +4 -2
- package/claude/skills/review-craft/references/ui.md +4 -4
- package/claude/skills/review-ui/REQUIREMENT.md +2 -2
- package/claude/skills/review-ui/SKILL.md +13 -11
- package/claude/skills/review-ui/references/driving.md +8 -8
- package/claude/skills/review-ui/references/post.md +13 -3
- package/claude/skills/review-ui/references/tick.md +22 -0
- package/claude/skills/role-orchestrator/REQUIREMENT.md +15 -15
- package/claude/skills/role-orchestrator/SKILL.md +24 -26
- package/claude/skills/role-orchestrator/references/orchestrator-dispatch.md +18 -16
- package/claude/skills/role-orchestrator/references/orchestrator-handback.md +12 -2
- package/claude/skills/role-orchestrator/references/orchestrator-inbound.md +1 -1
- package/claude/skills/role-orchestrator/references/orchestrator-launch.md +1 -1
- package/claude/skills/role-orchestrator/references/orchestrator-output.md +4 -4
- package/claude/skills/role-orchestrator/references/orchestrator-parked.md +5 -5
- package/claude/skills/role-orchestrator/references/orchestrator-poll.md +11 -2
- package/claude/skills/role-orchestrator/references/orchestrator-refill.md +6 -6
- package/claude/skills/role-orchestrator/references/orchestrator-sweep.md +2 -2
- package/claude/skills/role-orchestrator/scripts/poll.ts +304 -10
- package/claude/skills/role-planner/REQUIREMENT.md +1 -1
- package/claude/skills/role-planner/SKILL.md +2 -2
- package/claude/skills/role-reviewer/SKILL.md +1 -1
- package/claude/skills/role-worker/SKILL.md +3 -2
- package/claude/skills/ui-checklist/SKILL.md +1 -1
- package/docs/agents/canvas.md +4 -4
- package/docs/agents/gate.md +3 -1
- package/docs/agents/index.md +2 -1
- package/docs/agents/pr-evidence.md +9 -8
- package/docs/agents/pr-tick.md +72 -0
- package/docs/agents/sandbox.md +2 -2
- package/docs/agents/tasks-plans.md +1 -1
- package/docs/agents/tasks-validate.md +18 -18
- package/docs/agents/tasks.md +1 -1
- package/docs/workflow/operating-model.md +10 -15
- package/docs/workflow/parallel-features.md +7 -7
- package/package.json +1 -1
- package/src/audits/baseline.ts +1 -1
- package/src/audits/run.ts +1 -1
- package/src/canvas/capture.ts +1 -1
- package/src/canvas/client/inspector/field.tsx +61 -2
- package/src/canvas/client/inspector/fields.css +78 -0
- package/src/canvas/client/inspector/token-select.tsx +109 -0
- package/src/canvas/client/inspector/values.ts +74 -2
- package/src/canvas/client/inspector.tsx +121 -11
- package/src/canvas/client/keys.ts +54 -0
- package/src/canvas/client/selection.tsx +3 -1
- package/src/canvas/client/shell.css +108 -2
- package/src/canvas/client/state.ts +96 -5
- package/src/canvas/client/surface.tsx +194 -17
- package/src/canvas/client/tools.tsx +64 -0
- package/src/canvas/content.ts +1 -1
- package/src/canvas/edit.ts +12 -2
- package/src/canvas/tokens.ts +1 -1
- package/src/claude/seeds.ts +2 -2
- package/src/claude/skills-reach.ts +1 -1
- package/src/commands/canvas.ts +1 -1
- package/src/commands/claude.ts +3 -3
- package/src/commands/design.ts +3 -3
- package/src/commands/docs.ts +1 -1
- package/src/commands/feedback.ts +2 -2
- package/src/commands/gate.ts +9 -2
- package/src/commands/gov.ts +2 -2
- package/src/commands/init.ts +1 -1
- package/src/commands/migrate.ts +1 -1
- package/src/commands/pr.ts +183 -7
- package/src/commands/sandbox.ts +10 -13
- package/src/commands/slides.ts +1 -1
- package/src/commands/sync.ts +1 -1
- package/src/commands/tasks.ts +19 -2
- package/src/commands/tooling.ts +1 -1
- package/src/commands/transcripts.ts +1 -1
- package/src/commands/upgrade.ts +1 -1
- package/src/commands/wiki.ts +1 -1
- package/src/context/architecture.ts +1 -1
- package/src/context/citations.ts +2 -2
- package/src/context/classify/extract.ts +1 -1
- package/src/context/folders.ts +2 -2
- package/src/context/requirements.ts +1 -1
- package/src/docs/read.ts +1 -1
- package/src/exec.ts +1 -1
- package/src/feedback/github.ts +1 -1
- package/src/gate/measures.ts +1 -1
- package/src/gate/sequencer.ts +55 -3
- package/src/gate/stages.ts +8 -0
- package/src/gate/suite-lock.ts +214 -0
- package/src/intake/folder.ts +1 -1
- package/src/labels/map.ts +1 -1
- package/src/labels/phase.ts +3 -3
- package/src/migrate/record-layout.ts +1 -1
- package/src/migrate/records.ts +11 -11
- package/src/migrate/scratch-evidence.ts +1 -1
- package/src/migrate/surface-roots.ts +6 -6
- package/src/pr/evidence.ts +154 -16
- package/src/pr/tick.ts +56 -0
- package/src/process/harness.ts +1 -1
- package/src/records/backup.ts +1 -1
- package/src/records/ordinal.ts +1 -1
- package/src/records/prune.ts +1 -1
- package/src/records/size.ts +1 -1
- package/src/records/stale-canonical.ts +1 -1
- package/src/records/stale.ts +1 -1
- package/src/records/validate.ts +2 -2
- package/src/{project-root.ts → roots/project.ts} +1 -1
- package/src/{record-root.ts → roots/record.ts} +1 -1
- package/src/sandbox/coverage.ts +11 -12
- package/src/sandbox/equivalence.ts +57 -26
- package/src/sandbox/hooks.ts +1 -1
- package/src/sandbox/provision.ts +78 -28
- package/src/sandbox/scenario.ts +588 -0
- package/src/sessions/live.ts +4 -1
- package/src/sessions/transfer/bundle.ts +1 -1
- package/src/slides/decks.ts +1 -1
- package/src/standards/read.ts +1 -1
- package/src/sync/engine.ts +1 -1
- package/src/sync/layout.ts +2 -2
- package/src/sync/stamp.ts +1 -1
- package/src/targets/validate.ts +1 -1
- package/src/tasks/answers.ts +1 -1
- package/src/tasks/archive.ts +1 -1
- package/src/tasks/label.ts +1 -1
- package/src/tasks/reach.ts +1 -1
- package/src/tasks/validate.ts +38 -14
- package/src/teach/nav.ts +1 -1
- package/src/teach/workspace.ts +1 -1
- package/src/tooling/inject.ts +1 -1
- package/src/tooling/read.ts +1 -1
- package/src/tooling/scan.ts +1 -1
- package/src/version/installed.ts +1 -1
- package/src/version/skew.ts +1 -1
- package/standards/board.md +3 -3
- package/standards/plan.md +13 -7
- package/tsconfig.json +1 -1
- /package/src/{legacy-path.ts → roots/legacy.ts} +0 -0
- /package/src/{surface-root.ts → roots/surface.ts} +0 -0
package/src/commands/canvas.ts
CHANGED
|
@@ -27,7 +27,7 @@ import {
|
|
|
27
27
|
readEditing,
|
|
28
28
|
} from '@/canvas/editing'
|
|
29
29
|
import { CANVAS_PORT, startCanvas } from '@/canvas/server'
|
|
30
|
-
import { PROJECT_ROOT } from '@/project
|
|
30
|
+
import { PROJECT_ROOT } from '@/roots/project'
|
|
31
31
|
import { resolveFrameTokens } from '@/canvas/tokens'
|
|
32
32
|
import { displayPath, parsePort, waitForInterrupt } from '@/serve/report'
|
|
33
33
|
import {
|
package/src/commands/claude.ts
CHANGED
|
@@ -71,9 +71,9 @@ import {
|
|
|
71
71
|
} from '@/claude/settings'
|
|
72
72
|
import { copyPreservingMode } from '@/sync/copy'
|
|
73
73
|
import { execScript } from '@/exec'
|
|
74
|
-
import { checkoutMismatchWarning, PROJECT_ROOT } from '@/project
|
|
75
|
-
import { recordDir } from '@/record
|
|
76
|
-
import { SURFACE_ENTRIES, surfaceDir } from '@/surface
|
|
74
|
+
import { checkoutMismatchWarning, PROJECT_ROOT } from '@/roots/project'
|
|
75
|
+
import { recordDir } from '@/roots/record'
|
|
76
|
+
import { SURFACE_ENTRIES, surfaceDir } from '@/roots/surface'
|
|
77
77
|
import { isDirectory, resolveTarget } from '@/targets/validate'
|
|
78
78
|
import { injectGitignore, pruneGitignore } from '@/tooling/inject'
|
|
79
79
|
import {
|
package/src/commands/design.ts
CHANGED
|
@@ -11,9 +11,9 @@ import { HAND_DRAWN_FONT_FACES } from '@/design/fonts'
|
|
|
11
11
|
import { addProjectFace, listProjectFaces } from '@/design/project-fonts'
|
|
12
12
|
import { renderDesignDoc } from '@/design/render'
|
|
13
13
|
import { DESIGN_BASE_CSS, DESIGN_DOCUMENT, regenDesign } from '@/design/regen'
|
|
14
|
-
import { checkoutMismatchWarning, PROJECT_ROOT } from '@/project
|
|
15
|
-
import { creationRel, SCRATCH } from '@/record
|
|
16
|
-
import { surfaceDir } from '@/surface
|
|
14
|
+
import { checkoutMismatchWarning, PROJECT_ROOT } from '@/roots/project'
|
|
15
|
+
import { creationRel, SCRATCH } from '@/roots/record'
|
|
16
|
+
import { surfaceDir } from '@/roots/surface'
|
|
17
17
|
import { recordStamp, runDomainSync } from '@/sync/engine'
|
|
18
18
|
import { isDirectory, resolveTarget } from '@/targets/validate'
|
|
19
19
|
import { intro, logAdd, logError, logInfo, logWarn, outro, palette } from '@/ui'
|
package/src/commands/docs.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { Command } from 'commander'
|
|
2
2
|
import { type DocEntry, listDocs } from '@/docs/list'
|
|
3
3
|
import { listTopics, readTopic, resolveTopic } from '@/docs/read'
|
|
4
|
-
import { checkoutMismatchWarning, PROJECT_ROOT } from '@/project
|
|
4
|
+
import { checkoutMismatchWarning, PROJECT_ROOT } from '@/roots/project'
|
|
5
5
|
import { intro, logError, logInfo, logStep, logWarn, outro } from '@/ui'
|
|
6
6
|
|
|
7
7
|
export function register(program: Command): void {
|
package/src/commands/feedback.ts
CHANGED
|
@@ -8,8 +8,8 @@ import {
|
|
|
8
8
|
missingField,
|
|
9
9
|
missingFieldMessage,
|
|
10
10
|
} from '@/feedback/body'
|
|
11
|
-
import { checkoutMismatchWarning, PROJECT_ROOT } from '@/project
|
|
12
|
-
import { creationRel } from '@/record
|
|
11
|
+
import { checkoutMismatchWarning, PROJECT_ROOT } from '@/roots/project'
|
|
12
|
+
import { creationRel } from '@/roots/record'
|
|
13
13
|
import { createGithubIssue } from '@/feedback/github'
|
|
14
14
|
import { issueFailureMessage } from '@/feedback/github-format'
|
|
15
15
|
import { frameError, frameSuccess, logWarn, palette } from '@/ui'
|
package/src/commands/gate.ts
CHANGED
|
@@ -11,7 +11,7 @@ import {
|
|
|
11
11
|
summarize,
|
|
12
12
|
} from '@/gate/sequencer'
|
|
13
13
|
import { STAGES } from '@/gate/stages'
|
|
14
|
-
import { checkoutMismatchWarning, PROJECT_ROOT } from '@/project
|
|
14
|
+
import { checkoutMismatchWarning, PROJECT_ROOT } from '@/roots/project'
|
|
15
15
|
import {
|
|
16
16
|
intro,
|
|
17
17
|
logError,
|
|
@@ -113,6 +113,12 @@ async function runGate(opts: RunCommandOptions): Promise<number> {
|
|
|
113
113
|
cli: cliRunner(root),
|
|
114
114
|
write,
|
|
115
115
|
changed: changed.scoped ? changed.files : undefined,
|
|
116
|
+
onQueue: (label, holder) => {
|
|
117
|
+
const text = `${label} is waiting for the suite pid ${holder.pid} runs at ${holder.root}`
|
|
118
|
+
// stderr in both modes, so a JSON caller's stdout stays one record.
|
|
119
|
+
if (emitJson) process.stderr.write(`${text}\n`)
|
|
120
|
+
else logWarn(text)
|
|
121
|
+
},
|
|
116
122
|
}
|
|
117
123
|
|
|
118
124
|
const results = await runStages(
|
|
@@ -138,12 +144,13 @@ async function runGate(opts: RunCommandOptions): Promise<number> {
|
|
|
138
144
|
changed: changed.files.length,
|
|
139
145
|
summary,
|
|
140
146
|
ms: results.reduce((total, result) => total + result.ms, 0),
|
|
141
|
-
stages: results.map(({ id, label, status, failure, ms }) => ({
|
|
147
|
+
stages: results.map(({ id, label, status, failure, ms, queuedMs }) => ({
|
|
142
148
|
id,
|
|
143
149
|
label,
|
|
144
150
|
status,
|
|
145
151
|
failure,
|
|
146
152
|
ms,
|
|
153
|
+
queuedMs,
|
|
147
154
|
})),
|
|
148
155
|
})}\n`,
|
|
149
156
|
)
|
package/src/commands/gov.ts
CHANGED
|
@@ -8,8 +8,8 @@ import {
|
|
|
8
8
|
type CountsReport,
|
|
9
9
|
scanCounts,
|
|
10
10
|
} from '@/counts/scan'
|
|
11
|
-
import { checkoutMismatchWarning, PROJECT_ROOT } from '@/project
|
|
12
|
-
import { creationRel, SCRATCH } from '@/record
|
|
11
|
+
import { checkoutMismatchWarning, PROJECT_ROOT } from '@/roots/project'
|
|
12
|
+
import { creationRel, SCRATCH } from '@/roots/record'
|
|
13
13
|
import { createGovAdapter } from '@/gov/adapter'
|
|
14
14
|
import { regenConsumedRules } from '@/gov/consumed'
|
|
15
15
|
import { regenStandardRules } from '@/gov/standard-rules'
|
package/src/commands/init.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { Command } from 'commander'
|
|
2
2
|
import { cliPath, cliRun } from '@/cli-run'
|
|
3
|
-
import { PROJECT_ROOT } from '@/project
|
|
3
|
+
import { PROJECT_ROOT } from '@/roots/project'
|
|
4
4
|
import { applyInitOptions, flagsProvided } from '@/init/flags'
|
|
5
5
|
import { type InitFlags, parseSkip, planInit } from '@/init/plan'
|
|
6
6
|
import { runDomains } from '@/init/run'
|
package/src/commands/migrate.ts
CHANGED
|
@@ -44,7 +44,7 @@ import {
|
|
|
44
44
|
type ScratchEvidencePlan,
|
|
45
45
|
walkScratchEvidenceCorpus,
|
|
46
46
|
} from '@/migrate/scratch-evidence'
|
|
47
|
-
import { checkoutMismatchWarning, PROJECT_ROOT } from '@/project
|
|
47
|
+
import { checkoutMismatchWarning, PROJECT_ROOT } from '@/roots/project'
|
|
48
48
|
import { readStamp, stampedHashes } from '@/sync/stamp'
|
|
49
49
|
import { logError, logInfo, logStep, logWarn, plural } from '@/ui'
|
|
50
50
|
|
package/src/commands/pr.ts
CHANGED
|
@@ -29,12 +29,15 @@ import {
|
|
|
29
29
|
findEvidenceCaseCount,
|
|
30
30
|
findEvidenceChecklist,
|
|
31
31
|
findEvidenceCommentId,
|
|
32
|
+
findEvidenceHead,
|
|
32
33
|
findEvidenceLocal,
|
|
33
34
|
findEvidencePreview,
|
|
34
35
|
groupEvidence,
|
|
35
36
|
hasMarkedEvidenceComment,
|
|
37
|
+
readChecklistBoxes,
|
|
36
38
|
readOwed,
|
|
37
39
|
renderEvidenceBody,
|
|
40
|
+
settleChecklist,
|
|
38
41
|
} from '@/pr/evidence'
|
|
39
42
|
import {
|
|
40
43
|
dropFrames,
|
|
@@ -71,6 +74,7 @@ import {
|
|
|
71
74
|
servesChange,
|
|
72
75
|
type WorkflowFile,
|
|
73
76
|
} from '@/pr/preview'
|
|
77
|
+
import { type TickRefusal, tickBoxes } from '@/pr/tick'
|
|
74
78
|
import {
|
|
75
79
|
commentRowOf,
|
|
76
80
|
identityOf,
|
|
@@ -113,6 +117,28 @@ interface LocalOptions extends ReadOptions {
|
|
|
113
117
|
readonly remove?: boolean
|
|
114
118
|
}
|
|
115
119
|
|
|
120
|
+
interface TickOptions extends ReadOptions {
|
|
121
|
+
readonly boxes?: string
|
|
122
|
+
readonly head?: string
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
type TickReason = TickRefusal | 'stale-head' | 'bad-boxes' | 'no-comment'
|
|
126
|
+
|
|
127
|
+
const TICK_REFUSALS: Record<TickReason | 'gh-failed', string> = {
|
|
128
|
+
'stale-head':
|
|
129
|
+
'The head named is not the branch tip on the remote, so a tick would claim a pass on a commit that is no longer the pull request. Re-drive the checklist at the tip.',
|
|
130
|
+
'bad-boxes':
|
|
131
|
+
'--boxes takes box numbers as a comma-separated list of positive whole numbers, such as 1,3, and --head takes the commit that was driven.',
|
|
132
|
+
'no-comment': 'No comment on this pull request carries the evidence marker.',
|
|
133
|
+
'no-checklist': 'The evidence comment carries no checklist to tick.',
|
|
134
|
+
'no-box':
|
|
135
|
+
'A named box is past the end of the checklist. Number boxes off `canon pr evidence --json`.',
|
|
136
|
+
'taste-box':
|
|
137
|
+
'A named box ends in (taste), which no driver passes. Nothing was written.',
|
|
138
|
+
'gh-failed':
|
|
139
|
+
'gh could not read or edit the marked comment on this pull request.',
|
|
140
|
+
}
|
|
141
|
+
|
|
116
142
|
/** Shared by every verb that resolves a pull request from the checkout's branch. */
|
|
117
143
|
const AMBIGUOUS_PULL =
|
|
118
144
|
'More than one pull request is open on this branch, each against another base. Name the pull request number.'
|
|
@@ -563,10 +589,12 @@ export function register(program: Command): void {
|
|
|
563
589
|
'--checklist closes the body with a visual checklist, below the',
|
|
564
590
|
'comparison it annotates. A checklist the marked comment already',
|
|
565
591
|
'carries is read back and carried forward the same way the preview',
|
|
566
|
-
'address is
|
|
567
|
-
'
|
|
568
|
-
'
|
|
569
|
-
'
|
|
592
|
+
'address is. A tick survives only a render at the head it names: one',
|
|
593
|
+
'stamped `passed at <sha>` for another head is cleared and unstamped,',
|
|
594
|
+
'and so is one carrying no stamp when the comment described another',
|
|
595
|
+
'head. A checklist, supplied or carried, turns no-evidence into ok: a',
|
|
596
|
+
'branch with a checklist and no evidence image renders a marked body',
|
|
597
|
+
'holding the checklist, so a later call finds and edits that comment.',
|
|
570
598
|
'',
|
|
571
599
|
'--local adds a **Local preview:** segment after any hosted one on the',
|
|
572
600
|
'address line, or carries the line alone, and is carried forward the',
|
|
@@ -580,6 +608,8 @@ export function register(program: Command): void {
|
|
|
580
608
|
' preview the hosted **Preview:** address the comment carries',
|
|
581
609
|
' local the **Local preview:** address beside it',
|
|
582
610
|
' checklist the checklist between its delimiters, ticks included',
|
|
611
|
+
' boxes each checklist box with its number, tick, stamp, and taste',
|
|
612
|
+
' mark, the numbering `canon pr tick` takes',
|
|
583
613
|
'These come from the comment already posted, never from the flags this',
|
|
584
614
|
'call passed. A checklist the caller posted raw after a refused render',
|
|
585
615
|
'carries no marker, so the record reports none of the three for it. An',
|
|
@@ -741,6 +771,51 @@ export function register(program: Command): void {
|
|
|
741
771
|
: await runLocal(opts)
|
|
742
772
|
})
|
|
743
773
|
|
|
774
|
+
pr.command('tick')
|
|
775
|
+
.description(
|
|
776
|
+
"Tick the checklist boxes a UI pass drove, stamping each with the commit it passed at, on the pull request's evidence comment",
|
|
777
|
+
)
|
|
778
|
+
.argument('[number]', 'Pull request to edit, defaulting to this branch')
|
|
779
|
+
.helpOption('-h, --help', 'Show this help message')
|
|
780
|
+
.option('--root <path>', 'Repository to read, defaulting to the cwd')
|
|
781
|
+
.option('--json', 'Add a machine-readable record on stdout')
|
|
782
|
+
.requiredOption(
|
|
783
|
+
'--boxes <list>',
|
|
784
|
+
'Box numbers to tick, comma-separated, as `canon pr evidence --json` numbers them',
|
|
785
|
+
)
|
|
786
|
+
.requiredOption('--head <sha>', 'The commit the boxes were driven at')
|
|
787
|
+
.addHelpText(
|
|
788
|
+
'after',
|
|
789
|
+
[
|
|
790
|
+
'',
|
|
791
|
+
'Edits the marked evidence comment in place, so a skill never',
|
|
792
|
+
'hand-edits a comment body. Each named box becomes',
|
|
793
|
+
'`- [x] <box> · passed at <short sha>`. Ticking a box again restamps',
|
|
794
|
+
'it. A later render at another head clears a tick whose stamp names',
|
|
795
|
+
'a different commit.',
|
|
796
|
+
'',
|
|
797
|
+
'Read `reason` on the JSON record:',
|
|
798
|
+
' ticked the boxes were ticked, with the tip in `head`',
|
|
799
|
+
' stale-head --head is not the remote branch tip, nothing written',
|
|
800
|
+
' taste-box a named box ends in (taste), nothing written',
|
|
801
|
+
' no-box a named number is past the checklist, nothing written',
|
|
802
|
+
' no-checklist the evidence comment carries no checklist',
|
|
803
|
+
' no-comment no comment carries the evidence marker',
|
|
804
|
+
' bad-boxes --boxes or --head is malformed',
|
|
805
|
+
'',
|
|
806
|
+
'Exit codes:',
|
|
807
|
+
' 0 the boxes were ticked',
|
|
808
|
+
' 1 refused, with the reason on stderr or in the JSON record',
|
|
809
|
+
'',
|
|
810
|
+
'Examples:',
|
|
811
|
+
' canon pr tick 1341 --boxes 1,2,4 --head 1a2b3c4 --json',
|
|
812
|
+
'',
|
|
813
|
+
].join('\n'),
|
|
814
|
+
)
|
|
815
|
+
.action(async (number: string | undefined, opts: TickOptions) => {
|
|
816
|
+
process.exitCode = await runTick(number, opts)
|
|
817
|
+
})
|
|
818
|
+
|
|
744
819
|
pr.command('frames')
|
|
745
820
|
.description(
|
|
746
821
|
`Push a UI review frame to the never-merged ${FRAMES_BRANCH} branch and return its embed link, or drop frames from it`,
|
|
@@ -1786,7 +1861,10 @@ async function runEvidence(
|
|
|
1786
1861
|
const carried = {
|
|
1787
1862
|
...(carriedPreview !== undefined && { preview: carriedPreview }),
|
|
1788
1863
|
...(carriedLocal !== undefined && { local: carriedLocal }),
|
|
1789
|
-
...(carriedChecklist !== undefined && {
|
|
1864
|
+
...(carriedChecklist !== undefined && {
|
|
1865
|
+
checklist: carriedChecklist,
|
|
1866
|
+
boxes: readChecklistBoxes(carriedChecklist),
|
|
1867
|
+
}),
|
|
1790
1868
|
}
|
|
1791
1869
|
|
|
1792
1870
|
// A deploy that resolves and does not build from any changed path makes a
|
|
@@ -1834,7 +1912,8 @@ async function runEvidence(
|
|
|
1834
1912
|
// comment a later call edits. A local address rides on a checklist or a
|
|
1835
1913
|
// hosted preview and never opens one alone, so a docs-only branch with a
|
|
1836
1914
|
// server up posts nothing.
|
|
1837
|
-
const hasChecklist =
|
|
1915
|
+
const hasChecklist =
|
|
1916
|
+
suppliedChecklist !== undefined || carriedChecklist !== undefined
|
|
1838
1917
|
if (
|
|
1839
1918
|
grouped.kind === 'refused' &&
|
|
1840
1919
|
opts.preview === undefined &&
|
|
@@ -1870,7 +1949,11 @@ async function runEvidence(
|
|
|
1870
1949
|
|
|
1871
1950
|
const preview = opts.preview ?? (isUnserved ? undefined : carriedPreview)
|
|
1872
1951
|
const local = opts.local ?? carriedLocal
|
|
1873
|
-
const
|
|
1952
|
+
const unsettled = suppliedChecklist ?? carriedChecklist
|
|
1953
|
+
const checklist =
|
|
1954
|
+
unsettled === undefined
|
|
1955
|
+
? undefined
|
|
1956
|
+
: settleChecklist(unsettled, identity.head, findEvidenceHead(comments))
|
|
1874
1957
|
const states = grouped.kind === 'read' ? grouped.states : []
|
|
1875
1958
|
const body = renderEvidenceBody(
|
|
1876
1959
|
states,
|
|
@@ -2402,6 +2485,99 @@ async function runLocalRemove(
|
|
|
2402
2485
|
|
|
2403
2486
|
const POSITIVE_WHOLE = /^[1-9]\d*$/
|
|
2404
2487
|
|
|
2488
|
+
const MIN_TICK_HEAD_LENGTH = 7
|
|
2489
|
+
|
|
2490
|
+
/**
|
|
2491
|
+
* Ticks the named boxes on the marked comment. It refuses a head that is not
|
|
2492
|
+
* the remote tip and a taste box before writing, so both rules hold as checks
|
|
2493
|
+
* rather than as prose a driver can talk itself out of.
|
|
2494
|
+
*/
|
|
2495
|
+
async function runTick(
|
|
2496
|
+
number: string | undefined,
|
|
2497
|
+
opts: TickOptions,
|
|
2498
|
+
): Promise<number> {
|
|
2499
|
+
const root = resolve(opts.root ?? process.cwd())
|
|
2500
|
+
const emitJson = opts.json ?? false
|
|
2501
|
+
|
|
2502
|
+
intro('canon pr tick')
|
|
2503
|
+
|
|
2504
|
+
const refuseTick = (reason: TickReason | 'gh-failed'): number =>
|
|
2505
|
+
refuseWith(reason, TICK_REFUSALS[reason], emitJson, root)
|
|
2506
|
+
|
|
2507
|
+
const numbers = (opts.boxes ?? '').split(',').map((part) => part.trim())
|
|
2508
|
+
const head = (opts.head ?? '').trim()
|
|
2509
|
+
if (
|
|
2510
|
+
numbers.length === 0 ||
|
|
2511
|
+
!numbers.every((part) => POSITIVE_WHOLE.test(part)) ||
|
|
2512
|
+
!/^[0-9a-f]+$/.test(head) ||
|
|
2513
|
+
head.length < MIN_TICK_HEAD_LENGTH
|
|
2514
|
+
) {
|
|
2515
|
+
return refuseTick('bad-boxes')
|
|
2516
|
+
}
|
|
2517
|
+
|
|
2518
|
+
const read = await readIdentity(root, number)
|
|
2519
|
+
if (read.kind === 'refused') {
|
|
2520
|
+
return refuseWith(read.reason, PULL_REFUSALS[read.reason], emitJson, root)
|
|
2521
|
+
}
|
|
2522
|
+
const { identity } = read
|
|
2523
|
+
if (identity.number === undefined) return refuseTick('gh-failed')
|
|
2524
|
+
|
|
2525
|
+
const reading = await resolveTip(identity.branch, refReader(root))
|
|
2526
|
+
if (reading.kind === 'refused') {
|
|
2527
|
+
return refuseWith(
|
|
2528
|
+
reading.reason,
|
|
2529
|
+
PULL_REFUSALS[reading.reason],
|
|
2530
|
+
emitJson,
|
|
2531
|
+
root,
|
|
2532
|
+
)
|
|
2533
|
+
}
|
|
2534
|
+
if (!reading.tip.startsWith(head)) return refuseTick('stale-head')
|
|
2535
|
+
|
|
2536
|
+
const comments = await listComments(root, String(identity.number))
|
|
2537
|
+
if (comments === undefined) return refuseTick('gh-failed')
|
|
2538
|
+
const commentId = findEvidenceCommentId(comments)
|
|
2539
|
+
const marked = comments.find((comment) =>
|
|
2540
|
+
comment.url?.endsWith(`#issuecomment-${commentId}`),
|
|
2541
|
+
)
|
|
2542
|
+
if (commentId === undefined || marked === undefined) {
|
|
2543
|
+
return refuseTick('no-comment')
|
|
2544
|
+
}
|
|
2545
|
+
|
|
2546
|
+
const result = tickBoxes(marked.body, numbers.map(Number), reading.tip)
|
|
2547
|
+
if (result.kind === 'refused') return refuseTick(result.reason)
|
|
2548
|
+
|
|
2549
|
+
if (result.body !== marked.body) {
|
|
2550
|
+
const patched = await gh(root, [
|
|
2551
|
+
'api',
|
|
2552
|
+
'-X',
|
|
2553
|
+
'PATCH',
|
|
2554
|
+
`repos/{owner}/{repo}/issues/comments/${commentId}`,
|
|
2555
|
+
'-f',
|
|
2556
|
+
`body=${result.body}`,
|
|
2557
|
+
])
|
|
2558
|
+
if (patched === null) return refuseTick('gh-failed')
|
|
2559
|
+
}
|
|
2560
|
+
|
|
2561
|
+
logStep('Ticked')
|
|
2562
|
+
logInfo(
|
|
2563
|
+
`${plural(numbers.length, 'box')} at ${reading.tip.slice(0, 7)} on comment ${commentId}`,
|
|
2564
|
+
)
|
|
2565
|
+
outro()
|
|
2566
|
+
if (emitJson) {
|
|
2567
|
+
process.stdout.write(
|
|
2568
|
+
`${JSON.stringify({
|
|
2569
|
+
root,
|
|
2570
|
+
number: identity.number,
|
|
2571
|
+
reason: 'ticked',
|
|
2572
|
+
head: reading.tip,
|
|
2573
|
+
boxes: numbers.map(Number),
|
|
2574
|
+
commentId,
|
|
2575
|
+
})}\n`,
|
|
2576
|
+
)
|
|
2577
|
+
}
|
|
2578
|
+
return 0
|
|
2579
|
+
}
|
|
2580
|
+
|
|
2405
2581
|
/** One `gh api` call, keeping the HTTP status of a failure so a refused write can be told from a missing ref. */
|
|
2406
2582
|
type ApiCall =
|
|
2407
2583
|
| { readonly ok: true; readonly data: unknown }
|
package/src/commands/sandbox.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { existsSync, readFileSync
|
|
1
|
+
import { existsSync, readFileSync } from 'node:fs'
|
|
2
2
|
import { join } from 'node:path'
|
|
3
3
|
import type { Command } from 'commander'
|
|
4
|
-
import { checkoutMismatchWarning, PROJECT_ROOT } from '@/project
|
|
4
|
+
import { checkoutMismatchWarning, PROJECT_ROOT } from '@/roots/project'
|
|
5
5
|
import {
|
|
6
6
|
assertedPercent,
|
|
7
7
|
collectCensus,
|
|
@@ -27,6 +27,7 @@ import {
|
|
|
27
27
|
} from '@/sandbox/expect'
|
|
28
28
|
import { runHeadless } from '@/sandbox/headless/run'
|
|
29
29
|
import { runSandbox } from '@/sandbox/provision'
|
|
30
|
+
import { listCategories, listScenarioFiles } from '@/sandbox/scenario'
|
|
30
31
|
import { sandboxTree } from '@/sandbox/tree'
|
|
31
32
|
import {
|
|
32
33
|
frameError,
|
|
@@ -71,9 +72,6 @@ function reportAbsentScenarioTree(): boolean {
|
|
|
71
72
|
return true
|
|
72
73
|
}
|
|
73
74
|
|
|
74
|
-
/** Holds fixture content for scenarios rather than scenarios of its own. */
|
|
75
|
-
const FIXTURES_DIR = 'fixtures'
|
|
76
|
-
|
|
77
75
|
/**
|
|
78
76
|
* The provision verb takes positional words rather than options, since `reset`
|
|
79
77
|
* and `clean` share its slot with a target, so its usage is written out here
|
|
@@ -125,17 +123,16 @@ interface CoverageOptions {
|
|
|
125
123
|
}
|
|
126
124
|
|
|
127
125
|
function getCategories(): string[] {
|
|
128
|
-
return
|
|
129
|
-
.filter((d) => d.isDirectory() && d.name !== FIXTURES_DIR)
|
|
130
|
-
.map((d) => d.name)
|
|
131
|
-
.sort()
|
|
126
|
+
return listCategories(SANDBOX_DIR)
|
|
132
127
|
}
|
|
133
128
|
|
|
129
|
+
/** Lists a stem in both forms too, so picking it reaches the refusal that names it. */
|
|
134
130
|
function getCommands(category: string): string[] {
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
131
|
+
const { scenarios, ambiguous } = listScenarioFiles(
|
|
132
|
+
join(SANDBOX_DIR, category),
|
|
133
|
+
)
|
|
134
|
+
|
|
135
|
+
return [...scenarios.map((s) => s.command), ...ambiguous].sort()
|
|
139
136
|
}
|
|
140
137
|
|
|
141
138
|
async function interactivePicker(): Promise<string> {
|
package/src/commands/slides.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { resolve } from 'node:path'
|
|
2
2
|
import type { Command } from 'commander'
|
|
3
3
|
import { INSTALL_BROWSER } from '@/browser/engine'
|
|
4
|
-
import { creationRel, SCRATCH } from '@/record
|
|
4
|
+
import { creationRel, SCRATCH } from '@/roots/record'
|
|
5
5
|
import { listDecks, resolveDeck, SLIDES_FOLDER } from '@/slides/decks'
|
|
6
6
|
import { openDeck } from '@/slides/open'
|
|
7
7
|
import { intro, outro, palette } from '@/ui'
|
package/src/commands/sync.ts
CHANGED
|
@@ -2,7 +2,7 @@ import { existsSync } from 'node:fs'
|
|
|
2
2
|
import { join } from 'node:path'
|
|
3
3
|
import type { Command } from 'commander'
|
|
4
4
|
import { cliPath, cliRun } from '@/cli-run'
|
|
5
|
-
import { PROJECT_ROOT } from '@/project
|
|
5
|
+
import { PROJECT_ROOT } from '@/roots/project'
|
|
6
6
|
import {
|
|
7
7
|
buildCheckReport,
|
|
8
8
|
type CheckReport,
|
package/src/commands/tasks.ts
CHANGED
|
@@ -42,6 +42,7 @@ import {
|
|
|
42
42
|
import {
|
|
43
43
|
type Finding,
|
|
44
44
|
type FolderClaim,
|
|
45
|
+
type SharedFiles,
|
|
45
46
|
type WideToken,
|
|
46
47
|
type Unplaced,
|
|
47
48
|
type Untested,
|
|
@@ -239,7 +240,8 @@ export function register(program: Command): void {
|
|
|
239
240
|
' no Needs a plan row has a live plan, and every Up next row has one',
|
|
240
241
|
' every task file carries a board row or a backlog line, never both',
|
|
241
242
|
' no task carries more than one row',
|
|
242
|
-
'
|
|
243
|
+
' every Run now row names the files it touches',
|
|
244
|
+
' a file two Run now rows share is listed, never failed',
|
|
243
245
|
'',
|
|
244
246
|
'Exit codes:',
|
|
245
247
|
' 0 every check passed',
|
|
@@ -1383,7 +1385,7 @@ function reportValidation(
|
|
|
1383
1385
|
logStep(outcome.findings.length === 0 ? 'Clean' : 'Findings')
|
|
1384
1386
|
if (outcome.findings.length === 0) {
|
|
1385
1387
|
logInfo(
|
|
1386
|
-
'every row resolves, every task sits on one surface, and each
|
|
1388
|
+
'every row resolves, every task sits on one surface, and each run now row names its files',
|
|
1387
1389
|
)
|
|
1388
1390
|
} else {
|
|
1389
1391
|
for (const finding of outcome.findings) logWarn(describe(finding))
|
|
@@ -1412,6 +1414,16 @@ function reportValidation(
|
|
|
1412
1414
|
for (const claim of outcome.claims) logWarn(describeClaim(claim))
|
|
1413
1415
|
}
|
|
1414
1416
|
|
|
1417
|
+
// A shared file holds nothing under the dispatch gate, so the pairs report
|
|
1418
|
+
// with the warn glyph and move no exit code. They stay listed for merge
|
|
1419
|
+
// order and for the holds a Touches cell cannot state.
|
|
1420
|
+
logStep('Shared files')
|
|
1421
|
+
if (outcome.shared.length === 0) {
|
|
1422
|
+
logInfo('no two run now rows name the same file')
|
|
1423
|
+
} else {
|
|
1424
|
+
for (const pair of outcome.shared) logWarn(describeShared(pair))
|
|
1425
|
+
}
|
|
1426
|
+
|
|
1415
1427
|
// The width limit is a heuristic over one preview pane, so a wide token
|
|
1416
1428
|
// reports and moves no exit code, and a measure failing on a cell that
|
|
1417
1429
|
// fits would teach a reader to skip it.
|
|
@@ -1447,6 +1459,7 @@ function reportValidation(
|
|
|
1447
1459
|
findings: outcome.findings,
|
|
1448
1460
|
untested: outcome.untested,
|
|
1449
1461
|
claims: outcome.claims,
|
|
1462
|
+
shared: outcome.shared,
|
|
1450
1463
|
wide: outcome.wide,
|
|
1451
1464
|
unplaced: outcome.unplaced,
|
|
1452
1465
|
})}\n`,
|
|
@@ -1469,6 +1482,10 @@ function describeClaim(claim: FolderClaim): string {
|
|
|
1469
1482
|
return `${claim.group}: ${claim.subject} ${claim.message}`
|
|
1470
1483
|
}
|
|
1471
1484
|
|
|
1485
|
+
function describeShared(pair: SharedFiles): string {
|
|
1486
|
+
return `${pair.group}: ${pair.subject} ${pair.message}`
|
|
1487
|
+
}
|
|
1488
|
+
|
|
1472
1489
|
function describeWide(token: WideToken): string {
|
|
1473
1490
|
return `${token.group}: ${token.subject} ${token.message}`
|
|
1474
1491
|
}
|
package/src/commands/tooling.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { resolve } from 'node:path'
|
|
2
2
|
import type { Command } from 'commander'
|
|
3
|
-
import { checkoutMismatchWarning, PROJECT_ROOT } from '@/project
|
|
3
|
+
import { checkoutMismatchWarning, PROJECT_ROOT } from '@/roots/project'
|
|
4
4
|
import {
|
|
5
5
|
injectConfigs,
|
|
6
6
|
injectGitignore,
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { resolve } from 'node:path'
|
|
2
2
|
import type { Command } from 'commander'
|
|
3
|
-
import { recordDir } from '@/record
|
|
3
|
+
import { recordDir } from '@/roots/record'
|
|
4
4
|
import { ensureYtDlp, fetchOne } from '@/transcripts/fetch'
|
|
5
5
|
import { palette } from '@/ui'
|
|
6
6
|
import { mainWorktreeRoot } from '@/git/worktree'
|
package/src/commands/upgrade.ts
CHANGED
package/src/commands/wiki.ts
CHANGED
|
@@ -2,7 +2,7 @@ import { access, readFile } from 'node:fs/promises'
|
|
|
2
2
|
import { join, relative } from 'node:path'
|
|
3
3
|
import { AUDITS } from '@/audits/catalog'
|
|
4
4
|
import { type BodyLine, bodyLines } from '@/markdown/scan'
|
|
5
|
-
import { surfaceDir } from '@/surface
|
|
5
|
+
import { surfaceDir } from '@/roots/surface'
|
|
6
6
|
|
|
7
7
|
/**
|
|
8
8
|
* The record this measures, relative to `root`.
|
package/src/context/citations.ts
CHANGED
|
@@ -2,8 +2,8 @@ import { existsSync } from 'node:fs'
|
|
|
2
2
|
import { readFile } from 'node:fs/promises'
|
|
3
3
|
import { resolve } from 'node:path'
|
|
4
4
|
import { listRepositoryFiles } from '@/git/files'
|
|
5
|
-
import { RECORD_ROOTS } from '@/record
|
|
6
|
-
import { SURFACE_ROOTS } from '@/surface
|
|
5
|
+
import { RECORD_ROOTS } from '@/roots/record'
|
|
6
|
+
import { SURFACE_ROOTS } from '@/roots/surface'
|
|
7
7
|
|
|
8
8
|
/**
|
|
9
9
|
* Suppresses citation checking for the source line carrying it.
|
|
@@ -4,7 +4,7 @@ import { $ } from 'bun'
|
|
|
4
4
|
import { resolveFolders } from '@/context/folders'
|
|
5
5
|
import { gitEnv } from '@/git/env'
|
|
6
6
|
import { listChangedFiles, resolveBaseRef } from '@/git/files'
|
|
7
|
-
import { surfaceDir } from '@/surface
|
|
7
|
+
import { surfaceDir } from '@/roots/surface'
|
|
8
8
|
|
|
9
9
|
/**
|
|
10
10
|
* The five canonical doc types the classifier reaches, matching the file
|
package/src/context/folders.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { existsSync } from 'node:fs'
|
|
2
2
|
import { dirname, relative, resolve } from 'node:path'
|
|
3
3
|
import { INDEX_FILE, listIndexes } from '@/indexes/walk'
|
|
4
|
-
import { RECORD_ROOTS } from '@/record
|
|
5
|
-
import { SURFACE_ROOTS } from '@/surface
|
|
4
|
+
import { RECORD_ROOTS } from '@/roots/record'
|
|
5
|
+
import { SURFACE_ROOTS } from '@/roots/surface'
|
|
6
6
|
|
|
7
7
|
/**
|
|
8
8
|
* Folder names under a record root audited by default.
|
|
@@ -2,7 +2,7 @@ import { readFile } from 'node:fs/promises'
|
|
|
2
2
|
import { join, relative } from 'node:path'
|
|
3
3
|
import { wordCount } from '@/context/architecture'
|
|
4
4
|
import { bodyLines } from '@/markdown/scan'
|
|
5
|
-
import { surfaceDir } from '@/surface
|
|
5
|
+
import { surfaceDir } from '@/roots/surface'
|
|
6
6
|
|
|
7
7
|
/**
|
|
8
8
|
* The record this measures, relative to `root`, resolved at either surface
|
package/src/docs/read.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { existsSync, readFileSync, readdirSync } from 'node:fs'
|
|
2
2
|
import { basename, join } from 'node:path'
|
|
3
3
|
import { stripFrontmatter } from '@/markdown/frontmatter'
|
|
4
|
-
import { SURFACE_ROOTS } from '@/surface
|
|
4
|
+
import { SURFACE_ROOTS } from '@/roots/surface'
|
|
5
5
|
|
|
6
6
|
const INDEX_TOPIC = 'index'
|
|
7
7
|
|
package/src/exec.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { join } from 'node:path'
|
|
2
2
|
import { execa } from 'execa'
|
|
3
|
-
import { checkoutMismatchWarning, PROJECT_ROOT } from '@/project
|
|
3
|
+
import { checkoutMismatchWarning, PROJECT_ROOT } from '@/roots/project'
|
|
4
4
|
import { logWarn } from '@/ui'
|
|
5
5
|
|
|
6
6
|
export async function execScript(
|