@erclx/canon 5.45.0 → 5.47.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/claude/.claude-plugin/plugin.json +1 -1
- package/claude/skills/api-design/REQUIREMENT.md +15 -31
- package/claude/skills/auto-ship/REQUIREMENT.md +14 -25
- package/claude/skills/backlog-triage/REQUIREMENT.md +12 -23
- package/claude/skills/build-in-slices/REQUIREMENT.md +15 -25
- package/claude/skills/canon-cli/REQUIREMENT.md +15 -27
- package/claude/skills/canon-feedback/REQUIREMENT.md +10 -18
- package/claude/skills/canon-feedback-triage/REQUIREMENT.md +10 -15
- package/claude/skills/canvas-board/REQUIREMENT.md +14 -28
- package/claude/skills/ci-workflow/REQUIREMENT.md +12 -21
- package/claude/skills/code-craft/REQUIREMENT.md +14 -28
- package/claude/skills/codebase-layout/REQUIREMENT.md +14 -31
- package/claude/skills/context-fold/REQUIREMENT.md +15 -39
- package/claude/skills/context-fold/references/context.md +3 -3
- package/claude/skills/create-rule/REQUIREMENT.md +10 -24
- package/claude/skills/create-rule/SKILL.md +3 -0
- package/claude/skills/create-skill/REQUIREMENT.md +10 -21
- package/claude/skills/decision-escalate/REQUIREMENT.md +12 -24
- package/claude/skills/deploy-app/REQUIREMENT.md +12 -22
- package/claude/skills/deprecation-migration/REQUIREMENT.md +14 -42
- package/claude/skills/design-identity/REQUIREMENT.md +14 -20
- package/claude/skills/design-taste/REQUIREMENT.md +16 -57
- package/claude/skills/docs-sync/REQUIREMENT.md +14 -23
- package/claude/skills/document-health/REQUIREMENT.md +15 -29
- package/claude/skills/document-health/SKILL.md +2 -2
- package/claude/skills/document-repair/REQUIREMENT.md +32 -0
- package/claude/skills/document-repair/SKILL.md +76 -0
- package/claude/skills/document-repair/references/batches.md +44 -0
- package/claude/skills/draft-and-pick/REQUIREMENT.md +4 -3
- package/claude/skills/draft-doc/REQUIREMENT.md +4 -3
- package/claude/skills/draft-figure/REQUIREMENT.md +4 -3
- package/claude/skills/draft-identity/REQUIREMENT.md +4 -3
- package/claude/skills/draft-ready/REQUIREMENT.md +4 -3
- package/claude/skills/draft-screencast/REQUIREMENT.md +4 -3
- package/claude/skills/draft-slides/REQUIREMENT.md +4 -3
- package/claude/skills/draw-figure/REQUIREMENT.md +15 -39
- package/claude/skills/git-branch/REQUIREMENT.md +12 -26
- package/claude/skills/git-commit/REQUIREMENT.md +11 -15
- package/claude/skills/git-followup/REQUIREMENT.md +11 -22
- package/claude/skills/git-followup/SKILL.md +1 -0
- package/claude/skills/git-issue/REQUIREMENT.md +9 -15
- package/claude/skills/git-pr/REQUIREMENT.md +13 -32
- package/claude/skills/git-pr/SKILL.md +11 -2
- package/claude/skills/git-pr/references/closing-issues.md +47 -0
- package/claude/skills/git-pr/references/pr.md +62 -7
- package/claude/skills/git-ship/REQUIREMENT.md +13 -26
- package/claude/skills/git-split/REQUIREMENT.md +10 -17
- package/claude/skills/git-stage/REQUIREMENT.md +12 -18
- package/claude/skills/git-worktree/REQUIREMENT.md +15 -19
- package/claude/skills/make-deck/REQUIREMENT.md +13 -21
- package/claude/skills/markdown-craft/REQUIREMENT.md +9 -3
- package/claude/skills/plan-feature/REQUIREMENT.md +15 -24
- package/claude/skills/plan-groundwork/REQUIREMENT.md +12 -28
- package/claude/skills/plan-groundwork/SKILL.md +5 -2
- package/claude/skills/plan-groundwork/references/output.md +47 -1
- package/claude/skills/plan-intake/REQUIREMENT.md +11 -26
- package/claude/skills/plan-intake/references/intake.md +2 -2
- package/claude/skills/plan-intake-answer/REQUIREMENT.md +14 -29
- package/claude/skills/plan-intake-answer/SKILL.md +7 -1
- package/claude/skills/plan-ready/REQUIREMENT.md +17 -36
- package/claude/skills/pr-evidence/REQUIREMENT.md +11 -17
- package/claude/skills/read-frames/REQUIREMENT.md +3 -8
- package/claude/skills/record-screencast/REQUIREMENT.md +14 -17
- package/claude/skills/review-address/REQUIREMENT.md +14 -38
- package/claude/skills/review-address/SKILL.md +18 -6
- package/claude/skills/review-address/references/post-review-findings.md +4 -1
- package/claude/skills/review-address/references/rebase-only.md +14 -2
- package/claude/skills/review-craft/REQUIREMENT.md +14 -32
- package/claude/skills/review-pr/REQUIREMENT.md +15 -34
- package/claude/skills/review-pr/SKILL.md +9 -2
- package/claude/skills/review-pr/references/body-variants.md +29 -8
- package/claude/skills/review-pr/references/post.md +29 -10
- package/claude/skills/review-ui/REQUIREMENT.md +14 -39
- package/claude/skills/review-ui/SKILL.md +1 -0
- package/claude/skills/review-ui/references/post.md +2 -0
- package/claude/skills/review-ui/references/tick.md +3 -0
- package/claude/skills/role-orchestrator/REQUIREMENT.md +2 -2
- package/claude/skills/role-orchestrator/references/orchestrator-refill.md +5 -0
- package/claude/skills/role-orchestrator/references/orchestrator-review-dispatch.md +9 -0
- package/claude/skills/role-reviewer/REQUIREMENT.md +15 -65
- package/claude/skills/role-reviewer/SKILL.md +8 -1
- package/claude/skills/role-worker/REQUIREMENT.md +2 -2
- package/claude/skills/role-worker/SKILL.md +3 -0
- package/claude/skills/script-screencast/REQUIREMENT.md +10 -16
- package/claude/skills/search-craft/REQUIREMENT.md +12 -36
- package/claude/skills/search-craft/SKILL.md +1 -1
- package/claude/skills/search-craft/references/adopted.md +2 -2
- package/claude/skills/session-compact/REQUIREMENT.md +13 -23
- package/claude/skills/session-map/REQUIREMENT.md +13 -29
- package/claude/skills/session-relay/REQUIREMENT.md +14 -58
- package/claude/skills/session-relay/SKILL.md +6 -0
- package/claude/skills/session-resume/REQUIREMENT.md +13 -27
- package/claude/skills/session-worktree/REQUIREMENT.md +14 -43
- package/claude/skills/systematic-debugging/REQUIREMENT.md +13 -34
- package/claude/skills/target-check/REQUIREMENT.md +16 -23
- package/claude/skills/target-setup/REQUIREMENT.md +11 -34
- package/claude/skills/task-board/REQUIREMENT.md +13 -22
- package/claude/skills/task-board/SKILL.md +7 -0
- package/claude/skills/task-board/references/decline.md +2 -2
- package/claude/skills/task-board/references/tasks.md +1 -1
- package/claude/skills/task-complete/REQUIREMENT.md +11 -24
- package/claude/skills/task-complete/references/archive.md +4 -2
- package/claude/skills/teach-workspace/REQUIREMENT.md +14 -45
- package/claude/skills/test-craft/REQUIREMENT.md +15 -46
- package/claude/skills/test-first/REQUIREMENT.md +14 -20
- package/claude/skills/ui-checklist/REQUIREMENT.md +14 -29
- package/claude/skills/ui-checklist/SKILL.md +3 -3
- package/claude/skills/ux-audit/REQUIREMENT.md +3 -7
- package/claude/skills/ux-measure/REQUIREMENT.md +14 -28
- package/claude/skills/video-craft/REQUIREMENT.md +14 -27
- package/claude/skills/write-doc/REQUIREMENT.md +14 -24
- package/claude/skills/write-human/REQUIREMENT.md +15 -27
- package/claude/skills/youtube-transcripts/REQUIREMENT.md +9 -15
- package/docs/agents/context-audit.md +19 -12
- package/docs/agents/markdown-audit.md +15 -10
- package/docs/target/projects.md +2 -0
- package/docs/workflow/skill-map.md +6 -5
- package/docs/workflow/zshrc-aliases.md +3 -0
- package/governance/renames.toml +0 -2
- package/governance/rules/claude/output.md +5 -1
- package/governance/rules/code/routing.md +5 -1
- package/governance/stacks/base.toml +0 -1
- package/package.json +1 -1
- package/scripts/core/bootstrap.sh +1 -0
- package/src/claude/cases/workflow.ts +12 -0
- package/src/commands/context.ts +57 -3
- package/src/commands/labels.ts +149 -4
- package/src/commands/markdown.ts +188 -21
- package/src/commands/pr.ts +113 -4
- package/src/commands/sessions.ts +58 -11
- package/src/commands/tasks.ts +182 -0
- package/src/commands/worktrees.ts +2 -0
- package/src/context/coverage.ts +210 -0
- package/src/context/gate.ts +12 -3
- package/src/gate/stages.ts +1 -1
- package/src/init/steps.ts +45 -0
- package/src/intake/items.ts +14 -0
- package/src/labels/closing.ts +46 -0
- package/src/labels/density.ts +363 -0
- package/src/markdown/gated.ts +250 -0
- package/src/markdown/scope.ts +115 -2
- package/src/migrate/rename.ts +6 -0
- package/src/pr/evidence.ts +1 -1
- package/src/pr/rest.ts +2 -0
- package/src/pr/tick.ts +89 -0
- package/src/sessions/claim.ts +7 -0
- package/src/sessions/live.ts +79 -6
- package/src/sessions/registry.ts +11 -0
- package/src/sessions/resolve.ts +85 -7
- package/src/tasks/archive.ts +15 -4
- package/src/tasks/closes.ts +136 -0
- package/src/tasks/record.ts +1 -1
- package/src/worktrees/reclaim.ts +16 -1
- package/tooling/base/configs/scripts/verify.ts +1 -1
- package/tooling/base/seeds/.github/pull_request_template.md +1 -1
- package/tooling/claude/seeds/.claude/hooks/board-validate.sh +1 -1
- package/tooling/claude/seeds/CLAUDE.md +2 -2
- package/governance/rules/canon/task-scope.md +0 -12
package/src/markdown/scope.ts
CHANGED
|
@@ -36,12 +36,27 @@ const DENSITY_SCOPE = [
|
|
|
36
36
|
/^(?:README|CONTRIBUTING)\.md$/,
|
|
37
37
|
]
|
|
38
38
|
|
|
39
|
+
/**
|
|
40
|
+
* The roots `canon init` gates in a new project when each passes at init time:
|
|
41
|
+
* every density surface a target authors itself, leaving out the toolkit's own
|
|
42
|
+
* `claude/skills/` and `tooling/` trees.
|
|
43
|
+
*/
|
|
44
|
+
export const GATED_SCOPE_ROOTS: readonly string[] = [
|
|
45
|
+
'canon/context/',
|
|
46
|
+
'docs/',
|
|
47
|
+
'.claude/skills/',
|
|
48
|
+
'wiki/',
|
|
49
|
+
'README.md',
|
|
50
|
+
'CONTRIBUTING.md',
|
|
51
|
+
]
|
|
52
|
+
|
|
39
53
|
/**
|
|
40
54
|
* The path prefixes whose density findings fail a run, each added by the
|
|
41
55
|
* rewrite batch that brought its folders to zero.
|
|
42
56
|
*
|
|
43
57
|
* It describes this repository's rewrite progress and nothing about a target's
|
|
44
|
-
* corpus, which is why the audit reads it only inside a canon checkout.
|
|
58
|
+
* corpus, which is why the audit reads it only inside a canon checkout. A
|
|
59
|
+
* target keeps its own list in the file `src/markdown/gated.ts` reads.
|
|
45
60
|
*/
|
|
46
61
|
export const GATED_PATHS: readonly string[] = [
|
|
47
62
|
'canon/context/surfaces/',
|
|
@@ -50,8 +65,10 @@ export const GATED_PATHS: readonly string[] = [
|
|
|
50
65
|
'canon/context/features/',
|
|
51
66
|
'canon/context/context-model.md',
|
|
52
67
|
'canon/context/index.md',
|
|
68
|
+
'claude/skills/api-design/REQUIREMENT.md',
|
|
53
69
|
'claude/skills/api-design/SKILL.md',
|
|
54
70
|
'claude/skills/api-design/references/adopted.md',
|
|
71
|
+
'claude/skills/auto-ship/REQUIREMENT.md',
|
|
55
72
|
'claude/skills/auto-ship/SKILL.md',
|
|
56
73
|
'claude/skills/auto-ship/references/draft-mark.md',
|
|
57
74
|
'claude/skills/auto-ship/references/failure-recovery.md',
|
|
@@ -60,33 +77,45 @@ export const GATED_PATHS: readonly string[] = [
|
|
|
60
77
|
'claude/skills/auto-ship/references/ui-checklist.md',
|
|
61
78
|
'claude/skills/auto-ship/references/verb-absent.md',
|
|
62
79
|
'claude/skills/auto-ship/references/worktree-name.md',
|
|
80
|
+
'claude/skills/backlog-triage/REQUIREMENT.md',
|
|
63
81
|
'claude/skills/backlog-triage/SKILL.md',
|
|
64
82
|
'claude/skills/backlog-triage/references/apply.md',
|
|
83
|
+
'claude/skills/build-in-slices/REQUIREMENT.md',
|
|
65
84
|
'claude/skills/build-in-slices/SKILL.md',
|
|
66
85
|
'claude/skills/build-in-slices/references/adopted.md',
|
|
86
|
+
'claude/skills/canon-cli/REQUIREMENT.md',
|
|
67
87
|
'claude/skills/canon-cli/SKILL.md',
|
|
88
|
+
'claude/skills/canon-feedback-triage/REQUIREMENT.md',
|
|
68
89
|
'claude/skills/canon-feedback-triage/SKILL.md',
|
|
90
|
+
'claude/skills/canon-feedback/REQUIREMENT.md',
|
|
69
91
|
'claude/skills/canon-feedback/SKILL.md',
|
|
70
92
|
'claude/skills/canon-feedback/references/template.md',
|
|
93
|
+
'claude/skills/canvas-board/REQUIREMENT.md',
|
|
71
94
|
'claude/skills/canvas-board/SKILL.md',
|
|
72
95
|
'claude/skills/canvas-board/references/canvas.md',
|
|
73
96
|
'claude/skills/canvas-board/references/design.md',
|
|
97
|
+
'claude/skills/ci-workflow/REQUIREMENT.md',
|
|
74
98
|
'claude/skills/ci-workflow/SKILL.md',
|
|
75
99
|
'claude/skills/ci-workflow/references/workflows.md',
|
|
100
|
+
'claude/skills/code-craft/REQUIREMENT.md',
|
|
76
101
|
'claude/skills/code-craft/SKILL.md',
|
|
77
102
|
'claude/skills/code-craft/references/adopted.md',
|
|
103
|
+
'claude/skills/codebase-layout/REQUIREMENT.md',
|
|
78
104
|
'claude/skills/codebase-layout/SKILL.md',
|
|
79
105
|
'claude/skills/codebase-layout/references/adopted.md',
|
|
80
106
|
'claude/skills/codebase-layout/references/frontend.md',
|
|
81
107
|
'claude/skills/codebase-layout/references/tests.md',
|
|
108
|
+
'claude/skills/context-fold/REQUIREMENT.md',
|
|
82
109
|
'claude/skills/context-fold/SKILL.md',
|
|
83
110
|
'claude/skills/context-fold/references/classify.md',
|
|
84
111
|
'claude/skills/context-fold/references/completion-report.md',
|
|
85
112
|
'claude/skills/context-fold/references/context-refresh.md',
|
|
86
113
|
'claude/skills/context-fold/references/context.md',
|
|
87
114
|
'claude/skills/context-fold/references/unusable-baseline.md',
|
|
115
|
+
'claude/skills/create-rule/REQUIREMENT.md',
|
|
88
116
|
'claude/skills/create-rule/SKILL.md',
|
|
89
117
|
'claude/skills/create-rule/references/rule.md',
|
|
118
|
+
'claude/skills/create-skill/REQUIREMENT.md',
|
|
90
119
|
'claude/skills/create-skill/SKILL.md',
|
|
91
120
|
'claude/skills/create-skill/references/skill-disclosure.md',
|
|
92
121
|
'claude/skills/create-skill/references/skill-frontmatter.md',
|
|
@@ -95,12 +124,16 @@ export const GATED_PATHS: readonly string[] = [
|
|
|
95
124
|
'claude/skills/create-skill/references/skill-reference.md',
|
|
96
125
|
'claude/skills/create-skill/references/skill-requirement.md',
|
|
97
126
|
'claude/skills/create-skill/references/skill.md',
|
|
127
|
+
'claude/skills/decision-escalate/REQUIREMENT.md',
|
|
98
128
|
'claude/skills/decision-escalate/SKILL.md',
|
|
129
|
+
'claude/skills/deploy-app/REQUIREMENT.md',
|
|
99
130
|
'claude/skills/deploy-app/SKILL.md',
|
|
100
131
|
'claude/skills/deploy-app/references/cloudflare.md',
|
|
101
132
|
'claude/skills/deploy-app/references/vercel.md',
|
|
133
|
+
'claude/skills/deprecation-migration/REQUIREMENT.md',
|
|
102
134
|
'claude/skills/deprecation-migration/SKILL.md',
|
|
103
135
|
'claude/skills/deprecation-migration/references/adopted.md',
|
|
136
|
+
'claude/skills/design-taste/REQUIREMENT.md',
|
|
104
137
|
'claude/skills/design-taste/SKILL.md',
|
|
105
138
|
'claude/skills/design-taste/references/adopted.md',
|
|
106
139
|
'claude/skills/design-taste/references/craft.md',
|
|
@@ -109,14 +142,27 @@ export const GATED_PATHS: readonly string[] = [
|
|
|
109
142
|
'claude/skills/design-taste/references/systems.md',
|
|
110
143
|
'claude/skills/design-taste/references/tells.md',
|
|
111
144
|
'claude/skills/design-taste/references/vocabulary.md',
|
|
145
|
+
'claude/skills/docs-sync/REQUIREMENT.md',
|
|
112
146
|
'claude/skills/docs-sync/SKILL.md',
|
|
113
147
|
'claude/skills/docs-sync/references/docs.md',
|
|
114
148
|
'claude/skills/docs-sync/references/readme.md',
|
|
149
|
+
'claude/skills/document-health/REQUIREMENT.md',
|
|
115
150
|
'claude/skills/document-health/SKILL.md',
|
|
116
151
|
'claude/skills/document-health/references/axes.md',
|
|
152
|
+
'claude/skills/document-repair/REQUIREMENT.md',
|
|
153
|
+
'claude/skills/document-repair/SKILL.md',
|
|
154
|
+
'claude/skills/document-repair/references/batches.md',
|
|
155
|
+
'claude/skills/draft-and-pick/REQUIREMENT.md',
|
|
156
|
+
'claude/skills/draft-doc/REQUIREMENT.md',
|
|
157
|
+
'claude/skills/draft-figure/REQUIREMENT.md',
|
|
158
|
+
'claude/skills/draft-identity/REQUIREMENT.md',
|
|
159
|
+
'claude/skills/draft-ready/REQUIREMENT.md',
|
|
160
|
+
'claude/skills/draft-screencast/REQUIREMENT.md',
|
|
161
|
+
'claude/skills/draft-slides/REQUIREMENT.md',
|
|
117
162
|
'claude/skills/pick-by-eye/REQUIREMENT.md',
|
|
118
163
|
'claude/skills/pick-by-eye/SKILL.md',
|
|
119
164
|
'claude/skills/pick-by-eye/references/live-arms.md',
|
|
165
|
+
'claude/skills/write-doc/REQUIREMENT.md',
|
|
120
166
|
'claude/skills/write-doc/SKILL.md',
|
|
121
167
|
'claude/skills/write-doc/references/context.md',
|
|
122
168
|
'claude/skills/write-doc/references/docs.md',
|
|
@@ -129,76 +175,102 @@ export const GATED_PATHS: readonly string[] = [
|
|
|
129
175
|
'claude/skills/write-doc/references/requirements.md',
|
|
130
176
|
'claude/skills/write-doc/references/wiki-kind.md',
|
|
131
177
|
'claude/skills/write-doc/references/wiki.md',
|
|
178
|
+
'claude/skills/draw-figure/REQUIREMENT.md',
|
|
132
179
|
'claude/skills/draw-figure/SKILL.md',
|
|
133
180
|
'claude/skills/draw-figure/references/architecture-set.md',
|
|
134
181
|
'claude/skills/draw-figure/references/figures.md',
|
|
135
182
|
'claude/skills/draw-figure/references/hand-drawn.md',
|
|
136
183
|
'claude/skills/draw-figure/references/mermaid-path.md',
|
|
137
184
|
'claude/skills/draw-figure/references/mermaid.md',
|
|
185
|
+
'claude/skills/design-identity/REQUIREMENT.md',
|
|
138
186
|
'claude/skills/design-identity/SKILL.md',
|
|
139
187
|
'claude/skills/design-identity/references/card-route.md',
|
|
188
|
+
'claude/skills/plan-ready/REQUIREMENT.md',
|
|
140
189
|
'claude/skills/plan-ready/SKILL.md',
|
|
141
190
|
'claude/skills/plan-ready/references/assembly.md',
|
|
142
191
|
'claude/skills/plan-ready/references/ready.md',
|
|
192
|
+
'claude/skills/script-screencast/REQUIREMENT.md',
|
|
143
193
|
'claude/skills/script-screencast/SKILL.md',
|
|
144
194
|
'claude/skills/script-screencast/references/beats-template.md',
|
|
195
|
+
'claude/skills/make-deck/REQUIREMENT.md',
|
|
145
196
|
'claude/skills/make-deck/SKILL.md',
|
|
146
197
|
'claude/skills/make-deck/references/deck.md',
|
|
198
|
+
'claude/skills/git-branch/REQUIREMENT.md',
|
|
147
199
|
'claude/skills/git-branch/SKILL.md',
|
|
148
200
|
'claude/skills/git-branch/references/branch.md',
|
|
149
201
|
'claude/skills/git-branch/references/slug.md',
|
|
202
|
+
'claude/skills/git-commit/REQUIREMENT.md',
|
|
150
203
|
'claude/skills/git-commit/SKILL.md',
|
|
151
204
|
'claude/skills/git-commit/references/commit.md',
|
|
152
205
|
'claude/skills/git-commit/references/versioning.md',
|
|
206
|
+
'claude/skills/git-followup/REQUIREMENT.md',
|
|
153
207
|
'claude/skills/git-followup/SKILL.md',
|
|
208
|
+
'claude/skills/git-issue/REQUIREMENT.md',
|
|
154
209
|
'claude/skills/git-issue/SKILL.md',
|
|
155
210
|
'claude/skills/git-issue/references/issue.md',
|
|
211
|
+
'claude/skills/git-pr/REQUIREMENT.md',
|
|
156
212
|
'claude/skills/git-pr/SKILL.md',
|
|
157
213
|
'claude/skills/git-pr/references/final-command.md',
|
|
158
214
|
'claude/skills/git-pr/references/labels.md',
|
|
159
215
|
'claude/skills/git-pr/references/pr.md',
|
|
160
216
|
'claude/skills/git-pr/references/publish.md',
|
|
161
217
|
'claude/skills/git-pr/references/task-number.md',
|
|
218
|
+
'claude/skills/git-ship/REQUIREMENT.md',
|
|
162
219
|
'claude/skills/git-ship/SKILL.md',
|
|
163
220
|
'claude/skills/git-ship/references/ci-watch.md',
|
|
221
|
+
'claude/skills/git-split/REQUIREMENT.md',
|
|
164
222
|
'claude/skills/git-split/SKILL.md',
|
|
165
223
|
'claude/skills/git-split/references/independent.md',
|
|
166
224
|
'claude/skills/git-split/references/stacked.md',
|
|
225
|
+
'claude/skills/git-stage/REQUIREMENT.md',
|
|
167
226
|
'claude/skills/git-stage/SKILL.md',
|
|
227
|
+
'claude/skills/git-worktree/REQUIREMENT.md',
|
|
168
228
|
'claude/skills/git-worktree/SKILL.md',
|
|
169
229
|
'claude/skills/git-worktree/references/cleanup.md',
|
|
230
|
+
'claude/skills/markdown-craft/REQUIREMENT.md',
|
|
170
231
|
'claude/skills/markdown-craft/SKILL.md',
|
|
171
232
|
'claude/skills/markdown-craft/references/markdown.md',
|
|
233
|
+
'claude/skills/plan-feature/REQUIREMENT.md',
|
|
172
234
|
'claude/skills/plan-feature/SKILL.md',
|
|
173
235
|
'claude/skills/plan-feature/references/batches.md',
|
|
174
236
|
'claude/skills/plan-feature/references/plan-lifecycle.md',
|
|
175
237
|
'claude/skills/plan-feature/references/plan.md',
|
|
238
|
+
'claude/skills/plan-groundwork/REQUIREMENT.md',
|
|
176
239
|
'claude/skills/plan-groundwork/SKILL.md',
|
|
177
240
|
'claude/skills/plan-groundwork/references/groundwork-spikes.md',
|
|
178
241
|
'claude/skills/plan-groundwork/references/groundwork.md',
|
|
179
242
|
'claude/skills/plan-groundwork/references/output.md',
|
|
243
|
+
'claude/skills/plan-intake-answer/REQUIREMENT.md',
|
|
180
244
|
'claude/skills/plan-intake-answer/SKILL.md',
|
|
245
|
+
'claude/skills/plan-intake/REQUIREMENT.md',
|
|
181
246
|
'claude/skills/plan-intake/SKILL.md',
|
|
182
247
|
'claude/skills/plan-intake/references/intake.md',
|
|
248
|
+
'claude/skills/pr-evidence/REQUIREMENT.md',
|
|
183
249
|
'claude/skills/pr-evidence/SKILL.md',
|
|
184
250
|
'claude/skills/pr-evidence/references/evidence.md',
|
|
251
|
+
'claude/skills/read-frames/REQUIREMENT.md',
|
|
185
252
|
'claude/skills/read-frames/SKILL.md',
|
|
253
|
+
'claude/skills/record-screencast/REQUIREMENT.md',
|
|
186
254
|
'claude/skills/record-screencast/SKILL.md',
|
|
255
|
+
'claude/skills/review-address/REQUIREMENT.md',
|
|
187
256
|
'claude/skills/review-address/SKILL.md',
|
|
188
257
|
'claude/skills/review-address/references/post-review-findings.md',
|
|
189
258
|
'claude/skills/review-address/references/rebase-conflicts.md',
|
|
190
259
|
'claude/skills/review-address/references/rebase-only.md',
|
|
191
260
|
'claude/skills/review-address/references/rest-reads.md',
|
|
261
|
+
'claude/skills/review-craft/REQUIREMENT.md',
|
|
192
262
|
'claude/skills/review-craft/SKILL.md',
|
|
193
263
|
'claude/skills/review-craft/references/adopted.md',
|
|
194
264
|
'claude/skills/review-craft/references/security.md',
|
|
195
265
|
'claude/skills/review-craft/references/ui.md',
|
|
266
|
+
'claude/skills/review-pr/REQUIREMENT.md',
|
|
196
267
|
'claude/skills/review-pr/SKILL.md',
|
|
197
268
|
'claude/skills/review-pr/references/body-variants.md',
|
|
198
269
|
'claude/skills/review-pr/references/close-out.md',
|
|
199
270
|
'claude/skills/review-pr/references/key-changes.md',
|
|
200
271
|
'claude/skills/review-pr/references/post.md',
|
|
201
272
|
'claude/skills/review-pr/references/scope.md',
|
|
273
|
+
'claude/skills/review-ui/REQUIREMENT.md',
|
|
202
274
|
'claude/skills/review-ui/SKILL.md',
|
|
203
275
|
'claude/skills/review-ui/references/close-out.md',
|
|
204
276
|
'claude/skills/review-ui/references/driving.md',
|
|
@@ -225,38 +297,51 @@ export const GATED_PATHS: readonly string[] = [
|
|
|
225
297
|
'claude/skills/role-orchestrator/references/orchestrator-watch.md',
|
|
226
298
|
'claude/skills/role-planner/REQUIREMENT.md',
|
|
227
299
|
'claude/skills/role-planner/SKILL.md',
|
|
300
|
+
'claude/skills/role-reviewer/REQUIREMENT.md',
|
|
228
301
|
'claude/skills/role-reviewer/SKILL.md',
|
|
229
302
|
'claude/skills/role-worker/REQUIREMENT.md',
|
|
230
303
|
'claude/skills/role-worker/SKILL.md',
|
|
304
|
+
'claude/skills/search-craft/REQUIREMENT.md',
|
|
231
305
|
'claude/skills/search-craft/SKILL.md',
|
|
232
306
|
'claude/skills/search-craft/references/adopted.md',
|
|
307
|
+
'claude/skills/session-compact/REQUIREMENT.md',
|
|
233
308
|
'claude/skills/session-compact/SKILL.md',
|
|
234
309
|
'claude/skills/session-compact/references/handoff-note.md',
|
|
310
|
+
'claude/skills/session-map/REQUIREMENT.md',
|
|
235
311
|
'claude/skills/session-map/SKILL.md',
|
|
236
312
|
'claude/skills/session-map/references/session.md',
|
|
313
|
+
'claude/skills/session-relay/REQUIREMENT.md',
|
|
237
314
|
'claude/skills/session-relay/SKILL.md',
|
|
315
|
+
'claude/skills/session-resume/REQUIREMENT.md',
|
|
238
316
|
'claude/skills/session-resume/SKILL.md',
|
|
317
|
+
'claude/skills/session-worktree/REQUIREMENT.md',
|
|
239
318
|
'claude/skills/session-worktree/SKILL.md',
|
|
240
319
|
'claude/skills/session-worktree/references/install-command.md',
|
|
241
320
|
'claude/skills/session-worktree/references/main-root-writes.md',
|
|
321
|
+
'claude/skills/systematic-debugging/REQUIREMENT.md',
|
|
242
322
|
'claude/skills/systematic-debugging/SKILL.md',
|
|
243
323
|
'claude/skills/systematic-debugging/references/adopted.md',
|
|
324
|
+
'claude/skills/target-check/REQUIREMENT.md',
|
|
244
325
|
'claude/skills/target-check/SKILL.md',
|
|
245
326
|
'claude/skills/target-check/references/domains.md',
|
|
327
|
+
'claude/skills/target-setup/REQUIREMENT.md',
|
|
246
328
|
'claude/skills/target-setup/SKILL.md',
|
|
247
329
|
'claude/skills/target-setup/references/detect.md',
|
|
248
330
|
'claude/skills/target-setup/references/indexes.md',
|
|
249
331
|
'claude/skills/target-setup/references/verify.md',
|
|
332
|
+
'claude/skills/task-board/REQUIREMENT.md',
|
|
250
333
|
'claude/skills/task-board/SKILL.md',
|
|
251
334
|
'claude/skills/task-board/references/board.md',
|
|
252
335
|
'claude/skills/task-board/references/create-report.md',
|
|
253
336
|
'claude/skills/task-board/references/decline.md',
|
|
254
337
|
'claude/skills/task-board/references/tasks-decline.md',
|
|
255
338
|
'claude/skills/task-board/references/tasks.md',
|
|
339
|
+
'claude/skills/task-complete/REQUIREMENT.md',
|
|
256
340
|
'claude/skills/task-complete/SKILL.md',
|
|
257
341
|
'claude/skills/task-complete/references/archive.md',
|
|
258
342
|
'claude/skills/task-complete/references/receipt-sweep.md',
|
|
259
343
|
'claude/skills/task-complete/references/unusable-baseline.md',
|
|
344
|
+
'claude/skills/teach-workspace/REQUIREMENT.md',
|
|
260
345
|
'claude/skills/teach-workspace/SKILL.md',
|
|
261
346
|
'claude/skills/teach-workspace/references/glossary.md',
|
|
262
347
|
'claude/skills/teach-workspace/references/lesson-build.md',
|
|
@@ -266,23 +351,43 @@ export const GATED_PATHS: readonly string[] = [
|
|
|
266
351
|
'claude/skills/teach-workspace/references/promotion.md',
|
|
267
352
|
'claude/skills/teach-workspace/references/revisit.md',
|
|
268
353
|
'claude/skills/teach-workspace/references/teach.md',
|
|
354
|
+
'claude/skills/test-craft/REQUIREMENT.md',
|
|
269
355
|
'claude/skills/test-craft/SKILL.md',
|
|
270
356
|
'claude/skills/test-craft/references/adopted.md',
|
|
271
357
|
'claude/skills/test-craft/references/component.md',
|
|
272
358
|
'claude/skills/test-craft/references/e2e.md',
|
|
273
359
|
'claude/skills/test-craft/references/unit.md',
|
|
360
|
+
'claude/skills/test-first/REQUIREMENT.md',
|
|
274
361
|
'claude/skills/test-first/SKILL.md',
|
|
275
362
|
'claude/skills/test-first/references/adopted.md',
|
|
363
|
+
'claude/skills/ui-checklist/REQUIREMENT.md',
|
|
276
364
|
'claude/skills/ui-checklist/SKILL.md',
|
|
365
|
+
'claude/skills/ux-audit/REQUIREMENT.md',
|
|
277
366
|
'claude/skills/ux-audit/SKILL.md',
|
|
367
|
+
'claude/skills/ux-measure/REQUIREMENT.md',
|
|
278
368
|
'claude/skills/ux-measure/SKILL.md',
|
|
369
|
+
'claude/skills/video-craft/REQUIREMENT.md',
|
|
279
370
|
'claude/skills/video-craft/SKILL.md',
|
|
280
371
|
'claude/skills/video-craft/references/adopted.md',
|
|
372
|
+
'claude/skills/write-human/REQUIREMENT.md',
|
|
281
373
|
'claude/skills/write-human/SKILL.md',
|
|
282
374
|
'claude/skills/write-human/references/adopted.md',
|
|
283
375
|
'claude/skills/write-human/references/density.md',
|
|
284
376
|
'claude/skills/write-human/references/machine-tells.md',
|
|
377
|
+
'claude/skills/youtube-transcripts/REQUIREMENT.md',
|
|
285
378
|
'claude/skills/youtube-transcripts/SKILL.md',
|
|
379
|
+
'.claude/skills/internal-canvas/REQUIREMENT.md',
|
|
380
|
+
'.claude/skills/internal-claude/REQUIREMENT.md',
|
|
381
|
+
'.claude/skills/internal-docs/REQUIREMENT.md',
|
|
382
|
+
'.claude/skills/internal-governance/REQUIREMENT.md',
|
|
383
|
+
'.claude/skills/internal-rule-audit/REQUIREMENT.md',
|
|
384
|
+
'.claude/skills/internal-sandbox-check/REQUIREMENT.md',
|
|
385
|
+
'.claude/skills/internal-scripts/REQUIREMENT.md',
|
|
386
|
+
'.claude/skills/internal-shipped-reference/REQUIREMENT.md',
|
|
387
|
+
'.claude/skills/internal-teach/REQUIREMENT.md',
|
|
388
|
+
'.claude/skills/internal-tooling/REQUIREMENT.md',
|
|
389
|
+
'.claude/skills/internal-upstream-digest/REQUIREMENT.md',
|
|
390
|
+
'.claude/skills/internal-web/REQUIREMENT.md',
|
|
286
391
|
'.claude/skills/internal-',
|
|
287
392
|
'docs/',
|
|
288
393
|
'tooling/',
|
|
@@ -307,15 +412,23 @@ export function inDensityScope(rel: string): boolean {
|
|
|
307
412
|
return DENSITY_SCOPE.some((pattern) => pattern.test(rel))
|
|
308
413
|
}
|
|
309
414
|
|
|
310
|
-
/**
|
|
415
|
+
/**
|
|
416
|
+
* Whether a density finding of this kind on this path fails the run.
|
|
417
|
+
*
|
|
418
|
+
* `alwaysGated` is off for a target's list, since no stage of a target's ever
|
|
419
|
+
* failed a push on a bullet run, and gating one unlisted would fail an older
|
|
420
|
+
* project's push on prose it has not touched since the limit landed.
|
|
421
|
+
*/
|
|
311
422
|
export function densityGates(
|
|
312
423
|
rel: string,
|
|
313
424
|
kind: DensityKind,
|
|
314
425
|
gated: readonly string[] = GATED_PATHS,
|
|
426
|
+
alwaysGated = true,
|
|
315
427
|
): boolean {
|
|
316
428
|
if (!inDensityScope(rel)) return false
|
|
317
429
|
if (REQUIREMENT_SCOPE.test(rel)) return gated.includes(rel)
|
|
318
430
|
if (
|
|
431
|
+
alwaysGated &&
|
|
319
432
|
ALWAYS_GATED.includes(kind) &&
|
|
320
433
|
BULLET_RUN_SCOPE.some((pattern) => pattern.test(rel))
|
|
321
434
|
)
|
package/src/migrate/rename.ts
CHANGED
|
@@ -12,6 +12,12 @@
|
|
|
12
12
|
* sweeps sharing a module constant would have to agree on a single map, and
|
|
13
13
|
* the second rename this repository needed shares nothing with the first
|
|
14
14
|
* except the scanning discipline above.
|
|
15
|
+
*
|
|
16
|
+
* Two misses survive a clean run. The sweep reads spelled text only, so a path
|
|
17
|
+
* assembled as `join('.claude', ...)` is never joined back into a token and
|
|
18
|
+
* keeps its old spelling. And rewriting a test's fixtures can collapse a
|
|
19
|
+
* fallback test into a copy of its neighbor, which keeps passing while it
|
|
20
|
+
* covers nothing, so read the rewritten fixtures rather than trusting the run.
|
|
15
21
|
*/
|
|
16
22
|
|
|
17
23
|
/**
|
package/src/pr/evidence.ts
CHANGED
|
@@ -5,7 +5,7 @@ const EVIDENCE_SEGMENT = 'evidence'
|
|
|
5
5
|
const EVIDENCE_OPEN_LIMIT = 6
|
|
6
6
|
|
|
7
7
|
/** Length of the commit names the visible line shows, the width `git` abbreviates to. */
|
|
8
|
-
const SHORT_SHA_LENGTH = 7
|
|
8
|
+
export const SHORT_SHA_LENGTH = 7
|
|
9
9
|
|
|
10
10
|
/** Prefix of the trailing marker this module writes and reads back. */
|
|
11
11
|
const MARKER_PREFIX = '<!-- pr-evidence:'
|
package/src/pr/rest.ts
CHANGED
|
@@ -42,6 +42,7 @@ export interface PullIdentity {
|
|
|
42
42
|
readonly head?: string
|
|
43
43
|
readonly base?: string
|
|
44
44
|
readonly mergeState?: string
|
|
45
|
+
readonly body?: string
|
|
45
46
|
}
|
|
46
47
|
|
|
47
48
|
export type BranchPull =
|
|
@@ -70,6 +71,7 @@ export function identityOf(row: RestPull): PullIdentity {
|
|
|
70
71
|
...(row.head?.sha !== undefined && { head: row.head.sha }),
|
|
71
72
|
...(row.base?.ref !== undefined && { base: row.base.ref }),
|
|
72
73
|
...(mergeState !== undefined && { mergeState }),
|
|
74
|
+
...(typeof row.body === 'string' && { body: row.body }),
|
|
73
75
|
}
|
|
74
76
|
}
|
|
75
77
|
|
package/src/pr/tick.ts
CHANGED
|
@@ -3,9 +3,98 @@ import {
|
|
|
3
3
|
CHECKLIST_START,
|
|
4
4
|
parseBoxLine,
|
|
5
5
|
readChecklistBoxes,
|
|
6
|
+
SHORT_SHA_LENGTH,
|
|
6
7
|
writeBoxLine,
|
|
7
8
|
} from '@/pr/evidence'
|
|
8
9
|
|
|
10
|
+
const UI_PASS_MARKER = '<!-- pr-ui-pass -->'
|
|
11
|
+
const TESTING_HEADING = /^## Testing\s*$/
|
|
12
|
+
const SECTION_HEADING = /^## /
|
|
13
|
+
const REVIEWER_HEADING = /^## For the reviewer\s*$/
|
|
14
|
+
const UI_PASS_HEAD = /at `([0-9a-f]+)`/
|
|
15
|
+
|
|
16
|
+
/** What the UI pass line reports, either a pass or a reset to a head nobody has driven. */
|
|
17
|
+
export type UiPassSummary =
|
|
18
|
+
| {
|
|
19
|
+
readonly kind: 'passed'
|
|
20
|
+
/** Driven boxes ticked, taste boxes excluded. */
|
|
21
|
+
readonly passed: number
|
|
22
|
+
/** Boxes a driver can run, taste boxes excluded. */
|
|
23
|
+
readonly total: number
|
|
24
|
+
readonly taste: number
|
|
25
|
+
readonly head: string
|
|
26
|
+
}
|
|
27
|
+
| { readonly kind: 'reset'; readonly head: string }
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* The one `## Testing` line a UI pass owns. It is ticked only when every box
|
|
31
|
+
* a driver can run passed, so a failed box never reads as done, and taste
|
|
32
|
+
* boxes are counted apart so a full pass never reads as one box failed.
|
|
33
|
+
*/
|
|
34
|
+
export function uiPassLine(summary: UiPassSummary): string {
|
|
35
|
+
const at = `\`${summary.head.slice(0, SHORT_SHA_LENGTH)}\``
|
|
36
|
+
if (summary.kind === 'reset') {
|
|
37
|
+
return `- [ ] UI pass, not run at ${at} ${UI_PASS_MARKER}`
|
|
38
|
+
}
|
|
39
|
+
const taste =
|
|
40
|
+
summary.taste === 0
|
|
41
|
+
? ''
|
|
42
|
+
: `${summary.taste} taste ${summary.taste === 1 ? 'box' : 'boxes'} left to the operator`
|
|
43
|
+
if (summary.total === 0) {
|
|
44
|
+
return `- [ ] UI pass, no box a driver can run, ${taste} at ${at} ${UI_PASS_MARKER}`
|
|
45
|
+
}
|
|
46
|
+
const box = summary.passed === summary.total ? 'x' : ' '
|
|
47
|
+
const tail = taste === '' ? '' : `, ${taste}`
|
|
48
|
+
return `- [${box}] UI pass, ${summary.passed} of ${summary.total} boxes passed at ${at}${tail} ${UI_PASS_MARKER}`
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/** The short sha the marked line names, absent when the body carries no such line. */
|
|
52
|
+
export function readUiPassHead(body: string): string | undefined {
|
|
53
|
+
const line = body.split('\n').find((row) => row.includes(UI_PASS_MARKER))
|
|
54
|
+
return line === undefined ? undefined : UI_PASS_HEAD.exec(line)?.[1]
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Puts the UI pass line in a pull request body and touches nothing else.
|
|
59
|
+
* The marked line is replaced where it stands, else appended to the end of
|
|
60
|
+
* `## Testing`, which is created ahead of `## For the reviewer` when absent.
|
|
61
|
+
*/
|
|
62
|
+
export function writeUiPassLine(body: string, summary: UiPassSummary): string {
|
|
63
|
+
const line = uiPassLine(summary)
|
|
64
|
+
const lines = body.split('\n')
|
|
65
|
+
|
|
66
|
+
const marked = lines.findIndex((row) => row.includes(UI_PASS_MARKER))
|
|
67
|
+
if (marked !== -1) {
|
|
68
|
+
lines[marked] = line
|
|
69
|
+
return lines.join('\n')
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const heading = lines.findIndex((row) => TESTING_HEADING.test(row))
|
|
73
|
+
if (heading === -1) {
|
|
74
|
+
const reviewer = lines.findIndex((row) => REVIEWER_HEADING.test(row))
|
|
75
|
+
if (reviewer === -1) {
|
|
76
|
+
return `${body.trimEnd()}\n\n## Testing\n\n${line}\n`
|
|
77
|
+
}
|
|
78
|
+
lines.splice(reviewer, 0, '## Testing', '', line, '')
|
|
79
|
+
return lines.join('\n')
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
const next = lines.findIndex(
|
|
83
|
+
(row, index) => index > heading && SECTION_HEADING.test(row),
|
|
84
|
+
)
|
|
85
|
+
let last = next === -1 ? lines.length - 1 : next - 1
|
|
86
|
+
while (last > heading && lines[last]?.trim() === '') last -= 1
|
|
87
|
+
|
|
88
|
+
if (last > heading) {
|
|
89
|
+
lines.splice(last + 1, 0, line)
|
|
90
|
+
} else if (lines[heading + 1] === '') {
|
|
91
|
+
lines.splice(heading + 2, 0, line)
|
|
92
|
+
} else {
|
|
93
|
+
lines.splice(heading + 1, 0, '', line)
|
|
94
|
+
}
|
|
95
|
+
return lines.join('\n')
|
|
96
|
+
}
|
|
97
|
+
|
|
9
98
|
export type TickRefusal = 'no-checklist' | 'no-box' | 'taste-box'
|
|
10
99
|
|
|
11
100
|
export type TickResult =
|
package/src/sessions/claim.ts
CHANGED
|
@@ -19,6 +19,12 @@ export interface ClaimReport {
|
|
|
19
19
|
readonly refs: readonly string[]
|
|
20
20
|
/** False when the session roster could not be read, so `claimed` covers the readings around it alone and cannot be trusted as a clean "unclaimed". */
|
|
21
21
|
readonly sessionsReadable: boolean
|
|
22
|
+
/**
|
|
23
|
+
* Foreign-namespace records whose socket is gone. They read dead, so a branch
|
|
24
|
+
* one of them holds can read unclaimed, which a caller with a count above
|
|
25
|
+
* zero should treat as unverified.
|
|
26
|
+
*/
|
|
27
|
+
readonly foreignDead: number
|
|
22
28
|
/** False when the ref read failed, which is the same false clean under a different reading. */
|
|
23
29
|
readonly refsReadable: boolean
|
|
24
30
|
}
|
|
@@ -80,6 +86,7 @@ export async function checkClaim(
|
|
|
80
86
|
sessions,
|
|
81
87
|
refs: refs.refs,
|
|
82
88
|
sessionsReadable: report.kind === 'resolved',
|
|
89
|
+
foreignDead: report.kind === 'resolved' ? report.foreignDead : 0,
|
|
83
90
|
refsReadable: refs.readable,
|
|
84
91
|
}
|
|
85
92
|
}
|
package/src/sessions/live.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import { readFileSync } from 'node:fs'
|
|
1
|
+
import { existsSync, readFileSync, readlinkSync } from 'node:fs'
|
|
2
|
+
import { createConnection } from 'node:net'
|
|
2
3
|
import type { SessionRecord } from '@/sessions/registry'
|
|
3
4
|
|
|
4
5
|
/**
|
|
@@ -7,13 +8,19 @@ import type { SessionRecord } from '@/sessions/registry'
|
|
|
7
8
|
* `confirmed` means the running process was matched against the start time the
|
|
8
9
|
* record stamped, so the pid belongs to the session that wrote the file.
|
|
9
10
|
* `unverified` means the pid answers a signal probe and nothing more, which
|
|
10
|
-
* cannot separate the original session from whatever inherited its pid
|
|
11
|
+
* cannot separate the original session from whatever inherited its pid, or,
|
|
12
|
+
* for a `foreign` row, that its messaging socket still exists.
|
|
11
13
|
*/
|
|
12
14
|
export type Confidence = 'confirmed' | 'unverified'
|
|
13
15
|
|
|
14
16
|
export interface Liveness {
|
|
15
17
|
readonly alive: boolean
|
|
16
18
|
readonly confidence: Confidence
|
|
19
|
+
/**
|
|
20
|
+
* Set only when the record belongs to another pid namespace, so the pid was
|
|
21
|
+
* never probed. Absent rather than false on every other path.
|
|
22
|
+
*/
|
|
23
|
+
readonly foreign?: true
|
|
17
24
|
}
|
|
18
25
|
|
|
19
26
|
export interface LivenessProbes {
|
|
@@ -21,6 +28,20 @@ export interface LivenessProbes {
|
|
|
21
28
|
readonly procStartOf: (pid: number) => string | null
|
|
22
29
|
/** Whether the pid answers at all, ignoring which process holds it. */
|
|
23
30
|
readonly responds: (pid: number) => boolean
|
|
31
|
+
/**
|
|
32
|
+
* The reader's own pid namespace in `pid:[inode]` form, or null where it
|
|
33
|
+
* cannot be read. Optional so a caller with no use for the foreign branch,
|
|
34
|
+
* such as the gate's lock, states nothing and takes the pid path.
|
|
35
|
+
*/
|
|
36
|
+
readonly ownDomain?: () => string | null
|
|
37
|
+
/** Whether the messaging socket a foreign record names still exists. */
|
|
38
|
+
readonly socketExists?: (path: string) => boolean
|
|
39
|
+
/**
|
|
40
|
+
* Whether something accepts a connection on the socket within the timeout.
|
|
41
|
+
* Async, so `liveness()` never calls it and a caller with no use for it, such
|
|
42
|
+
* as the gate's lock, states nothing.
|
|
43
|
+
*/
|
|
44
|
+
readonly listens?: (path: string, timeoutMs: number) => Promise<boolean>
|
|
24
45
|
}
|
|
25
46
|
|
|
26
47
|
/**
|
|
@@ -51,7 +72,40 @@ function responds(pid: number): boolean {
|
|
|
51
72
|
}
|
|
52
73
|
}
|
|
53
74
|
|
|
54
|
-
|
|
75
|
+
/** The link target reads `pid:[inode]`, which a record's `pidDomain` ends with. */
|
|
76
|
+
function ownDomain(): string | null {
|
|
77
|
+
try {
|
|
78
|
+
return readlinkSync('/proc/self/ns/pid')
|
|
79
|
+
} catch {
|
|
80
|
+
return null
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Connects and closes without writing, so the peer sees no bytes. The socket is
|
|
86
|
+
* destroyed on connect rather than waited on, since a listener that accepts and
|
|
87
|
+
* never closes would otherwise hold the probe open past its timeout.
|
|
88
|
+
*/
|
|
89
|
+
function listens(path: string, timeoutMs: number): Promise<boolean> {
|
|
90
|
+
return new Promise((resolve) => {
|
|
91
|
+
const socket = createConnection(path)
|
|
92
|
+
const settle = (accepted: boolean): void => {
|
|
93
|
+
socket.destroy()
|
|
94
|
+
resolve(accepted)
|
|
95
|
+
}
|
|
96
|
+
socket.setTimeout(timeoutMs, () => settle(false))
|
|
97
|
+
socket.once('connect', () => settle(true))
|
|
98
|
+
socket.once('error', () => settle(false))
|
|
99
|
+
})
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
export const SYSTEM_PROBES: LivenessProbes = {
|
|
103
|
+
procStartOf,
|
|
104
|
+
responds,
|
|
105
|
+
ownDomain,
|
|
106
|
+
socketExists: existsSync,
|
|
107
|
+
listens,
|
|
108
|
+
}
|
|
55
109
|
|
|
56
110
|
/**
|
|
57
111
|
* Decides whether a record describes a session that is still running.
|
|
@@ -62,13 +116,32 @@ export const SYSTEM_PROBES: LivenessProbes = { procStartOf, responds }
|
|
|
62
116
|
* probe alone stands, and every surviving row is marked so a caller never reads
|
|
63
117
|
* a recycled pid as a confirmed identity.
|
|
64
118
|
*
|
|
65
|
-
*
|
|
66
|
-
*
|
|
119
|
+
* A record from another pid namespace is judged first, on its messaging
|
|
120
|
+
* socket, because its pid names nothing in this namespace and either probe
|
|
121
|
+
* would read an unrelated process or none. That answer is always `unverified`,
|
|
122
|
+
* since a crashed session leaves its socket behind.
|
|
123
|
+
*
|
|
124
|
+
* The pid and start time are the only fields the rest needs, so a record that
|
|
125
|
+
* is not a session, such as the gate's machine lock, carries the same proof and
|
|
126
|
+
* takes the pid path for want of a `pidDomain`.
|
|
67
127
|
*/
|
|
68
128
|
export function liveness(
|
|
69
|
-
record: Pick<SessionRecord, 'pid' | 'procStart'
|
|
129
|
+
record: Pick<SessionRecord, 'pid' | 'procStart'> &
|
|
130
|
+
Partial<Pick<SessionRecord, 'pidDomain' | 'messagingSocketPath'>>,
|
|
70
131
|
probes: LivenessProbes = SYSTEM_PROBES,
|
|
71
132
|
): Liveness {
|
|
133
|
+
const own = probes.ownDomain?.() ?? null
|
|
134
|
+
if (
|
|
135
|
+
own !== null &&
|
|
136
|
+
record.pidDomain !== undefined &&
|
|
137
|
+
!record.pidDomain.endsWith(own)
|
|
138
|
+
) {
|
|
139
|
+
const socket = record.messagingSocketPath
|
|
140
|
+
const alive =
|
|
141
|
+
socket !== undefined && (probes.socketExists?.(socket) ?? false)
|
|
142
|
+
return { alive, confidence: 'unverified', foreign: true }
|
|
143
|
+
}
|
|
144
|
+
|
|
72
145
|
const started = record.procStart
|
|
73
146
|
|
|
74
147
|
if (started !== undefined && started.length > 0) {
|
package/src/sessions/registry.ts
CHANGED
|
@@ -45,6 +45,17 @@ export interface SessionRecord {
|
|
|
45
45
|
* been handed to something else.
|
|
46
46
|
*/
|
|
47
47
|
readonly procStart: string | undefined
|
|
48
|
+
/**
|
|
49
|
+
* The pid namespace the client stamped, ending in the `pid:[inode]` form that
|
|
50
|
+
* `/proc/self/ns/pid` reads as. A reader in another namespace cannot see the
|
|
51
|
+
* record's pid, so this is what tells a foreign row from a dead one.
|
|
52
|
+
*/
|
|
53
|
+
readonly pidDomain: string | undefined
|
|
54
|
+
/**
|
|
55
|
+
* The socket the session listens on for messages. It is the only evidence of
|
|
56
|
+
* life a foreign-namespace reader can reach, since the pid is out of sight.
|
|
57
|
+
*/
|
|
58
|
+
readonly messagingSocketPath: string | undefined
|
|
48
59
|
}
|
|
49
60
|
|
|
50
61
|
/**
|