@mccune1224/pi-pstack 0.1.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/LICENSE +21 -0
- package/README.md +142 -0
- package/agents/comment-sicko.md +37 -0
- package/agents/poteto-agent.md +16 -0
- package/docs/guide/01-setup.md +49 -0
- package/docs/guide/02-poteto-mode.md +98 -0
- package/docs/guide/03-understand.md +61 -0
- package/docs/guide/04-design.md +81 -0
- package/docs/guide/05-build-and-clean.md +75 -0
- package/docs/guide/06-verify-and-ship.md +87 -0
- package/docs/guide/07-overnight.md +81 -0
- package/docs/guide/08-principles.md +69 -0
- package/docs/guide/09-make-it-yours.md +67 -0
- package/docs/guide/10-recipes-and-pitfalls.md +94 -0
- package/docs/guide/README.md +30 -0
- package/docs/guide/images/design.jpg +0 -0
- package/docs/guide/images/overnight.jpg +0 -0
- package/docs/guide/images/recipes.jpg +0 -0
- package/docs/guide/images/router.jpg +0 -0
- package/docs/guide/images/understanding.jpg +0 -0
- package/docs/guide/images/verification.jpg +0 -0
- package/extensions/index.ts +358 -0
- package/extensions/settings.test.ts +110 -0
- package/extensions/settings.ts +240 -0
- package/package.json +77 -0
- package/skills/architect/SKILL.md +83 -0
- package/skills/architect/references/design-red-flags.md +33 -0
- package/skills/architect/references/rationale-template.md +35 -0
- package/skills/architect/references/runner-prompt.md +20 -0
- package/skills/arena/SKILL.md +71 -0
- package/skills/automate-me/SKILL.md +109 -0
- package/skills/blast-radius/SKILL.md +50 -0
- package/skills/bro/SKILL.md +7 -0
- package/skills/control-cli/SKILL.md +109 -0
- package/skills/control-ui/SKILL.md +109 -0
- package/skills/create-skill/SKILL.md +69 -0
- package/skills/create-verification-skill/SKILL.md +44 -0
- package/skills/create-verification-skill/references/feature-map-example/README.md +47 -0
- package/skills/create-verification-skill/references/feature-map-example/create-note.md +39 -0
- package/skills/create-verification-skill/references/feature-map-example/search.md +45 -0
- package/skills/deslop/SKILL.md +22 -0
- package/skills/figure-it-out/SKILL.md +55 -0
- package/skills/how/SKILL.md +130 -0
- package/skills/how/references/critic-prompt.md +59 -0
- package/skills/how/references/critique-rubric.md +58 -0
- package/skills/how/references/explainer-prompt.md +55 -0
- package/skills/how/references/explorer-prompt.md +52 -0
- package/skills/interrogate/SKILL.md +112 -0
- package/skills/interrogate/references/code-quality-review.md +47 -0
- package/skills/interrogate/references/lead-judgment.md +58 -0
- package/skills/interrogate/references/reviewer-prompt.md +72 -0
- package/skills/interrogate/references/rubric.md +77 -0
- package/skills/maintain-verification-skill/SKILL.md +39 -0
- package/skills/no-comments/SKILL.md +24 -0
- package/skills/poteto-mode/SKILL.md +142 -0
- package/skills/poteto-mode/playbooks/authoring-a-skill.md +12 -0
- package/skills/poteto-mode/playbooks/autonomous-run.md +13 -0
- package/skills/poteto-mode/playbooks/autopilot-full.md +13 -0
- package/skills/poteto-mode/playbooks/autopilot-stack.md +16 -0
- package/skills/poteto-mode/playbooks/babysit.md +27 -0
- package/skills/poteto-mode/playbooks/bug-fix.md +17 -0
- package/skills/poteto-mode/playbooks/eval.md +27 -0
- package/skills/poteto-mode/playbooks/feature.md +21 -0
- package/skills/poteto-mode/playbooks/hillclimb.md +21 -0
- package/skills/poteto-mode/playbooks/investigation.md +14 -0
- package/skills/poteto-mode/playbooks/multi-phase-plan.md +155 -0
- package/skills/poteto-mode/playbooks/opening-a-pr.md +29 -0
- package/skills/poteto-mode/playbooks/orchestrate.md +113 -0
- package/skills/poteto-mode/playbooks/pause-safely.md +10 -0
- package/skills/poteto-mode/playbooks/perf-issue.md +24 -0
- package/skills/poteto-mode/playbooks/prototype.md +14 -0
- package/skills/poteto-mode/playbooks/refactoring.md +16 -0
- package/skills/poteto-mode/playbooks/runtime-forensics.md +11 -0
- package/skills/poteto-mode/playbooks/session-pickup.md +13 -0
- package/skills/poteto-mode/playbooks/shipping.md +20 -0
- package/skills/poteto-mode/playbooks/trace-forensics.md +14 -0
- package/skills/poteto-mode/playbooks/visual-parity.md +11 -0
- package/skills/poteto-mode/playbooks/worktree-cleanup.md +14 -0
- package/skills/poteto-mode/references/bugbot-triage.md +142 -0
- package/skills/poteto-mode/scripts/bootstrap.ts +62 -0
- package/skills/poteto-mode/scripts/bun.lock +67 -0
- package/skills/poteto-mode/scripts/check-plan.mjs +186 -0
- package/skills/poteto-mode/scripts/orch/orch.test.ts +634 -0
- package/skills/poteto-mode/scripts/orch/orch.ts +578 -0
- package/skills/poteto-mode/scripts/orch/store.ts +1607 -0
- package/skills/poteto-mode/scripts/package.json +16 -0
- package/skills/poteto-mode/scripts/watch-pr/cli.test.ts +224 -0
- package/skills/poteto-mode/scripts/watch-pr/cli.ts +223 -0
- package/skills/poteto-mode/scripts/watch-pr/fakes.test-helper.ts +118 -0
- package/skills/poteto-mode/scripts/watch-pr/github.test.ts +306 -0
- package/skills/poteto-mode/scripts/watch-pr/github.ts +699 -0
- package/skills/poteto-mode/scripts/watch-pr/policy.test.ts +420 -0
- package/skills/poteto-mode/scripts/watch-pr/policy.ts +832 -0
- package/skills/poteto-mode/scripts/watch-pr/render.ts +169 -0
- package/skills/poteto-mode/scripts/watch-pr/tsconfig.json +13 -0
- package/skills/poteto-mode/scripts/watch-pr/types.compile.ts +93 -0
- package/skills/poteto-mode/scripts/watch-pr/types.ts +401 -0
- package/skills/poteto-mode/scripts/watch-pr/watch-pr +6 -0
- package/skills/poteto-mode/scripts/worktree-audit.mjs +178 -0
- package/skills/principle-boundary-discipline/SKILL.md +34 -0
- package/skills/principle-build-the-lever/SKILL.md +23 -0
- package/skills/principle-encode-lessons-in-structure/SKILL.md +31 -0
- package/skills/principle-exhaust-the-design-space/SKILL.md +21 -0
- package/skills/principle-experience-first/SKILL.md +19 -0
- package/skills/principle-fix-root-causes/SKILL.md +23 -0
- package/skills/principle-foundational-thinking/SKILL.md +21 -0
- package/skills/principle-guard-the-context-window/SKILL.md +17 -0
- package/skills/principle-laziness-protocol/SKILL.md +18 -0
- package/skills/principle-make-operations-idempotent/SKILL.md +24 -0
- package/skills/principle-migrate-callers-then-delete-legacy-apis/SKILL.md +22 -0
- package/skills/principle-minimize-reader-load/SKILL.md +23 -0
- package/skills/principle-model-the-domain/SKILL.md +26 -0
- package/skills/principle-never-block-on-the-human/SKILL.md +23 -0
- package/skills/principle-outcome-oriented-execution/SKILL.md +22 -0
- package/skills/principle-prove-it-works/SKILL.md +33 -0
- package/skills/principle-redesign-from-first-principles/SKILL.md +16 -0
- package/skills/principle-separate-before-serializing-shared-state/SKILL.md +16 -0
- package/skills/principle-sequence-verifiable-units/SKILL.md +22 -0
- package/skills/principle-subtract-before-you-add/SKILL.md +22 -0
- package/skills/principle-type-system-discipline/SKILL.md +31 -0
- package/skills/recall/SKILL.md +35 -0
- package/skills/reflect/SKILL.md +77 -0
- package/skills/reflect/references/divergent-reviewer.md +43 -0
- package/skills/reflect/references/judgment-reviewer.md +42 -0
- package/skills/reflect/references/synthesizer.md +56 -0
- package/skills/reflect/references/tooling-reviewer.md +57 -0
- package/skills/setup-pstack/SKILL.md +92 -0
- package/skills/show-me-your-work/SKILL.md +82 -0
- package/skills/show-me-your-work/references/decision-log-template.tsv +1 -0
- package/skills/show-me-your-work/scripts/log.mjs +30 -0
- package/skills/swarm/SKILL.md +46 -0
- package/skills/tdd/SKILL.md +44 -0
- package/skills/teach/SKILL.md +21 -0
- package/skills/technical-writing/SKILL.md +130 -0
- package/skills/typescript-best-practices/SKILL.md +28 -0
- package/skills/typescript-best-practices/references/patterns.md +292 -0
- package/skills/unslop/SKILL.md +80 -0
- package/skills/why/SKILL.md +229 -0
- package/skills/why/references/epistemics.md +144 -0
- package/skills/why/references/investigator-prompt.md +103 -0
- package/skills/why/references/source-playbook.md +17 -0
- package/skills/why/references/sources/code-archaeology.md +88 -0
- package/skills/why/references/sources/databricks.md +70 -0
- package/skills/why/references/sources/datadog.md +99 -0
- package/skills/why/references/sources/incident-postmortem.md +15 -0
- package/skills/why/references/sources/linear.md +48 -0
- package/skills/why/references/sources/notion.md +55 -0
- package/skills/why/references/sources/sentry.md +100 -0
- package/skills/why/references/sources/slack.md +54 -0
- package/skills/why/references/synthesizer-prompt.md +135 -0
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
# Bugbot triage
|
|
2
|
+
|
|
3
|
+
Use this reference when the Babysit playbook (`../playbooks/babysit.md`) handles Bugbot or review-automation comments. The goal is not to ignore Bugbot by default. The goal is to stop treating every comment as a required code change.
|
|
4
|
+
|
|
5
|
+
## Decision rubric
|
|
6
|
+
|
|
7
|
+
Classify each Bugbot thread before acting:
|
|
8
|
+
|
|
9
|
+
- `fix`: The comment identifies a plausible correctness, security, privacy, data loss, auth, billing, migration, idempotency, race, or shipped-behavior issue. Fix it in the lowest owning PR, then reply with the commit SHA and resolve the thread.
|
|
10
|
+
- `dismiss`: The comment matches a documented low-risk noisy pattern, and the current code/context proves the concern does not need a code change. Reply with a short reason and resolve the thread.
|
|
11
|
+
- `ask`: The comment is novel, high-severity, security/privacy/data-related, or ambiguous. Ask the user instead of guessing.
|
|
12
|
+
|
|
13
|
+
When in doubt, ask. Skipping a noisy code-quality comment is cheap; skipping a real data or security bug is not.
|
|
14
|
+
|
|
15
|
+
## Learned pattern format
|
|
16
|
+
|
|
17
|
+
Add future patterns in this shape:
|
|
18
|
+
|
|
19
|
+
```markdown
|
|
20
|
+
### <short pattern name>
|
|
21
|
+
|
|
22
|
+
- Confidence: candidate | recurring | strong
|
|
23
|
+
- Skip when: <conditions that must be true>
|
|
24
|
+
- Do not skip when: <risk boundaries>
|
|
25
|
+
- Example signal: <phrases or code context that identify the pattern>
|
|
26
|
+
- Source: <PR/comment URL or short historical note>
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
Use `candidate` for one or two examples. Use `recurring` after multiple real dismissals. Use `strong` only when the pattern is narrow, repeatedly verified, and low-risk.
|
|
30
|
+
|
|
31
|
+
## Recurring skip candidates
|
|
32
|
+
|
|
33
|
+
### Intentional UI or design-system visual changes
|
|
34
|
+
|
|
35
|
+
- Confidence: candidate
|
|
36
|
+
- Skip when: The PR description, screenshots, design review, or nearby code makes the visual change explicit, and the Bugbot comment is only restating that a shared visual default changed.
|
|
37
|
+
- Do not skip when: The comment points to accessibility, focus visibility, keyboard navigation, color contrast, or a component API contract that the PR did not intentionally change.
|
|
38
|
+
- Example signal: Comments about focus outlines, button sizes, spacing, or shared component visual defaults where the owner replies "intentional" or "intended".
|
|
39
|
+
|
|
40
|
+
### Upstack or stack-local usage Bugbot cannot see
|
|
41
|
+
|
|
42
|
+
- Confidence: candidate
|
|
43
|
+
- Skip when: Bugbot flags an export, component, helper, or file as unused, and `gt ls -s`, upper-stack diffs, or PR context shows it is used by a later PR in the stack.
|
|
44
|
+
- Do not skip when: The current PR is not part of a stack, the symbol is public API, or the supposed upstack use cannot be verified.
|
|
45
|
+
- Example signal: "Exported component is never used" with a human reply like "used upstack".
|
|
46
|
+
|
|
47
|
+
### Temporary duplication during parallel implementation
|
|
48
|
+
|
|
49
|
+
- Confidence: candidate
|
|
50
|
+
- Skip when: The PR intentionally duplicates a small amount of code to keep a new path parallel to an old path that is being deleted, replaced, or proven out.
|
|
51
|
+
- Do not skip when: The duplicated code changes security, billing, data access, API behavior, or a long-lived shared abstraction would clearly reduce risk.
|
|
52
|
+
- Example signal: "Significant duplication" or "duplicated validation logic" where the owner explains the old path will be deleted or the duplicate logic is intentionally local.
|
|
53
|
+
|
|
54
|
+
### Existing framework or component invariant covers the warning
|
|
55
|
+
|
|
56
|
+
- Confidence: candidate
|
|
57
|
+
- Skip when: The concern is already guaranteed by a shared component, framework contract, type invariant, or single source of truth visible in the current diff or nearby code.
|
|
58
|
+
- Do not skip when: The invariant is assumed but not enforced, depends on timing, or crosses async/state boundaries where values can diverge.
|
|
59
|
+
- Example signal: Comments about missing max-height on an inner popover when the shared popover enforces viewport bounds, or nullable values where the local checked value and passed value share the same source.
|
|
60
|
+
|
|
61
|
+
### Owner-declared follow-up or deferred cleanup
|
|
62
|
+
|
|
63
|
+
- Confidence: candidate
|
|
64
|
+
- Skip when: The PR owner explicitly says the issue is a known follow-up, the behavior is not made worse by the current PR, and the comment is not about a high-risk area.
|
|
65
|
+
- Do not skip when: The agent is acting without owner input, the issue is medium/high severity product behavior, or deferring would merge a new regression.
|
|
66
|
+
- Example signal: "I'll worry about that later" or "we'll delete this eventually".
|
|
67
|
+
|
|
68
|
+
### Self-withdrawn or explicit false-positive rule comments
|
|
69
|
+
|
|
70
|
+
- Confidence: recurring
|
|
71
|
+
- Skip when: The comment body or a later Bugbot reply explicitly says the finding is withdrawn, compliant, or a false positive, and the agent can verify the relevant rule locally.
|
|
72
|
+
- Do not skip when: The only evidence is a human saying "false positive" on a high-risk issue without explanation.
|
|
73
|
+
- Example signal: A file-naming rule comment whose body says the file is already compliant.
|
|
74
|
+
|
|
75
|
+
## Ask by default
|
|
76
|
+
|
|
77
|
+
Do not auto-skip these categories, even if a previous PR dismissed something similar:
|
|
78
|
+
|
|
79
|
+
- Security, privacy, auth, billing, data retention, training-data, and permission-boundary findings.
|
|
80
|
+
- High-severity findings.
|
|
81
|
+
- Migration, schema, idempotency, concurrency, and cross-system behavior findings.
|
|
82
|
+
- Comments where the suggested fix is small and clearly reduces risk without changing product intent.
|
|
83
|
+
|
|
84
|
+
Historical data showed humans sometimes dismiss security/data-flow comments. Treat those as owner judgment calls, not team-wide skip rules.
|
|
85
|
+
|
|
86
|
+
## Candidate learnings from recent babysits
|
|
87
|
+
|
|
88
|
+
Append new candidate learnings here during or after babysitting when they look team-useful but not yet mature. Prefer promoting recurring candidates into the section above once several PRs confirm the pattern.
|
|
89
|
+
|
|
90
|
+
### Manual reimplementations of native browser behavior
|
|
91
|
+
|
|
92
|
+
- Confidence: candidate
|
|
93
|
+
- Skip when: Practically never. When a diff replaces native browser behavior with a manual equivalent (native sticky → JS-positioned clones, native scroll targeting → forwarded wheel/touch events, paint-order occlusion → masks/clip-path), Bugbot's logic-bug findings against that code have been consistently legitimate.
|
|
94
|
+
- Do not skip when: The finding concerns event-forwarding gaps (wheel deltaMode, touch pans, scroll-chaining at edges, tap slop), mask/clip hit-testing divergence, or observer-vs-React state timing races in such code. Default to fix.
|
|
95
|
+
- Example signal: "masks do not affect hit-testing", "overlay blocks wheel scroll", "ignores deltaMode", "runs in the IntersectionObserver callback before React applies state".
|
|
96
|
+
- Source: one sticky-occlusion PR: six Bugbot passes, roughly eighteen findings, every one fixed rather than dismissed.
|
|
97
|
+
|
|
98
|
+
### Contract-test drift claims are cheaply verifiable — run the test first
|
|
99
|
+
|
|
100
|
+
- Confidence: candidate
|
|
101
|
+
- Skip when: Never skip the verification itself; it costs one command. When a PR
|
|
102
|
+
ships a contract test that pins protocol or documentation prose (regexes over
|
|
103
|
+
a SKILL.md, snapshot of doc wording), and Bugbot claims "the test no longer
|
|
104
|
+
matches the doc" (or vice versa), run that test on the PR tip before
|
|
105
|
+
classifying. A red run confirms the claim empirically; a green run is a
|
|
106
|
+
concrete disproof for the dismissal reply.
|
|
107
|
+
- Do not skip when: n/a — this is a verification shortcut, not a dismissal
|
|
108
|
+
pattern. Note that repeat-pass lean-dismiss heuristics would misfire here:
|
|
109
|
+
prose-pinning tests drift precisely BECAUSE earlier fix rounds edit the prose.
|
|
110
|
+
- Example signal: "Contract test omits the pre-fix wait" on a PR whose earlier
|
|
111
|
+
fix commits reworded the pinned passage; the test run on the tip failed on
|
|
112
|
+
exactly the cited assertion.
|
|
113
|
+
- Source: one prose-pinning PR with eight Bugbot passes; the claim was real on
|
|
114
|
+
pass 7 despite every earlier pass being fixed-and-resolved.
|
|
115
|
+
|
|
116
|
+
### Stale security-review finding already fixed later in the same PR
|
|
117
|
+
|
|
118
|
+
- Confidence: candidate
|
|
119
|
+
- Skip when: An agentic security review (or similar) claims a missing authz/validation call, and the current PR tip clearly includes that exact gate (with tests), typically added in a later hardening commit after the review ran.
|
|
120
|
+
- Do not skip when: The cited helper is a no-op for the principal under discussion, the check runs after the side effect it guards, or coverage for the claimed principal is missing.
|
|
121
|
+
- Example signal: A HIGH "missing authorization check" finding while the exact guard is already called before the side effect on the tip.
|
|
122
|
+
- Source: one webhook-endpoint PR whose hardening commit postdated the review run.
|
|
123
|
+
|
|
124
|
+
### Widening a deliberately narrow error condition would mask the real error
|
|
125
|
+
|
|
126
|
+
- Confidence: candidate
|
|
127
|
+
- Skip when: The finding asks to broaden a narrow error condition (a specific
|
|
128
|
+
`errno`, error code, or status class) into a catch-all, and that narrowness
|
|
129
|
+
encodes a real distinction. The canonical shape is a dependency fallback
|
|
130
|
+
gated on `ENOENT`: "binary is not installed" is a different situation from
|
|
131
|
+
"the command ran and failed". Retrying on any non-zero exit would re-run a
|
|
132
|
+
legitimate failure (not found, expired auth, network) against the fallback
|
|
133
|
+
and then report the fallback's error, hiding the true one.
|
|
134
|
+
- Do not skip when: The narrow condition misses a case in the SAME category
|
|
135
|
+
(another "binary unusable" errno such as `EACCES`, another transport-level
|
|
136
|
+
failure), the unhandled path loses data or leaves partial state, or the retry
|
|
137
|
+
is idempotent AND the original error is still surfaced.
|
|
138
|
+
- Example signal: "only retries when X fails with ENOENT … never tries the
|
|
139
|
+
fallback even when a working Y exists", pointing at code whose fallback
|
|
140
|
+
exists for a missing dependency rather than a failed operation.
|
|
141
|
+
- Source: one CLI-rename PR whose fallback existed for a missing binary rather
|
|
142
|
+
than a failed command.
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { existsSync, readFileSync, writeFileSync } from "node:fs";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
|
|
5
|
+
const scriptsDirectory = import.meta.dir;
|
|
6
|
+
const nodeModulesDirectory = join(scriptsDirectory, "node_modules");
|
|
7
|
+
const commanderPackagePath = join(
|
|
8
|
+
nodeModulesDirectory,
|
|
9
|
+
"commander",
|
|
10
|
+
"package.json"
|
|
11
|
+
);
|
|
12
|
+
const installKeyPath = join(
|
|
13
|
+
nodeModulesDirectory,
|
|
14
|
+
".poteto-mode-tools-install-key"
|
|
15
|
+
);
|
|
16
|
+
|
|
17
|
+
function currentInstallKey(): string {
|
|
18
|
+
return createHash("sha256")
|
|
19
|
+
.update(readFileSync(join(scriptsDirectory, "package.json")))
|
|
20
|
+
.update("\0")
|
|
21
|
+
.update(readFileSync(join(scriptsDirectory, "bun.lock")))
|
|
22
|
+
.digest("hex");
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export function ensureDependenciesInstalled(): void {
|
|
26
|
+
const installKey = currentInstallKey();
|
|
27
|
+
if (
|
|
28
|
+
existsSync(commanderPackagePath) &&
|
|
29
|
+
existsSync(installKeyPath) &&
|
|
30
|
+
readFileSync(installKeyPath, "utf8").trim() === installKey
|
|
31
|
+
) {
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const result = Bun.spawnSync(
|
|
36
|
+
[process.execPath, "install", "--frozen-lockfile"],
|
|
37
|
+
{ cwd: scriptsDirectory }
|
|
38
|
+
);
|
|
39
|
+
if (result.exitCode !== 0) {
|
|
40
|
+
process.stdout.write(result.stdout);
|
|
41
|
+
process.stderr.write(result.stderr);
|
|
42
|
+
throw new Error(
|
|
43
|
+
`bun install --frozen-lockfile exited with status ${result.exitCode}`
|
|
44
|
+
);
|
|
45
|
+
}
|
|
46
|
+
if (!existsSync(commanderPackagePath)) {
|
|
47
|
+
throw new Error(
|
|
48
|
+
"bun install --frozen-lockfile completed without installing commander"
|
|
49
|
+
);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
writeFileSync(installKeyPath, `${installKey}\n`);
|
|
53
|
+
|
|
54
|
+
const restarted = Bun.spawnSync([process.execPath, ...process.argv.slice(1)], {
|
|
55
|
+
cwd: process.cwd(),
|
|
56
|
+
env: process.env,
|
|
57
|
+
stdin: "inherit",
|
|
58
|
+
stdout: "inherit",
|
|
59
|
+
stderr: "inherit",
|
|
60
|
+
});
|
|
61
|
+
process.exit(restarted.exitCode ?? 1);
|
|
62
|
+
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
{
|
|
2
|
+
"lockfileVersion": 1,
|
|
3
|
+
"configVersion": 1,
|
|
4
|
+
"workspaces": {
|
|
5
|
+
"": {
|
|
6
|
+
"name": "@cursor-skill/poteto-mode-tools",
|
|
7
|
+
"dependencies": {
|
|
8
|
+
"commander": "14.0.0",
|
|
9
|
+
},
|
|
10
|
+
"devDependencies": {
|
|
11
|
+
"bun-types": "latest",
|
|
12
|
+
"typescript": "latest",
|
|
13
|
+
},
|
|
14
|
+
},
|
|
15
|
+
},
|
|
16
|
+
"packages": {
|
|
17
|
+
"@types/node": ["@types/node@26.1.2", "", { "dependencies": { "undici-types": "~8.3.0" } }, "sha512-Vu4a5UFA9rIIFJ7rB/Vaafh9lrCQszopTCx6KjFboXTGQbPNasehVR5TEiithSDGyd1DEiUByggTZsg8jukeIg=="],
|
|
18
|
+
|
|
19
|
+
"@typescript/typescript-aix-ppc64": ["@typescript/typescript-aix-ppc64@7.0.2", "", { "os": "aix", "cpu": "ppc64" }, "sha512-MTKKkWB7p/0E9xi1d1tHtZ5PiLkGEMIq88pK2CubZjOsLtYTLqhgIgi6zepFa+9GHZ6h05NMCkQxGKiPXMxXtQ=="],
|
|
20
|
+
|
|
21
|
+
"@typescript/typescript-darwin-arm64": ["@typescript/typescript-darwin-arm64@7.0.2", "", { "os": "darwin", "cpu": "arm64" }, "sha512-gowzar9MwS/aRWp6f3a4KUqzRjAZjOsmGNCM6LcTgXum+dBfgsBVMN+AgvOCCbguXyick6LJhpBszxMebJ8syA=="],
|
|
22
|
+
|
|
23
|
+
"@typescript/typescript-darwin-x64": ["@typescript/typescript-darwin-x64@7.0.2", "", { "os": "darwin", "cpu": "x64" }, "sha512-SZ9xZInqApNlNGc9s0W1VSsktYSOe9cFqNOIqmN1Gs8SmkjKZYFt017G4VwPxASInODuAdbTW7sXiFUf893RgA=="],
|
|
24
|
+
|
|
25
|
+
"@typescript/typescript-freebsd-arm64": ["@typescript/typescript-freebsd-arm64@7.0.2", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-W5NH4y/J0plIIS5b2xvTEkU7JFxyqdMAOgf+Ilhl0vHQXKO5dZoxd+C/jEtq56c4F3wk71RB4BMRQ2XdI+bwYQ=="],
|
|
26
|
+
|
|
27
|
+
"@typescript/typescript-freebsd-x64": ["@typescript/typescript-freebsd-x64@7.0.2", "", { "os": "freebsd", "cpu": "x64" }, "sha512-UMGDx5sTpzNw3WiPebH7l90IWfJggEd+egHt/q6p7/Cm3zqoV7VxkGXt+3DxPIw8CcmvAB0j3sVVfbhX+M4Tpw=="],
|
|
28
|
+
|
|
29
|
+
"@typescript/typescript-linux-arm": ["@typescript/typescript-linux-arm@7.0.2", "", { "os": "linux", "cpu": "arm" }, "sha512-gffT3xPz9sR7j/YJExkyPntrI0P2EP9XbOyWzth2/Gs0RstK+90RBcO0ncXoXy/beYll1SXw846Nf2zdnEz0QQ=="],
|
|
30
|
+
|
|
31
|
+
"@typescript/typescript-linux-arm64": ["@typescript/typescript-linux-arm64@7.0.2", "", { "os": "linux", "cpu": "arm64" }, "sha512-Qh4eU4/y3yDjnfjjyPYihMj5/ODIlmt+Bzu17OI+fiSRDW57QmU5SiN63exPRNJPKUzcc1INa1NXdrJ+MqHjUQ=="],
|
|
32
|
+
|
|
33
|
+
"@typescript/typescript-linux-loong64": ["@typescript/typescript-linux-loong64@7.0.2", "", { "os": "linux", "cpu": "none" }, "sha512-uEHck9i8hoAzXPiYRib1O7miOnz23SxIeVl6F4LXox+qov1K35jHcEW6VHKvZI+pyvl7fZEP4MCU5LYvIq1GuQ=="],
|
|
34
|
+
|
|
35
|
+
"@typescript/typescript-linux-mips64el": ["@typescript/typescript-linux-mips64el@7.0.2", "", { "os": "linux", "cpu": "none" }, "sha512-R4KvAMnE43W5Qeqb0Ly56O3mWMWIAgsMyz36DCaycd5nbg/9kzm0liw3JocfRqyJY0KPmzFjbswozXyW0DnIYA=="],
|
|
36
|
+
|
|
37
|
+
"@typescript/typescript-linux-ppc64": ["@typescript/typescript-linux-ppc64@7.0.2", "", { "os": "linux", "cpu": "ppc64" }, "sha512-DORx5b3sd/4S7eayxm4FQv+A7CrkUIGRaHiwI8oiHTAI1fAPWhF4J0vAlkC8biAlHSVVwxMQ3tjZ2/DVbnQiiA=="],
|
|
38
|
+
|
|
39
|
+
"@typescript/typescript-linux-riscv64": ["@typescript/typescript-linux-riscv64@7.0.2", "", { "os": "linux", "cpu": "none" }, "sha512-wf0jqEDOjrPRnKwYRyyJDRo11KMbvMFrU+q4zqKyChODBzvlkbhNQfKvLxQCcwTpdDaXSHZTVuh0JoCrKCUMHQ=="],
|
|
40
|
+
|
|
41
|
+
"@typescript/typescript-linux-s390x": ["@typescript/typescript-linux-s390x@7.0.2", "", { "os": "linux", "cpu": "s390x" }, "sha512-IkwJc3L7yhytWd/ewjyxNDfOmswCm9GWMJT/ue/dU4aZNbwZeYAetq42VyLmsmSjvoX7z74X6ZaYCtzAr0EuGw=="],
|
|
42
|
+
|
|
43
|
+
"@typescript/typescript-linux-x64": ["@typescript/typescript-linux-x64@7.0.2", "", { "os": "linux", "cpu": "x64" }, "sha512-EYdf2cNg7rgCWJnxCdJ+F3V39O8ihb37eHAu1LK8oAFizgTQbPOK7zHHXbPt8rX24COqODXeI3sIf0fCXG7H/A=="],
|
|
44
|
+
|
|
45
|
+
"@typescript/typescript-netbsd-arm64": ["@typescript/typescript-netbsd-arm64@7.0.2", "", { "os": "none", "cpu": "arm64" }, "sha512-+polYF4MF04aPpO5FTkHran9yUQDSXqy5GiSDKpsll5jy3l3+g9QLhpf39T+ePtefhXLOGrLl0QIjkQP6VnelA=="],
|
|
46
|
+
|
|
47
|
+
"@typescript/typescript-netbsd-x64": ["@typescript/typescript-netbsd-x64@7.0.2", "", { "os": "none", "cpu": "x64" }, "sha512-8YIT0EHM/3dq10ZOVF/A7pc/YSMtbcecct4rWtexrnSCHOPcpC2KTLXfTCR6vDpnSiY12heNb1GiN/wu+T/FyA=="],
|
|
48
|
+
|
|
49
|
+
"@typescript/typescript-openbsd-arm64": ["@typescript/typescript-openbsd-arm64@7.0.2", "", { "os": "openbsd", "cpu": "arm64" }, "sha512-APT8+ClYnuYm1u9+kgGXoMj2VzWzcymwh2gNSQVySHfkRDGOTVkoWLjCmOQSaO+PoqQ57B0flRp9SA+7GnnkzQ=="],
|
|
50
|
+
|
|
51
|
+
"@typescript/typescript-openbsd-x64": ["@typescript/typescript-openbsd-x64@7.0.2", "", { "os": "openbsd", "cpu": "x64" }, "sha512-yX7s+Q0Dln0Dt9tEzZsAjXXR/+ytBM7AlglaqyeMPxQszJ1JhlJdZ6jLA+IzldHtflX81em7lDao1xXu+aRRkg=="],
|
|
52
|
+
|
|
53
|
+
"@typescript/typescript-sunos-x64": ["@typescript/typescript-sunos-x64@7.0.2", "", { "os": "sunos", "cpu": "x64" }, "sha512-dLJDGaLZ1D4HPQn62u1n8mBDkJREwMsAkCdkwd4Ieqw+x3TUyTsqY0YiBCtE6H6OzzgGk3iuZ3vFWRS+E8/d1g=="],
|
|
54
|
+
|
|
55
|
+
"@typescript/typescript-win32-arm64": ["@typescript/typescript-win32-arm64@7.0.2", "", { "os": "win32", "cpu": "arm64" }, "sha512-Gyl1Vy6OsWesLzmq+EP0Fb7b4Nid5232AvcA2SFcdYreldpNtYFFofPjnt62y9hQy7VTaZp65ICJjuAQRaVcIQ=="],
|
|
56
|
+
|
|
57
|
+
"@typescript/typescript-win32-x64": ["@typescript/typescript-win32-x64@7.0.2", "", { "os": "win32", "cpu": "x64" }, "sha512-0BQ3HkAHHlKLSp1qRvf3SUhGpGsDuhB/jgFw75guyqbxJqEaS0Cw/VFO8i2nHglJUzQCRtMMR/IBAKE3ETMC4g=="],
|
|
58
|
+
|
|
59
|
+
"bun-types": ["bun-types@1.3.14", "", { "dependencies": { "@types/node": "*" } }, "sha512-4N0ig0fEomHt5R0KCFWjovxow98rIoRwKolrYdCcknNwMekCXRnWEUvgu5soYV8QXtVsrUD8B95MBOZGPvr6KQ=="],
|
|
60
|
+
|
|
61
|
+
"commander": ["commander@14.0.0", "", {}, "sha512-2uM9rYjPvyq39NwLRqaiLtWHyDC1FvryJDa2ATTVims5YAS4PupsEQsDvP14FqhFr0P49CYDugi59xaxJlTXRA=="],
|
|
62
|
+
|
|
63
|
+
"typescript": ["typescript@7.0.2", "", { "optionalDependencies": { "@typescript/typescript-aix-ppc64": "7.0.2", "@typescript/typescript-darwin-arm64": "7.0.2", "@typescript/typescript-darwin-x64": "7.0.2", "@typescript/typescript-freebsd-arm64": "7.0.2", "@typescript/typescript-freebsd-x64": "7.0.2", "@typescript/typescript-linux-arm": "7.0.2", "@typescript/typescript-linux-arm64": "7.0.2", "@typescript/typescript-linux-loong64": "7.0.2", "@typescript/typescript-linux-mips64el": "7.0.2", "@typescript/typescript-linux-ppc64": "7.0.2", "@typescript/typescript-linux-riscv64": "7.0.2", "@typescript/typescript-linux-s390x": "7.0.2", "@typescript/typescript-linux-x64": "7.0.2", "@typescript/typescript-netbsd-arm64": "7.0.2", "@typescript/typescript-netbsd-x64": "7.0.2", "@typescript/typescript-openbsd-arm64": "7.0.2", "@typescript/typescript-openbsd-x64": "7.0.2", "@typescript/typescript-sunos-x64": "7.0.2", "@typescript/typescript-win32-arm64": "7.0.2", "@typescript/typescript-win32-x64": "7.0.2" }, "bin": { "tsc": "bin/tsc" } }, "sha512-8FYau96o3NKOhbjKi/qNvG/W5jhzxkbdm5sj9AbZ/5T5sWqn3hJgLfGx27sRKZWTvyzCP8dLRBTf5tBTSRVUNA=="],
|
|
64
|
+
|
|
65
|
+
"undici-types": ["undici-types@8.3.0", "", {}, "sha512-j375ScV60dom+YkPFIfTLcOiPxkN/buHz5GobjLhixFuANaNs3C9l4GmrWqejgXWJ7BbJcFYpTEUkS1Ge8bpZQ=="],
|
|
66
|
+
}
|
|
67
|
+
}
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import fs from "node:fs";
|
|
3
|
+
import process from "node:process";
|
|
4
|
+
|
|
5
|
+
const RULE =
|
|
6
|
+
"Tests alone are not sufficient verification. A PR is verified only when its unit, live, and perf boxes are all checked.";
|
|
7
|
+
const LANES = "Ten parallel lanes at the PR head";
|
|
8
|
+
const SUB_BLOCKS = [
|
|
9
|
+
"Depends on.",
|
|
10
|
+
"Files.",
|
|
11
|
+
"Build.",
|
|
12
|
+
"You see.",
|
|
13
|
+
"Verify, unit.",
|
|
14
|
+
"Verify, live.",
|
|
15
|
+
"Verify, perf.",
|
|
16
|
+
"Review gate.",
|
|
17
|
+
"Merge.",
|
|
18
|
+
];
|
|
19
|
+
const PROGRAM_H3 = ["Arm the program", "Spawn owners", "PR mechanics", "Verdict and merge", "Boot recipe"];
|
|
20
|
+
const PROGRAM_MARKERS = ["/goal", "git show origin/main:", /30[- ]minute/, "status message"];
|
|
21
|
+
const HOW_TO_READ_MARKERS = [
|
|
22
|
+
"One box is one unit of work",
|
|
23
|
+
"names the evidence",
|
|
24
|
+
"Check a box only when its evidence exists",
|
|
25
|
+
"playbooks/",
|
|
26
|
+
RULE,
|
|
27
|
+
];
|
|
28
|
+
const PERF_ITEMS = ["Metric.", "Probe.", "Baseline.", "Rule."];
|
|
29
|
+
const BOX = /^\s*- \[[ x]\] (.*)$/;
|
|
30
|
+
|
|
31
|
+
const file = process.argv[2];
|
|
32
|
+
if (!file) {
|
|
33
|
+
console.error("Usage: node check-plan.mjs <plan.md>");
|
|
34
|
+
process.exit(2);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const raw = fs.readFileSync(file, "utf8").split(/\r?\n/);
|
|
38
|
+
const problems = [];
|
|
39
|
+
const fail = (line, message) => problems.push(`${file}:${line}: ${message}`);
|
|
40
|
+
|
|
41
|
+
let start = 0;
|
|
42
|
+
if (raw[0] === "---") {
|
|
43
|
+
start = raw.indexOf("---", 1) + 1;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const lines = [];
|
|
47
|
+
let fence = false;
|
|
48
|
+
for (let i = start; i < raw.length; i++) {
|
|
49
|
+
const text = raw[i];
|
|
50
|
+
const n = i + 1;
|
|
51
|
+
if (/^```/.test(text)) fence = !fence;
|
|
52
|
+
lines.push({ n, text, code: fence });
|
|
53
|
+
if (fence) continue;
|
|
54
|
+
const prose = text
|
|
55
|
+
.replace(/`[^`]*`/g, "`")
|
|
56
|
+
.replace(/!\[[^\]]*\]\([^)]*\)/g, "")
|
|
57
|
+
.replace(/\]\([^)]*\)/g, "]");
|
|
58
|
+
if (/[\u2013\u2014]/.test(prose)) fail(n, "long dash");
|
|
59
|
+
if (/[\u2018\u2019\u201c\u201d]/.test(prose)) fail(n, "curly quote");
|
|
60
|
+
if (/: \S/.test(prose)) fail(n, "mid-sentence colon");
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const h2 = (l) => (!l.code && l.text.startsWith("## ") ? l.text.slice(3).trim() : null);
|
|
64
|
+
const sections = [];
|
|
65
|
+
for (const l of lines) {
|
|
66
|
+
const title = h2(l);
|
|
67
|
+
if (title !== null) sections.push({ title, n: l.n, body: [] });
|
|
68
|
+
else if (sections.length) sections.at(-1).body.push(l);
|
|
69
|
+
}
|
|
70
|
+
const find = (title) => sections.find((s) => s.title === title);
|
|
71
|
+
const bodyText = (s) => s.body.map((l) => l.text).join("\n");
|
|
72
|
+
const boxes = (ls) => ls.filter((l) => !l.code && BOX.test(l.text)).map((l) => ({ n: l.n, text: l.text.match(BOX)[1] }));
|
|
73
|
+
|
|
74
|
+
const h1 = lines.findIndex((l) => !l.code && l.text.startsWith("# "));
|
|
75
|
+
if (h1 === -1) fail(1, "no H1 title");
|
|
76
|
+
const howToRead = find("How to read this");
|
|
77
|
+
if (!howToRead) fail(1, 'no "## How to read this" section');
|
|
78
|
+
if (h1 !== -1 && howToRead) {
|
|
79
|
+
const intro = lines.slice(h1 + 1).filter((l) => l.n < howToRead.n && l.text.trim() !== "");
|
|
80
|
+
if (intro.length >= 10) fail(lines[h1].n, `intro is ${intro.length} lines, under ten required`);
|
|
81
|
+
for (const marker of HOW_TO_READ_MARKERS) {
|
|
82
|
+
if (!bodyText(howToRead).includes(marker)) fail(howToRead.n, `How to read this lacks "${marker}"`);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
const program = find("Program checklist");
|
|
87
|
+
if (!program) fail(1, 'no "## Program checklist" section');
|
|
88
|
+
else {
|
|
89
|
+
const h3s = program.body.filter((l) => !l.code && l.text.startsWith("### ")).map((l) => l.text.slice(4).trim());
|
|
90
|
+
let cursor = 0;
|
|
91
|
+
for (const name of PROGRAM_H3) {
|
|
92
|
+
const at = h3s.findIndex((t, i) => i >= cursor && t.startsWith(name));
|
|
93
|
+
if (at === -1) fail(program.n, `Program checklist lacks "### ${name}" in order`);
|
|
94
|
+
else cursor = at + 1;
|
|
95
|
+
}
|
|
96
|
+
for (const marker of PROGRAM_MARKERS) {
|
|
97
|
+
const ok = marker instanceof RegExp ? marker.test(bodyText(program)) : bodyText(program).includes(marker);
|
|
98
|
+
if (!ok) fail(program.n, `Program checklist lacks "${marker}"`);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
const close = find("Close the program");
|
|
103
|
+
if (!close) fail(1, 'no "## Close the program" section');
|
|
104
|
+
const programIndex = sections.indexOf(program);
|
|
105
|
+
const closeIndex = sections.indexOf(close);
|
|
106
|
+
const prSections = programIndex === -1 || closeIndex === -1 ? [] : sections.slice(programIndex + 1, closeIndex);
|
|
107
|
+
if (prSections.length === 0) fail(1, "no PR sections between Program checklist and Close the program");
|
|
108
|
+
|
|
109
|
+
const report = [];
|
|
110
|
+
for (const pr of prSections) {
|
|
111
|
+
const heads = [];
|
|
112
|
+
for (const l of pr.body) {
|
|
113
|
+
if (l.code) continue;
|
|
114
|
+
const m = l.text.match(/^\*\*([^*]+)\*\*(.*)$/);
|
|
115
|
+
if (m && SUB_BLOCKS.includes(m[1])) heads.push({ name: m[1], n: l.n, rest: m[2].trim(), lines: [] });
|
|
116
|
+
else if (heads.length) heads.at(-1).lines.push(l);
|
|
117
|
+
}
|
|
118
|
+
const names = heads.map((h) => h.name);
|
|
119
|
+
if (names.join("|") !== SUB_BLOCKS.join("|")) {
|
|
120
|
+
fail(pr.n, `${pr.title}: sub-blocks are [${names.join(", ")}], expected [${SUB_BLOCKS.join(", ")}]`);
|
|
121
|
+
}
|
|
122
|
+
const block = (name) => heads.find((h) => h.name === name);
|
|
123
|
+
const counts = {};
|
|
124
|
+
for (const h of heads) counts[h.name] = boxes(h.lines).length;
|
|
125
|
+
|
|
126
|
+
const depends = block("Depends on.");
|
|
127
|
+
if (depends && depends.rest === "") fail(depends.n, `${pr.title}: Depends on names nothing`);
|
|
128
|
+
for (const name of ["Files.", "Build.", "You see.", "Verify, unit.", "Merge."]) {
|
|
129
|
+
const b = block(name);
|
|
130
|
+
if (b && boxes(b.lines).length === 0) fail(b.n, `${pr.title}: ${name} has no box`);
|
|
131
|
+
}
|
|
132
|
+
for (const name of ["Verify, unit.", "Verify, live.", "Verify, perf."]) {
|
|
133
|
+
const b = block(name);
|
|
134
|
+
if (b && !b.rest.startsWith(RULE)) fail(b.n, `${pr.title}: ${name} does not open with the rule`);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
const live = block("Verify, live.");
|
|
138
|
+
if (live) {
|
|
139
|
+
if (!live.rest.includes(LANES)) fail(live.n, `${pr.title}: Verify, live lacks "${LANES}"`);
|
|
140
|
+
const lanes = boxes(live.lines).map((b) => ({ ...b, m: b.text.match(/^Lane (\d+)\. /) }));
|
|
141
|
+
const numbers = lanes.filter((b) => b.m).map((b) => Number(b.m[1])).sort((a, b) => a - b);
|
|
142
|
+
if (numbers.join(",") !== "1,2,3,4,5,6,7,8,9,10") fail(live.n, `${pr.title}: lanes are [${numbers.join(",")}], expected 1 to 10`);
|
|
143
|
+
for (const lane of lanes) {
|
|
144
|
+
if (!lane.m) fail(lane.n, `${pr.title}: live box is not a lane`);
|
|
145
|
+
else if (!/Save `[^`]+`/.test(lane.text)) fail(lane.n, `${pr.title}: lane ${lane.m[1]} names no screenshot`);
|
|
146
|
+
else if (!lane.text.includes("Pass when")) fail(lane.n, `${pr.title}: lane ${lane.m[1]} has no pass predicate`);
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
const perf = block("Verify, perf.");
|
|
151
|
+
if (perf) {
|
|
152
|
+
const items = boxes(perf.lines).map((b) => b.text.split(" ")[0]);
|
|
153
|
+
if (items.join("|") !== PERF_ITEMS.join("|")) fail(perf.n, `${pr.title}: perf boxes are [${items.join(", ")}], expected [${PERF_ITEMS.join(", ")}]`);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
const gate = block("Review gate.");
|
|
157
|
+
if (gate) {
|
|
158
|
+
const gateBoxes = boxes(gate.lines);
|
|
159
|
+
if (gate.rest.startsWith("None.")) {
|
|
160
|
+
if (gateBoxes.length) fail(gate.n, `${pr.title}: Review gate says None but has boxes`);
|
|
161
|
+
} else {
|
|
162
|
+
const text = gate.lines.map((l) => l.text).join("\n");
|
|
163
|
+
if (gateBoxes.length === 0) fail(gate.n, `${pr.title}: Review gate has no box`);
|
|
164
|
+
for (const word of ["screenshot", "video", "operator"]) {
|
|
165
|
+
if (!text.includes(word)) fail(gate.n, `${pr.title}: Review gate lacks "${word}"`);
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
const total = boxes(pr.body).length;
|
|
171
|
+
const cells = SUB_BLOCKS.filter((s) => s !== "Depends on.").map((s) => `${s.replace(/[ ,.]+/g, "-").replace(/-$/, "").toLowerCase()}=${counts[s] ?? 0}`);
|
|
172
|
+
report.push(`${pr.title} boxes=${total} ${cells.join(" ")}`);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
if (closeIndex !== -1) {
|
|
176
|
+
const tail = sections.slice(closeIndex + 1);
|
|
177
|
+
for (const s of tail) {
|
|
178
|
+
if (!s.title.startsWith("Appendix")) fail(s.n, `"## ${s.title}" after Close the program is not an appendix`);
|
|
179
|
+
}
|
|
180
|
+
if (!tail.some((s) => s.title.includes("Prototype evidence"))) fail(close.n, 'no "## Appendix ... Prototype evidence" section');
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
for (const line of report) console.log(line);
|
|
184
|
+
console.log(`${prSections.length} PR sections, ${problems.length} problems`);
|
|
185
|
+
for (const p of problems) console.error(p);
|
|
186
|
+
process.exit(problems.length ? 1 : 0);
|