@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
|
@@ -40,19 +40,33 @@ export interface Row {
|
|
|
40
40
|
passAt: number
|
|
41
41
|
prior: string
|
|
42
42
|
replyAt: number
|
|
43
|
+
/** What the pull request writes, null when its diff could not be read. */
|
|
44
|
+
paths: null | string[]
|
|
43
45
|
resp: number
|
|
46
|
+
stale: Staleness
|
|
47
|
+
staleFiles: string[]
|
|
44
48
|
ui: string
|
|
45
49
|
unmatchedCount: number
|
|
46
50
|
unmatchedHeading: string
|
|
47
51
|
}
|
|
48
52
|
|
|
53
|
+
/**
|
|
54
|
+
* Whether the base moved under a path the pull request writes since the commit
|
|
55
|
+
* it branched from. `unknown` is a written set this run could not read, which
|
|
56
|
+
* must never pass for `fresh`.
|
|
57
|
+
*/
|
|
58
|
+
export type Staleness = 'fresh' | 'stale' | 'unknown'
|
|
59
|
+
|
|
49
60
|
/** One baseline line, kept as the strings it was written with. */
|
|
50
61
|
export interface Baseline {
|
|
51
62
|
head: string
|
|
52
63
|
merges: string
|
|
53
64
|
number: string
|
|
65
|
+
/** Open pull requests this one conflicts with, comma-separated, or `none`. */
|
|
66
|
+
overlaps: string
|
|
54
67
|
prior: string
|
|
55
68
|
resp: string
|
|
69
|
+
stale: string
|
|
56
70
|
state: string
|
|
57
71
|
ui: string
|
|
58
72
|
unmatched: string
|
|
@@ -211,7 +225,7 @@ export const uiState = (payload: Payload, head: string): string => {
|
|
|
211
225
|
return `${heading}-${covered ? 'head' : 'behind'}-${commit.slice(0, 7)}`
|
|
212
226
|
}
|
|
213
227
|
|
|
214
|
-
/** Takes the first
|
|
228
|
+
/** Takes the first ten fields and ignores the rest, so a short or padded line still reads. */
|
|
215
229
|
export const parseBaseline = (line: string): Baseline => {
|
|
216
230
|
const fields = line.split(' ')
|
|
217
231
|
|
|
@@ -219,14 +233,21 @@ export const parseBaseline = (line: string): Baseline => {
|
|
|
219
233
|
head: fields[1] ?? '',
|
|
220
234
|
merges: fields[4] ?? '',
|
|
221
235
|
number: fields[0] ?? '',
|
|
236
|
+
overlaps: fields[9] ?? '',
|
|
222
237
|
prior: fields[2] ?? '',
|
|
223
238
|
resp: fields[3] ?? '',
|
|
239
|
+
stale: fields[8] ?? '',
|
|
224
240
|
state: fields[5] ?? '',
|
|
225
241
|
ui: fields[7] ?? '',
|
|
226
242
|
unmatched: fields[6] ?? '',
|
|
227
243
|
}
|
|
228
244
|
}
|
|
229
245
|
|
|
246
|
+
/**
|
|
247
|
+
* New fields append at the end, so every positional read of an older line still
|
|
248
|
+
* lands. A field an older line never carried stays empty and is not written
|
|
249
|
+
* back as a trailing space.
|
|
250
|
+
*/
|
|
230
251
|
export const formatBaseline = (entry: Baseline): string =>
|
|
231
252
|
[
|
|
232
253
|
entry.number,
|
|
@@ -237,10 +258,30 @@ export const formatBaseline = (entry: Baseline): string =>
|
|
|
237
258
|
entry.state,
|
|
238
259
|
entry.unmatched,
|
|
239
260
|
entry.ui,
|
|
240
|
-
|
|
261
|
+
entry.stale,
|
|
262
|
+
entry.overlaps,
|
|
263
|
+
]
|
|
264
|
+
.join(' ')
|
|
265
|
+
.trimEnd()
|
|
241
266
|
|
|
242
267
|
const short = (head: string): string => head.slice(0, 7)
|
|
243
268
|
|
|
269
|
+
const listFiles = (files: string[]): string =>
|
|
270
|
+
files.length > 3
|
|
271
|
+
? `${files.slice(0, 3).join(', ')}, +${files.length - 3} more`
|
|
272
|
+
: files.join(', ')
|
|
273
|
+
|
|
274
|
+
/**
|
|
275
|
+
* A conflict already sends the branch to its owner for a rebase, so a pull
|
|
276
|
+
* request reading both reports the conflict alone and earns one handback.
|
|
277
|
+
*/
|
|
278
|
+
const staleLine = (row: Row, base: string): string[] =>
|
|
279
|
+
row.stale === 'stale' && row.merges !== 'conflict'
|
|
280
|
+
? [
|
|
281
|
+
`STALE #${row.number} ${base} changed ${row.staleFiles.length} file(s) it writes since its base: ${listFiles(row.staleFiles)}`,
|
|
282
|
+
]
|
|
283
|
+
: []
|
|
284
|
+
|
|
244
285
|
export interface Classification {
|
|
245
286
|
baseline: Baseline
|
|
246
287
|
isChanged: boolean
|
|
@@ -266,8 +307,10 @@ const classifyFirstSighting = (
|
|
|
266
307
|
head: row.head,
|
|
267
308
|
merges: row.merges,
|
|
268
309
|
number: row.number,
|
|
310
|
+
overlaps: 'none',
|
|
269
311
|
prior: row.prior,
|
|
270
312
|
resp: String(row.resp),
|
|
313
|
+
stale: row.stale,
|
|
271
314
|
state: row.heading,
|
|
272
315
|
ui: row.ui,
|
|
273
316
|
unmatched: String(row.unmatchedCount),
|
|
@@ -280,7 +323,11 @@ const classifyFirstSighting = (
|
|
|
280
323
|
line = `OPENED #${row.number} at ${at}`
|
|
281
324
|
}
|
|
282
325
|
|
|
283
|
-
return {
|
|
326
|
+
return {
|
|
327
|
+
baseline,
|
|
328
|
+
isChanged: true,
|
|
329
|
+
lines: [line, ...staleLine(row, context.base)],
|
|
330
|
+
}
|
|
284
331
|
}
|
|
285
332
|
|
|
286
333
|
const classifyUi = (row: Row, old: Baseline): string[] => {
|
|
@@ -312,11 +359,23 @@ export const classify = (
|
|
|
312
359
|
let state = row.heading
|
|
313
360
|
|
|
314
361
|
// A conflict arrives from the base moving, not from the branch, so it is
|
|
315
|
-
// reported on the transition rather than only when the head changes.
|
|
316
|
-
|
|
362
|
+
// reported on the transition rather than only when the head changes. A push
|
|
363
|
+
// that leaves it conflicting reports it again, for the same reason STALE does.
|
|
364
|
+
if (
|
|
365
|
+
row.merges === 'conflict' &&
|
|
366
|
+
(old.merges !== 'conflict' || row.head !== old.head)
|
|
367
|
+
) {
|
|
317
368
|
lines.push(`CONFLICT #${row.number} no longer merges into ${context.base}`)
|
|
318
369
|
}
|
|
319
370
|
|
|
371
|
+
// Staleness arrives the same way, and reports on its transition so a branch
|
|
372
|
+
// left behind across several runs earns one handback rather than one a run.
|
|
373
|
+
// A push that leaves it behind reports it again, since the draft lift reads a
|
|
374
|
+
// MOVED with no STALE beside it as the rebase having landed.
|
|
375
|
+
if (old.stale !== 'stale' || row.head !== old.head) {
|
|
376
|
+
lines.push(...staleLine(row, context.base))
|
|
377
|
+
}
|
|
378
|
+
|
|
320
379
|
// A rising count is what is new here. It fires on a tracked pull request
|
|
321
380
|
// only: a first sighting takes whatever already sits on the thread as its
|
|
322
381
|
// baseline rather than flagging history retroactively.
|
|
@@ -386,8 +445,10 @@ export const classify = (
|
|
|
386
445
|
head: row.head,
|
|
387
446
|
merges: row.merges,
|
|
388
447
|
number: row.number,
|
|
448
|
+
overlaps: old.overlaps,
|
|
389
449
|
prior: row.prior,
|
|
390
450
|
resp: String(row.resp),
|
|
451
|
+
stale: row.stale,
|
|
391
452
|
state,
|
|
392
453
|
ui: row.ui === '' ? old.ui : row.ui,
|
|
393
454
|
unmatched: String(row.unmatchedCount),
|
|
@@ -396,6 +457,29 @@ export const classify = (
|
|
|
396
457
|
return { baseline, isChanged: lines.length > 0, lines }
|
|
397
458
|
}
|
|
398
459
|
|
|
460
|
+
/**
|
|
461
|
+
* One pull request's base movement as a single line. STALE and CONFLICT print
|
|
462
|
+
* in the poll only on a transition or a push, so a reader deciding a draft
|
|
463
|
+
* lift reads this instead. A reading that could not be taken is unknown, never
|
|
464
|
+
* clear.
|
|
465
|
+
*/
|
|
466
|
+
export const standingLine = (
|
|
467
|
+
entry: Pick<Baseline, 'head' | 'merges' | 'number' | 'stale'>,
|
|
468
|
+
): string => {
|
|
469
|
+
const at = short(entry.head)
|
|
470
|
+
if (entry.merges === 'conflict') {
|
|
471
|
+
return `CONFLICT #${entry.number} standing at ${at}`
|
|
472
|
+
}
|
|
473
|
+
if (entry.stale === 'stale') {
|
|
474
|
+
return `STALE #${entry.number} standing at ${at}`
|
|
475
|
+
}
|
|
476
|
+
if (entry.merges === 'clean' && entry.stale === 'fresh') {
|
|
477
|
+
return `CLEAR #${entry.number} at ${at}`
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
return `UNKNOWN #${entry.number} at ${at}, whether it merges or is current could not be read`
|
|
481
|
+
}
|
|
482
|
+
|
|
399
483
|
/**
|
|
400
484
|
* A pull request this run could not read keeps the line it had, so the GONE
|
|
401
485
|
* sweep does not read the gap as a merge. Its heading is blanked to a value no
|
|
@@ -532,6 +616,152 @@ const readMerges = (number: string, head: string, ref: string): string => {
|
|
|
532
616
|
: 'conflict'
|
|
533
617
|
}
|
|
534
618
|
|
|
619
|
+
interface Written {
|
|
620
|
+
forkPoint: string
|
|
621
|
+
paths: string[]
|
|
622
|
+
}
|
|
623
|
+
|
|
624
|
+
const nonEmptyLines = (output: string): string[] =>
|
|
625
|
+
output.split('\n').filter((line) => line !== '')
|
|
626
|
+
|
|
627
|
+
/**
|
|
628
|
+
* The paths a pull request writes, read from its own diff against the commit it
|
|
629
|
+
* branched from. `--no-renames` lists both sides of a rename, since a change on
|
|
630
|
+
* the base to either one is a change under the branch. A head or base that does
|
|
631
|
+
* not resolve, which a fork or a deleted head leaves, reads as null.
|
|
632
|
+
*/
|
|
633
|
+
const readWritten = (head: string, ref: string): null | Written => {
|
|
634
|
+
const forkPoint = run('git', ['merge-base', ref, head])
|
|
635
|
+
if (!forkPoint.isOk) return null
|
|
636
|
+
const point = forkPoint.stdout.trim()
|
|
637
|
+
const diff = run('git', ['diff', '--name-only', '--no-renames', point, head])
|
|
638
|
+
if (!diff.isOk) return null
|
|
639
|
+
|
|
640
|
+
return { forkPoint: point, paths: nonEmptyLines(diff.stdout) }
|
|
641
|
+
}
|
|
642
|
+
|
|
643
|
+
/**
|
|
644
|
+
* Re-testing after every merge to the base is a merge queue's cost without its
|
|
645
|
+
* batching, so only a change to a path the branch writes makes it stale. The
|
|
646
|
+
* net diff rather than the log is read, so a change the base made and then
|
|
647
|
+
* reverted moves nothing.
|
|
648
|
+
*/
|
|
649
|
+
const readStale = (
|
|
650
|
+
written: null | Written,
|
|
651
|
+
ref: string,
|
|
652
|
+
): { files: string[]; stale: Staleness } => {
|
|
653
|
+
if (written === null) return { files: [], stale: 'unknown' }
|
|
654
|
+
if (written.paths.length === 0) return { files: [], stale: 'fresh' }
|
|
655
|
+
|
|
656
|
+
const moved = run('git', [
|
|
657
|
+
'--literal-pathspecs',
|
|
658
|
+
'diff',
|
|
659
|
+
'--name-only',
|
|
660
|
+
'--no-renames',
|
|
661
|
+
written.forkPoint,
|
|
662
|
+
ref,
|
|
663
|
+
'--',
|
|
664
|
+
...written.paths,
|
|
665
|
+
])
|
|
666
|
+
if (!moved.isOk) return { files: [], stale: 'unknown' }
|
|
667
|
+
|
|
668
|
+
const files = nonEmptyLines(moved.stdout)
|
|
669
|
+
|
|
670
|
+
return { files, stale: files.length > 0 ? 'stale' : 'fresh' }
|
|
671
|
+
}
|
|
672
|
+
|
|
673
|
+
export interface Overlap {
|
|
674
|
+
files: string[]
|
|
675
|
+
pair: readonly [string, string]
|
|
676
|
+
}
|
|
677
|
+
|
|
678
|
+
/**
|
|
679
|
+
* Pairwise merge-tree grows with the square of open pull requests, so only a
|
|
680
|
+
* pair sharing a written path is tested. Each pair comes lower number first,
|
|
681
|
+
* which fixes the order its line and its baseline entry are read in.
|
|
682
|
+
*/
|
|
683
|
+
export const sharedPairs = (rows: Row[]): [Row, Row][] => {
|
|
684
|
+
const readable = rows
|
|
685
|
+
.filter((row) => row.paths !== null && row.paths.length > 0)
|
|
686
|
+
.toSorted((a, b) => Number(a.number) - Number(b.number))
|
|
687
|
+
const pairs: [Row, Row][] = []
|
|
688
|
+
for (const [index, first] of readable.entries()) {
|
|
689
|
+
const written = new Set(first.paths)
|
|
690
|
+
for (const second of readable.slice(index + 1)) {
|
|
691
|
+
if ((second.paths ?? []).some((path) => written.has(path))) {
|
|
692
|
+
pairs.push([first, second])
|
|
693
|
+
}
|
|
694
|
+
}
|
|
695
|
+
}
|
|
696
|
+
|
|
697
|
+
return pairs
|
|
698
|
+
}
|
|
699
|
+
|
|
700
|
+
const partnersOf = (entry: Baseline | undefined): string[] =>
|
|
701
|
+
entry === undefined || entry.overlaps === '' || entry.overlaps === 'none'
|
|
702
|
+
? []
|
|
703
|
+
: entry.overlaps.split(',')
|
|
704
|
+
|
|
705
|
+
/**
|
|
706
|
+
* A partner this run could not read keeps its place, so a pull request that
|
|
707
|
+
* drops out of one run and returns does not report the same pair twice.
|
|
708
|
+
*/
|
|
709
|
+
export const overlapPartners = (
|
|
710
|
+
number: string,
|
|
711
|
+
overlaps: Overlap[],
|
|
712
|
+
old: Baseline | undefined,
|
|
713
|
+
carried: Set<string>,
|
|
714
|
+
): string => {
|
|
715
|
+
const partners = new Set(
|
|
716
|
+
overlaps.flatMap(({ pair: [first, second] }) =>
|
|
717
|
+
first === number ? [second] : second === number ? [first] : [],
|
|
718
|
+
),
|
|
719
|
+
)
|
|
720
|
+
for (const partner of partnersOf(old)) {
|
|
721
|
+
if (carried.has(partner)) partners.add(partner)
|
|
722
|
+
}
|
|
723
|
+
const sorted = [...partners].toSorted((a, b) => Number(a) - Number(b))
|
|
724
|
+
|
|
725
|
+
return sorted.length === 0 ? 'none' : sorted.join(',')
|
|
726
|
+
}
|
|
727
|
+
|
|
728
|
+
/** A pair reports once, read off the lower-numbered side's baseline entry. */
|
|
729
|
+
export const overlapLines = (
|
|
730
|
+
overlaps: Overlap[],
|
|
731
|
+
known: Map<string, Baseline>,
|
|
732
|
+
): string[] =>
|
|
733
|
+
overlaps
|
|
734
|
+
.filter(
|
|
735
|
+
({ pair: [first, second] }) =>
|
|
736
|
+
!partnersOf(known.get(first)).includes(second),
|
|
737
|
+
)
|
|
738
|
+
.map(
|
|
739
|
+
({ files, pair: [first, second] }) =>
|
|
740
|
+
`OVERLAP #${first} #${second} conflict on ${listFiles(files)}`,
|
|
741
|
+
)
|
|
742
|
+
|
|
743
|
+
/**
|
|
744
|
+
* Two pull request heads merged against each other, which is the merge the
|
|
745
|
+
* second to land will have to make. merge-tree exits 1 on a conflict and
|
|
746
|
+
* prints the tree then one conflicted path a line. Any other failure prints no
|
|
747
|
+
* tree, and reads as null beside a clean merge, since neither names a file.
|
|
748
|
+
*/
|
|
749
|
+
const readOverlap = (first: string, second: string): null | string[] => {
|
|
750
|
+
const merged = run('git', [
|
|
751
|
+
'merge-tree',
|
|
752
|
+
'--write-tree',
|
|
753
|
+
'--name-only',
|
|
754
|
+
'--no-messages',
|
|
755
|
+
first,
|
|
756
|
+
second,
|
|
757
|
+
])
|
|
758
|
+
if (merged.isOk) return null
|
|
759
|
+
const lines = nonEmptyLines(merged.stdout)
|
|
760
|
+
if (lines.length < 2) return null
|
|
761
|
+
|
|
762
|
+
return [...new Set(lines.slice(1))]
|
|
763
|
+
}
|
|
764
|
+
|
|
535
765
|
const buildRow = (
|
|
536
766
|
number: string,
|
|
537
767
|
payload: Payload,
|
|
@@ -545,17 +775,23 @@ const buildRow = (
|
|
|
545
775
|
run('canon', ['pr', 'review-state', number, '--json']).stdout,
|
|
546
776
|
)
|
|
547
777
|
const scope = scopeFromVerb(record, now) ?? scopeFallback(payload, now)
|
|
778
|
+
const merges = readMerges(number, head, ref)
|
|
779
|
+
const written = merges === 'unknown' ? null : readWritten(head, ref)
|
|
780
|
+
const stale = readStale(written, ref)
|
|
548
781
|
|
|
549
782
|
return {
|
|
550
783
|
age: scope.age,
|
|
551
784
|
head,
|
|
552
785
|
heading: scope.state,
|
|
553
|
-
merges
|
|
786
|
+
merges,
|
|
554
787
|
number,
|
|
555
788
|
passAt: scope.readAt,
|
|
789
|
+
paths: written?.paths ?? null,
|
|
556
790
|
prior: scope.commit,
|
|
557
791
|
replyAt: replies.newestAt,
|
|
558
792
|
resp: replies.count,
|
|
793
|
+
stale: stale.stale,
|
|
794
|
+
staleFiles: stale.files,
|
|
559
795
|
ui: uiState(payload, head),
|
|
560
796
|
unmatchedCount: unmatched.count,
|
|
561
797
|
unmatchedHeading: unmatched.heading,
|
|
@@ -573,7 +809,37 @@ const countSince =
|
|
|
573
809
|
return log.isOk ? log.stdout.split('\n').filter((l) => l !== '').length : 0
|
|
574
810
|
}
|
|
575
811
|
|
|
576
|
-
|
|
812
|
+
/**
|
|
813
|
+
* `--check <number>` reads the base and the head at call time and writes
|
|
814
|
+
* nothing. The baseline is as old as the last full run, and a sibling merging
|
|
815
|
+
* after it would leave a stored reading claiming clear.
|
|
816
|
+
*/
|
|
817
|
+
const check = (number: string): number => {
|
|
818
|
+
const ref = baseRef()
|
|
819
|
+
run('git', ['fetch', '-q', 'origin', baseBranch(ref)])
|
|
820
|
+
const head = readHead(number)
|
|
821
|
+
if (head === '') {
|
|
822
|
+
console.error(
|
|
823
|
+
`poll: #${number} returned no head, so nothing is known about it`,
|
|
824
|
+
)
|
|
825
|
+
|
|
826
|
+
return 1
|
|
827
|
+
}
|
|
828
|
+
const merges = readMerges(number, head, ref)
|
|
829
|
+
const written = merges === 'unknown' ? null : readWritten(head, ref)
|
|
830
|
+
console.log(
|
|
831
|
+
standingLine({
|
|
832
|
+
head,
|
|
833
|
+
merges,
|
|
834
|
+
number,
|
|
835
|
+
stale: readStale(written, ref).stale,
|
|
836
|
+
}),
|
|
837
|
+
)
|
|
838
|
+
|
|
839
|
+
return 0
|
|
840
|
+
}
|
|
841
|
+
|
|
842
|
+
const main = (args: string[]): number => {
|
|
577
843
|
const root = mainRoot()
|
|
578
844
|
if (root === null) {
|
|
579
845
|
console.error('poll: not a git repository, so nothing is classified')
|
|
@@ -583,6 +849,7 @@ const main = (): number => {
|
|
|
583
849
|
|
|
584
850
|
// The baseline is per-machine mutable state, so it stays in gitignored
|
|
585
851
|
// scratch even though the script is tracked.
|
|
852
|
+
if (args[0] === '--check') return check(args[1] ?? '')
|
|
586
853
|
const stateDir = join(root, '.canon', 'tmp', 'pr', 'poll')
|
|
587
854
|
mkdirSync(stateDir, { recursive: true })
|
|
588
855
|
const statePath = join(stateDir, 'baseline.txt')
|
|
@@ -655,6 +922,22 @@ const main = (): number => {
|
|
|
655
922
|
snapshots.push({ row: buildRow(number, payload, head, ref) })
|
|
656
923
|
}
|
|
657
924
|
|
|
925
|
+
const rows = snapshots.flatMap((snapshot) =>
|
|
926
|
+
'row' in snapshot ? [snapshot.row] : [],
|
|
927
|
+
)
|
|
928
|
+
const carried = new Set(
|
|
929
|
+
snapshots.flatMap((snapshot) =>
|
|
930
|
+
'baseline' in snapshot ? [snapshot.baseline.number] : [],
|
|
931
|
+
),
|
|
932
|
+
)
|
|
933
|
+
const overlaps = sharedPairs(rows).flatMap(([first, second]) => {
|
|
934
|
+
const files = readOverlap(first.head, second.head)
|
|
935
|
+
|
|
936
|
+
return files === null
|
|
937
|
+
? []
|
|
938
|
+
: [{ files, pair: [first.number, second.number] as const }]
|
|
939
|
+
})
|
|
940
|
+
|
|
658
941
|
let isChanged = false
|
|
659
942
|
const final: string[] = []
|
|
660
943
|
const seen = new Set<string>()
|
|
@@ -667,13 +950,24 @@ const main = (): number => {
|
|
|
667
950
|
|
|
668
951
|
const { row } = snapshot
|
|
669
952
|
seen.add(row.number)
|
|
670
|
-
const
|
|
953
|
+
const old = known.get(row.number)
|
|
954
|
+
const result = classify(row, old, {
|
|
671
955
|
base,
|
|
672
956
|
countSince: countSince(row.number),
|
|
673
957
|
})
|
|
674
958
|
for (const line of result.lines) console.log(line)
|
|
675
959
|
if (result.isChanged) isChanged = true
|
|
676
|
-
final.push(
|
|
960
|
+
final.push(
|
|
961
|
+
formatBaseline({
|
|
962
|
+
...result.baseline,
|
|
963
|
+
overlaps: overlapPartners(row.number, overlaps, old, carried),
|
|
964
|
+
}),
|
|
965
|
+
)
|
|
966
|
+
}
|
|
967
|
+
|
|
968
|
+
for (const line of overlapLines(overlaps, known)) {
|
|
969
|
+
console.log(line)
|
|
970
|
+
isChanged = true
|
|
677
971
|
}
|
|
678
972
|
|
|
679
973
|
for (const entry of previous) {
|
|
@@ -688,4 +982,4 @@ const main = (): number => {
|
|
|
688
982
|
return 0
|
|
689
983
|
}
|
|
690
984
|
|
|
691
|
-
if (import.meta.main) process.exitCode = main()
|
|
985
|
+
if (import.meta.main) process.exitCode = main(process.argv.slice(2))
|
|
@@ -89,4 +89,4 @@ four carried at least one, caught only because the planner ran
|
|
|
89
89
|
- The mechanical addressee resolution, which `session-relay` owns for every session with or without a role
|
|
90
90
|
- The branch, the build, and the pull request, which `role-worker` and `auto-ship` own
|
|
91
91
|
- Deciding which rows run and in what order, which is the controlling session's call and stated in `role-orchestrator`
|
|
92
|
-
- The dispatch itself, its collision check, and its
|
|
92
|
+
- The dispatch itself, its collision check, and its conflict check, which `orchestrator-dispatch.md` holds
|
|
@@ -42,7 +42,7 @@ Each item below is something a record needed and a launch string did not carry.
|
|
|
42
42
|
- The task file, and its `## Findings` before deciding anything. A row can carry its own disproof under a title that still states the original claim, which is how one trial nearly planned against a premise the file had already recorded as dead.
|
|
43
43
|
- The source files themselves, opened rather than summarized. Never a count quoted from the task file, which was wrong or stale in ten places across four plans.
|
|
44
44
|
- `CLAUDE.md` and `canon/ARCHITECTURE.md`, for the decision and the alternative it was taken against rather than for the decision alone.
|
|
45
|
-
- `.canon/tasks/priority.md` for the row's Touches column and its stated blocker. That column is the file set the dispatch
|
|
45
|
+
- `.canon/tasks/priority.md` for the row's Touches column and its stated blocker. That column is the file set the dispatch conflict check already reads. Read it for the shared-path lines and the holds of the constraint block, never as a gate a plan must clear.
|
|
46
46
|
|
|
47
47
|
Two reads belong to the `plan-feature` path alone, since neither a groundwork
|
|
48
48
|
track nor an intake pass is sequenced behind another row or validated as a plan:
|
|
@@ -60,7 +60,7 @@ it.
|
|
|
60
60
|
- Run `canon sessions list --json`, take the `branch` field of every session whose `repository` matches this one and whose branch is neither null nor the trunk branch, union that with every `headRefName` from `gh pr list --json number,headRefName`, dedupe by branch name, then diff each with `git diff --name-only main...<branch>` for its file set. That resolves locally whether or not a pull request exists, since every track in this repository shares one git directory across its worktrees.
|
|
61
61
|
- A bare branch or worktree with neither a live session nor a pull request behind it is still not evidence, since this repository squash-merges and leaves both behind. The roster read is already filtered to live sessions, so what changed is the second source composed with it rather than a raw count. It still misses a live session that reached its worktree through the direct-path fallback rather than `EnterWorktree`, since that session's registered branch never moves off the trunk.
|
|
62
62
|
- Run the read once per row rather than once per batch. A session working several rows ages its picture of the tree while it goes, and this read is what dates it.
|
|
63
|
-
- Name each in-flight set as a constraint,
|
|
63
|
+
- Name each in-flight set as a constraint, being a hold that says which act it forbids or a shared path that says which branch rebases, and stamp the block with the commit the tree was read at. A bare path list leaves the reader guessing. The plan standard fixes that block for a feature plan, and a groundwork or intake record carries the same three parts in whatever shape its own standard states.
|
|
64
64
|
|
|
65
65
|
## The board is read-only
|
|
66
66
|
|
|
@@ -25,7 +25,7 @@ from here.
|
|
|
25
25
|
|
|
26
26
|
## Where the session stands
|
|
27
27
|
|
|
28
|
-
- Write the review comment through `review-pr` and the body file it names under `.canon/tmp/pr/review/`, and nothing else. A UI reviewer writes the comment `review-ui` posts, its body file, and the browser's snapshots and frames, all under `.canon/tmp/pr/review-ui
|
|
28
|
+
- Write the review comment through `review-pr` and the body file it names under `.canon/tmp/pr/review/`, and nothing else. A UI reviewer writes the comment `review-ui` posts, its body file, and the browser's snapshots and frames, all under `.canon/tmp/pr/review-ui/`, and ticks the boxes it passed on the evidence comment through `canon pr tick`. A tracked file, a branch, a worktree, a board file, and a commit all sit outside what this session may touch.
|
|
29
29
|
- Never enter a worktree and never check the branch out. The pass reads a change, and a checkout is the first step toward editing it.
|
|
30
30
|
- Read a file a finding rests on at the head `review-pr` resolved, through `git show <head>:<path>`. The main worktree holds the trunk, so a finding read off its copy is about a tree the pull request never changed, and it reads as confidently as one that is right.
|
|
31
31
|
- Resolve `.canon/plans/`, `.canon/tasks/`, and `.canon/tmp/` at the main worktree root, and send the body file as a heredoc, the main-root route `session-worktree` states. Report a plan or task that fails to resolve there as unreadable, naming the path, rather than reviewing as though the branch had none.
|
|
@@ -49,7 +49,7 @@ The controlling session cannot watch this one build, so the messages below are
|
|
|
49
49
|
owed and nothing else.
|
|
50
50
|
|
|
51
51
|
- Announce the pull request as the ship chain's pull request step returns, carrying the number, the branch, and the task it closes. That transition is the one moment only this session knows, and the controller's review poll no longer starts on a dispatch because of it.
|
|
52
|
-
- Carry the reach in that same message: the paths this branch wrote that another live plan or `## Run now` row holds, and who holds each, which `canon tasks plan-reach` reports as `claimed`. The gate cleared this branch against a prediction it has since outgrown, so the controller holds a
|
|
52
|
+
- Carry the reach in that same message: the paths this branch wrote that another live plan or `## Run now` row holds, and who holds each, which `canon tasks plan-reach` reports as `claimed`. Report each as a rebase the controller should order, never as a breach, since sharing a path is not a hold. The gate cleared this branch against a prediction it has since outgrown, so the controller holds a reading of what this branch shares that stopped being true hours ago and nothing else tells it. Say the reach was unread rather than clear when the installed binary carries no such subcommand, and leave the undeclared list to the pull request.
|
|
53
53
|
- Carry a row this build turned up in that same message, with its title, its origin, and the surface it would take. Send it as its own message only when the session will end before a pull request opens, since the row has no other way to reach the controller.
|
|
54
54
|
- Announce when an address-review pass finishes, as `review-address` Step 8 returns, carrying what was addressed and the PR's new CI state. That transition is the other moment only this session knows, and it is what tells the controller to re-review rather than leaving it to poll for an answer nothing marks as landed.
|
|
55
55
|
- Send a block out as a message before it becomes an interactive prompt.
|
|
@@ -84,7 +84,8 @@ feature branch and a controller does not.
|
|
|
84
84
|
- Refuse an instruction the tree contradicts, and carry the evidence with it. Name the commands read and the consequences of complying, rather than reporting reluctance.
|
|
85
85
|
- Halt on a plan question written as needing the operator's call. The plan standard defines that as a stop for an executing session, so the dispatch that sent it unattended is the defect and the halt is not.
|
|
86
86
|
- Halting is cheap and guessing is not. Four measured halts each cost the dispatcher one reply, and the correction that mattered most reached the right place because a worker argued back rather than complying.
|
|
87
|
-
-
|
|
87
|
+
- Write a file a constraint names as shared, and keep both sides on the rebase. A shared path forbids nothing, so the hunk comparison below does not apply to it.
|
|
88
|
+
- Read a file named as held, whether by a plan's Constraints block or by a controller's message sent mid-build, at the hunk against the branch or pull request holding it before conceding it. A hold is a dependency, a contract, a relocation, or a sweep.
|
|
88
89
|
- Report that reading to the controller as the hunks or line ranges on each side, and take its reply as the call on whether the file is kept or given up, not a decision this session makes alone.
|
|
89
90
|
- When the holding track carries no commits yet, there is nothing to diff. Compare against the file set its plan declares instead, and report that the hunk comparison could not be taken, rather than conceding on the name or proceeding as if the sets never collided.
|
|
90
91
|
- A dispatcher once named three files a branch was writing. The worker diffed that branch, found its hunks at two lines against its own at three others, and reported the comparison, and the dispatcher reversed the instruction and returned every file.
|
|
@@ -41,7 +41,7 @@ Write each box so a session driving the running app can run it without guessing:
|
|
|
41
41
|
- **Taste.** End the line with `(taste)` when the result is a judgment of look or feel rather than a fact a driver can observe. A driver reports such a box as needing a person's eyes instead of passing it.
|
|
42
42
|
- **Setup.** A box needing a state the page does not open in states the setup inline. When a hosted preview cannot reach that state, such as a stopped service or a seeded record, end the line with `(local only)` instead.
|
|
43
43
|
|
|
44
|
-
The checkbox stays `- [ ]` followed by the action and the result, since the evidence comment
|
|
44
|
+
The checkbox stays `- [ ]` followed by the action and the result, since the evidence comment reads boxes by that shape. Only the UI reviewer ticks a box, stamping it with the commit it passed at, and the comment carries that tick forward only while the head it names is current.
|
|
45
45
|
|
|
46
46
|
## The missing-test list
|
|
47
47
|
|
package/docs/agents/canvas.md
CHANGED
|
@@ -39,23 +39,23 @@ A pick is refused as `address-mismatch` when the browser builds elements the fil
|
|
|
39
39
|
|
|
40
40
|
## Edit
|
|
41
41
|
|
|
42
|
-
`canvas edit <page>/<frame> --element <index> --set <property>=<value>` sets one property of the element at that index, the same index `canvas selection` reports. The properties are `text`, `color`, `background-color`, `font-size`, `font-weight`, `width`, `height`, `padding`, `gap`, `display`, `flex-direction`, `justify-content`, `align-items`, `flex-wrap`, `opacity`, `border-radius`, `border-top-left-radius`, `border-top-right-radius`, `border-bottom-right-radius`, and `border-bottom-left-radius`. A style property is written into the element's inline `style`, replacing that property and leaving the others as they were, and an empty value drops it. Writing `border-radius` or `padding` also drops that shorthand's longhands from the same `style`, so no corner or side left over overrides the new value. Writing a longhand leaves its shorthand in place, since the longhand comes later and wins.
|
|
42
|
+
`canvas edit <page>/<frame> --element <index> --set <property>=<value>` sets one property of the element at that index, the same index `canvas selection` reports. The properties are `text`, `color`, `background-color`, `font-size`, `font-weight`, `font-family`, `line-height`, `letter-spacing`, `text-align`, `width`, `height`, `padding`, `gap`, `display`, `flex-direction`, `justify-content`, `align-items`, `flex-wrap`, `opacity`, `border-radius`, `border-top-left-radius`, `border-top-right-radius`, `border-bottom-right-radius`, and `border-bottom-left-radius`. A style property is written into the element's inline `style`, replacing that property and leaving the others as they were, and an empty value drops it. Writing `border-radius` or `padding` also drops that shorthand's longhands from the same `style`, so no corner or side left over overrides the new value. Writing a longhand leaves its shorthand in place, since the longhand comes later and wins.
|
|
43
43
|
|
|
44
44
|
`text` replaces the text of an element holding text alone and refuses one holding other elements as `not-text-only`. Every byte outside the element stays as it was. `--json` emits the page, frame, file, path, and the file's new `hash`.
|
|
45
45
|
|
|
46
46
|
The operator edits the same set from the inspector, which posts to the same writer with the hash the frame was served with. Dragging a selected element's corner handle writes `width` and then `height` in pixels through that writer, the second edit carrying the hash the first answered. An edit made against a version of the file Claude has since rewritten is refused as `stale-address` and the shell reloads the frame, so nothing lands on a shifted element. Both writers take the frame file's lock, and an edit that keeps the selected element where it was keeps the selection fresh.
|
|
47
47
|
|
|
48
|
-
Size, padding, opacity, and
|
|
48
|
+
Size, padding, opacity, radius, and the type properties show in the inspector for every element. Family and font size list the sheet's `font-family` and `font-size` token groups first and write a pick as `var(--<name>)`, the same as a color. The flex fields show only on a flex container, a grid container gets none, and the inspector sets `display` only to `flex`, so any other `display` change goes through the verb.
|
|
49
49
|
|
|
50
50
|
A color picked from the project's tokens is written as `var(--<name>)`, and below full opacity as a `color-mix()` of that `var()` with `transparent` at the opacity percent, so it keeps following the theme. A browser older than 2023 drops a `color-mix()` value, which leaves the property unset there. A hex is written as six digits, or eight below full opacity. The inspector's full color picker writes a hex for any color it sets, so the field marks the value raw, and only its alpha keeps a token through the `color-mix()` form.
|
|
51
51
|
|
|
52
|
-
Inline style beats a class, so an operator's edit masks a class change Claude makes later. Read a frame's inline styles before restyling it, and remove one the change should replace. The inspector marks a color
|
|
52
|
+
Inline style beats a class, so an operator's edit masks a class change Claude makes later. Read a frame's inline styles before restyling it, and remove one the change should replace. The inspector marks a color, a background, or a family or font size the sheet has tokens for, set inline, with the word `raw` when its value names no `var()` and is not `currentColor` or a CSS-wide keyword, since that value stays fixed when the theme changes. A property outside the set, or a value carrying `;`, `{`, `}`, `<`, `>`, or a line break, is refused as `invalid-edit`.
|
|
53
53
|
|
|
54
54
|
The Theme tab in the shell lists what the token stylesheet defines, grouped as color, spacing, radius, font family, font size, and other. The page record at `/api/pages` carries the same list as `tokens.groups`.
|
|
55
55
|
|
|
56
56
|
## Mark a frame being edited
|
|
57
57
|
|
|
58
|
-
`canvas editing <page>/<frame>` marks a frame as being edited, so an open canvas
|
|
58
|
+
`canvas editing <page>/<frame>` marks a frame as being edited, so an open canvas shows who is working on it before the write lands. The shell draws the mark as `<by> editing` on that frame's label with a dashed outline around it, and drops it on the reread after `--done` or at the mark's `until` with no reread. Run it before writing a frame and again with `--done` once the edits are written. A bare `canvas editing` lists the live marks, and `--json` emits `{ ok, editing }` after each of the three, with each mark carrying `page`, `frame`, `by`, `since`, and `until`.
|
|
59
59
|
|
|
60
60
|
The label is `--by <name>` when given. Without it, the verb reads the caller's name off the session roster, and falls back to `a session` when the environment names no session or the roster holds none matching it.
|
|
61
61
|
|
package/docs/agents/gate.md
CHANGED
|
@@ -86,4 +86,6 @@ One code for a failure, which is what a `bun run` caller and a git hook both rea
|
|
|
86
86
|
|
|
87
87
|
A failing stage carries its remedy in `failure`, and the same line goes to stderr so a caller reading neither the record nor the frame is still told what to fix.
|
|
88
88
|
|
|
89
|
-
`ms` carries the wall time each stage
|
|
89
|
+
`ms` carries the wall time each stage took, including every process spawn, and the top-level `ms` sums them. Neither reading changes what a stage measures. Both exist so a slow run is attributable to a stage rather than read off a stopwatch held against the whole thing.
|
|
90
|
+
|
|
91
|
+
The Tests stage holds a machine lock so one box runs one suite at a time, and its `ms` includes any time it queued behind another worktree's suite. A stage that queued carries `queuedMs`, the part of `ms` spent waiting, which is how to separate a queue from a slow suite. The field is absent on a stage that never waited. The wait is also announced on stderr when it starts, naming the holder's pid and worktree, in every mode.
|
package/docs/agents/index.md
CHANGED
|
@@ -40,6 +40,7 @@ CLI catalog and invocation rules for agents, split by command domain. Start with
|
|
|
40
40
|
- [The pull request local preview](pr-local.md): How canon pr local finds the server a worktree is running, why a server in another worktree is never reported, the refusal reasons it names, and how a close workflow drops the address line with --remove
|
|
41
41
|
- [The pull request preview deploy](pr-preview.md): How canon pr preview finds a fenced Cloudflare Pages deploy, refuses a branch its push filter does not serve, dispatches it on a pull request's branch, reads the alias the run prints, the refusal reasons it names, and why an unfenced deploy is refused before anything runs
|
|
42
42
|
- [Head-sensitive pull request reads](pr-reads.md): Resolving a branch tip from the remote rather than from the pull request object, reading what a review pass covered off its own marker rather than off GitHub's submission stamps, why an empty run list is not a pass, the refusal reasons each verb names, and what the remote read costs
|
|
43
|
+
- [The pull request checklist tick](pr-tick.md): How canon pr tick marks the checklist boxes a UI pass drove, the stamp that names the commit each passed at, why a render at a newer head clears a tick, the refusal reasons it names, and why a taste box is never ticked
|
|
43
44
|
- [Records push](records-push.md): Backing the record folders to a private remote and restoring them, which folders the payload carries, the one-time setup, the refusal reasons, and the hook that runs the push
|
|
44
45
|
- [Records stale](records-stale.md): Reading the memory pen as a review queue, what makes an entry due, how a cited path is read and resolved, the order entries come back in, counting the releases since the requirements and architecture records were last reviewed, and the exit codes and refusals
|
|
45
46
|
- [Records validate](records-validate.md): Validating the session records and the standards corpus, the per-kind checks, which root each kind defaults to, the exit codes, and the refusal reasons
|
|
@@ -61,7 +62,7 @@ CLI catalog and invocation rules for agents, split by command domain. Start with
|
|
|
61
62
|
- [Targets](targets.md): The projects this toolkit installed into, the record the install writes against the sweep that backs it, what bounds each answer, and the cross-target pull request read
|
|
62
63
|
- [Tasks archive](tasks-archive.md): Archiving a shipped task by stem or pull request, declining one decided against, the refusal reasons each carries, and how the plan and ready folder move with the task
|
|
63
64
|
- [Tasks plans](tasks-plans.md): The five verbs that read or write a task's plan, being where it sits and who cites it, whether it waits on the operator, the branch it derives, what a branch reached against what it declared, and writing its Plan line
|
|
64
|
-
- [Tasks validate](tasks-validate.md): Validating the board against the tree, the seven checks, the
|
|
65
|
+
- [Tasks validate](tasks-validate.md): Validating the board against the tree, the seven checks, the five report arrays, the exit codes, and the refusal reasons
|
|
65
66
|
- [Tasks](tasks.md): Claiming the next phase label, recording a pull request number and closing an outcome, listing each task's readiness, and where archiving, the plan verbs, and board validation are described
|
|
66
67
|
- [Teach authoring](teach-authoring.md): Resolving what the next lesson needs before it is written, rendering a lesson body's blocks to HTML, and rewriting the root listing, a contents page, and each lesson's chrome and quiz stepper from its marker regions
|
|
67
68
|
- [Teach](teach.md): Listing learning workspaces with what their records schedule next, opening one with its required files, recording sources and glossary terms, viewing one with its chrome refreshed, where the lesson authoring verbs are described, the refusal reasons, and why every write here runs through a verb
|
|
@@ -33,16 +33,16 @@ its filename carries an image extension (`png`, `jpg`, `jpeg`, `gif`, `webp`,
|
|
|
33
33
|
folder holds whatever else a project keeps beside its captures. This
|
|
34
34
|
repository's own tree carries eight `.md` files, two `.sh` scripts, a `.tsv`,
|
|
35
35
|
a `.json`, and an `.html` file against a single `.png`, and every one of them
|
|
36
|
-
would render as a broken
|
|
36
|
+
would render as a broken image embed without the extension filter.
|
|
37
37
|
|
|
38
38
|
## What the record carries
|
|
39
39
|
|
|
40
40
|
The record groups every evidence path by state, the remainder of its
|
|
41
41
|
directory under the `evidence/` segment, and within a state by filename stem,
|
|
42
42
|
so a `before/hero.png` and an `after/hero.png` naming the same case group as
|
|
43
|
-
one entry
|
|
44
|
-
|
|
45
|
-
image
|
|
43
|
+
one entry, not two files. Each entry carries whether it existed at the base
|
|
44
|
+
commit, so the comment shows a base image or marks it new. All-new states drop
|
|
45
|
+
the Base column and each image is an `<img width="1000">`.
|
|
46
46
|
|
|
47
47
|
`reason` on the record is what a caller branches on, not the exit code:
|
|
48
48
|
|
|
@@ -73,6 +73,7 @@ shows a reviewer, each field present only when that comment holds it:
|
|
|
73
73
|
| `preview` | The hosted `**Preview:**` address on the comment's address line. |
|
|
74
74
|
| `local` | The `**Local preview:**` address beside it. |
|
|
75
75
|
| `checklist` | The checklist between its delimiters, with any ticked boxes kept. |
|
|
76
|
+
| `boxes` | Each box's `number`, `text`, tick, `stamp`, and `isTaste`. |
|
|
76
77
|
|
|
77
78
|
These come from the comment already posted, never from the flags the call
|
|
78
79
|
passed, so a skill driving a review reads which address and which checklist a
|
|
@@ -102,7 +103,7 @@ The trade is that the verb no longer sees evidence that is uncommitted or
|
|
|
102
103
|
unpushed, which the comment's head-pinned image links could not show anyway.
|
|
103
104
|
A path the pull request renamed or copied counts as added, since the new path
|
|
104
105
|
has no counterpart at the merge base, so a rename loses its before image and
|
|
105
|
-
|
|
106
|
+
reads as new. A path the pull request removed is dropped, since it has no
|
|
106
107
|
head image to show. A failed files read refuses as `unreadable-changes` rather
|
|
107
108
|
than rendering a short set, and a missing merge base refuses as `no-base`.
|
|
108
109
|
|
|
@@ -181,9 +182,9 @@ screenshots, and what a reviewer has to look at. The file is what
|
|
|
181
182
|
|
|
182
183
|
A run without the option carries forward whatever checklist the marked comment
|
|
183
184
|
already holds, the same way the preview address is carried. That is what keeps
|
|
184
|
-
`git-followup`'s re-render after a push from
|
|
185
|
-
|
|
186
|
-
|
|
185
|
+
`git-followup`'s re-render after a push from dropping the checklist, since
|
|
186
|
+
`git-pr` deletes the handoff once the first post reports success and no later
|
|
187
|
+
call has a file to pass. `canon docs pr-tick` covers what a tick carries.
|
|
187
188
|
|
|
188
189
|
The option decides `no-evidence` on its own. A branch with a checklist and no
|
|
189
190
|
changed evidence image renders `ok` with a marked body holding the checklist,
|