dsh-pstack 0.0.0-stage → 0.2.0-beta.1
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 +22 -0
- package/README.md +133 -2
- package/assets/agents/comment-sicko.md +32 -0
- package/assets/agents/poteto-agent.md +8 -0
- package/assets/docs/guide/01-setup.md +64 -0
- package/assets/docs/guide/02-poteto-mode.md +139 -0
- package/assets/docs/guide/03-understand.md +79 -0
- package/assets/docs/guide/04-design.md +133 -0
- package/assets/docs/guide/05-build-and-clean.md +83 -0
- package/assets/docs/guide/06-verify-and-ship.md +152 -0
- package/assets/docs/guide/07-overnight.md +120 -0
- package/assets/docs/guide/08-principles.md +72 -0
- package/assets/docs/guide/09-make-it-yours.md +96 -0
- package/assets/docs/guide/10-recipes-and-pitfalls.md +180 -0
- package/assets/docs/guide/README.md +38 -0
- package/assets/docs/guide/images/design.jpg +0 -0
- package/assets/docs/guide/images/overnight.jpg +0 -0
- package/assets/docs/guide/images/recipes.jpg +0 -0
- package/assets/docs/guide/images/router.jpg +0 -0
- package/assets/docs/guide/images/understanding.jpg +0 -0
- package/assets/docs/guide/images/verification.jpg +0 -0
- package/assets/docs/harness/dsh.md +60 -0
- package/assets/docs/pstack-models.example.md +54 -0
- package/assets/skills/architect/SKILL.md +81 -0
- package/assets/skills/architect/references/design-red-flags.md +57 -0
- package/assets/skills/architect/references/rationale-template.md +35 -0
- package/assets/skills/architect/references/runner-prompt.md +20 -0
- package/assets/skills/arena/SKILL.md +50 -0
- package/assets/skills/automate-me/SKILL.md +106 -0
- package/assets/skills/benchmark-checklist/SKILL.md +39 -0
- package/assets/skills/blast-radius/SKILL.md +52 -0
- package/assets/skills/bro/SKILL.md +7 -0
- package/assets/skills/correct/SKILL.md +32 -0
- package/assets/skills/create-formal-verification-skill/SKILL.md +43 -0
- package/assets/skills/create-verification-skill/SKILL.md +46 -0
- package/assets/skills/create-verification-skill/references/feature-map-example/README.md +47 -0
- package/assets/skills/create-verification-skill/references/feature-map-example/create-note.md +39 -0
- package/assets/skills/create-verification-skill/references/feature-map-example/search.md +45 -0
- package/assets/skills/figure-it-out/SKILL.md +54 -0
- package/assets/skills/how/SKILL.md +38 -0
- package/assets/skills/how/references/explainer-prompt.md +55 -0
- package/assets/skills/how/references/explorer-prompt.md +35 -0
- package/assets/skills/in-your-own-words/SKILL.md +7 -0
- package/assets/skills/interrogate/SKILL.md +71 -0
- package/assets/skills/interrogate/references/code-quality-review.md +47 -0
- package/assets/skills/interrogate/references/lead-judgment.md +39 -0
- package/assets/skills/interrogate/references/reviewer-prompt.md +62 -0
- package/assets/skills/interrogate/references/rubric.md +77 -0
- package/assets/skills/ketchup/SKILL.md +45 -0
- package/assets/skills/maintain-formal-verification-skill/SKILL.md +60 -0
- package/assets/skills/maintain-verification-skill/SKILL.md +41 -0
- package/assets/skills/no-comments/SKILL.md +26 -0
- package/assets/skills/poteto-help/SKILL.md +162 -0
- package/assets/skills/poteto-help/references/prompting.md +51 -0
- package/assets/skills/poteto-help/references/recipes.md +51 -0
- package/assets/skills/poteto-mode/SKILL.md +151 -0
- package/assets/skills/poteto-mode/playbooks/authoring-a-skill.md +12 -0
- package/assets/skills/poteto-mode/playbooks/autonomous-run.md +15 -0
- package/assets/skills/poteto-mode/playbooks/autopilot-full.md +25 -0
- package/assets/skills/poteto-mode/playbooks/autopilot-stack.md +12 -0
- package/assets/skills/poteto-mode/playbooks/babysit.md +23 -0
- package/assets/skills/poteto-mode/playbooks/bug-fix.md +15 -0
- package/assets/skills/poteto-mode/playbooks/eval.md +25 -0
- package/assets/skills/poteto-mode/playbooks/feature.md +21 -0
- package/assets/skills/poteto-mode/playbooks/hillclimb.md +21 -0
- package/assets/skills/poteto-mode/playbooks/investigation.md +14 -0
- package/assets/skills/poteto-mode/playbooks/multi-phase-plan.md +155 -0
- package/assets/skills/poteto-mode/playbooks/opening-a-pr.md +34 -0
- package/assets/skills/poteto-mode/playbooks/orchestrate.md +112 -0
- package/assets/skills/poteto-mode/playbooks/pause-safely.md +10 -0
- package/assets/skills/poteto-mode/playbooks/perf-issue.md +25 -0
- package/assets/skills/poteto-mode/playbooks/prototype.md +14 -0
- package/assets/skills/poteto-mode/playbooks/refactoring.md +16 -0
- package/assets/skills/poteto-mode/playbooks/runtime-forensics.md +11 -0
- package/assets/skills/poteto-mode/playbooks/session-pickup.md +11 -0
- package/assets/skills/poteto-mode/playbooks/shipping.md +12 -0
- package/assets/skills/poteto-mode/playbooks/trace-forensics.md +14 -0
- package/assets/skills/poteto-mode/playbooks/visual-parity.md +11 -0
- package/assets/skills/poteto-mode/playbooks/worktree-cleanup.md +20 -0
- package/assets/skills/poteto-mode/references/bugbot-triage.md +142 -0
- package/assets/skills/poteto-mode/scripts/check-plan.mjs +220 -0
- package/assets/skills/poteto-mode/scripts/check-plan.test.mjs +343 -0
- package/assets/skills/poteto-mode/scripts/orch/orch.mjs +4282 -0
- package/assets/skills/poteto-mode/scripts/orch/orch.test.mjs +2042 -0
- package/assets/skills/poteto-mode/scripts/watch-pr/cli.test.mjs +5101 -0
- package/assets/skills/poteto-mode/scripts/watch-pr/github.test.mjs +1177 -0
- package/assets/skills/poteto-mode/scripts/watch-pr/policy.test.mjs +1441 -0
- package/assets/skills/poteto-mode/scripts/watch-pr/watch-pr.mjs +4601 -0
- package/assets/skills/poteto-mode/scripts/worktree-audit.mjs +675 -0
- package/assets/skills/principle-attack-the-premise/SKILL.md +21 -0
- package/assets/skills/principle-boundary-discipline/SKILL.md +34 -0
- package/assets/skills/principle-build-the-lever/SKILL.md +23 -0
- package/assets/skills/principle-encode-lessons-in-structure/SKILL.md +11 -0
- package/assets/skills/principle-exhaust-the-design-space/SKILL.md +9 -0
- package/assets/skills/principle-experience-first/SKILL.md +19 -0
- package/assets/skills/principle-explain-the-number/SKILL.md +17 -0
- package/assets/skills/principle-fix-root-causes/SKILL.md +21 -0
- package/assets/skills/principle-foundational-thinking/SKILL.md +21 -0
- package/assets/skills/principle-guard-the-context-window/SKILL.md +12 -0
- package/assets/skills/principle-laziness-protocol/SKILL.md +10 -0
- package/assets/skills/principle-make-operations-idempotent/SKILL.md +22 -0
- package/assets/skills/principle-migrate-callers-then-delete-legacy-apis/SKILL.md +14 -0
- package/assets/skills/principle-minimize-reader-load/SKILL.md +18 -0
- package/assets/skills/principle-model-the-domain/SKILL.md +24 -0
- package/assets/skills/principle-never-block-on-the-human/SKILL.md +15 -0
- package/assets/skills/principle-outcome-oriented-execution/SKILL.md +21 -0
- package/assets/skills/principle-prove-it-works/SKILL.md +24 -0
- package/assets/skills/principle-redesign-from-first-principles/SKILL.md +16 -0
- package/assets/skills/principle-separate-before-serializing-shared-state/SKILL.md +16 -0
- package/assets/skills/principle-sequence-verifiable-units/SKILL.md +17 -0
- package/assets/skills/principle-subtract-before-you-add/SKILL.md +18 -0
- package/assets/skills/principle-test-behavior-not-implementation/SKILL.md +20 -0
- package/assets/skills/principle-type-system-discipline/SKILL.md +13 -0
- package/assets/skills/recall/SKILL.md +37 -0
- package/assets/skills/reflect/SKILL.md +69 -0
- package/assets/skills/reflect/references/divergent-reviewer.md +38 -0
- package/assets/skills/reflect/references/judgment-reviewer.md +37 -0
- package/assets/skills/reflect/references/synthesizer.md +56 -0
- package/assets/skills/reflect/references/tooling-reviewer.md +48 -0
- package/assets/skills/setup-pstack/SKILL.md +67 -0
- package/assets/skills/sharpen/SKILL.md +16 -0
- package/assets/skills/show-me-your-work/SKILL.md +84 -0
- package/assets/skills/show-me-your-work/references/decision-log-template.tsv +1 -0
- package/assets/skills/show-me-your-work/scripts/log.mjs +108 -0
- package/assets/skills/swarm/SKILL.md +43 -0
- package/assets/skills/tdd/SKILL.md +42 -0
- package/assets/skills/teach/SKILL.md +23 -0
- package/assets/skills/technical-writing/SKILL.md +108 -0
- package/assets/skills/typescript-best-practices/SKILL.md +34 -0
- package/assets/skills/typescript-best-practices/references/patterns.md +328 -0
- package/assets/skills/unslop/SKILL.md +62 -0
- package/assets/skills/why/SKILL.md +100 -0
- package/assets/skills/why/references/epistemics.md +71 -0
- package/assets/skills/why/references/investigator-prompt.md +84 -0
- package/assets/skills/why/references/source-playbook.md +17 -0
- package/assets/skills/why/references/sources/code-archaeology.md +51 -0
- package/assets/skills/why/references/sources/databricks.md +70 -0
- package/assets/skills/why/references/sources/datadog.md +97 -0
- package/assets/skills/why/references/sources/incident-postmortem.md +5 -0
- package/assets/skills/why/references/sources/linear.md +48 -0
- package/assets/skills/why/references/sources/notion.md +55 -0
- package/assets/skills/why/references/sources/sentry.md +66 -0
- package/assets/skills/why/references/sources/slack.md +17 -0
- package/assets/skills/why/references/synthesizer-prompt.md +129 -0
- package/cordis.patch.yml +39 -0
- package/dist/frontmatter-BQX3ekwP.mjs +229 -0
- package/dist/frontmatter-BQX3ekwP.mjs.map +1 -0
- package/dist/history.d.mts +429 -0
- package/dist/history.mjs +1129 -0
- package/dist/history.mjs.map +1 -0
- package/dist/host-contract-o47JH_r8.d.mts +527 -0
- package/dist/index.d.mts +278 -0
- package/dist/index.mjs +272 -0
- package/dist/index.mjs.map +1 -0
- package/dist/paths-D_JlVmBk.mjs +42 -0
- package/dist/paths-D_JlVmBk.mjs.map +1 -0
- package/dist/script.d.mts +193 -0
- package/dist/script.mjs +782 -0
- package/dist/script.mjs.map +1 -0
- package/dist/subagent.d.mts +336 -0
- package/dist/subagent.mjs +1402 -0
- package/dist/subagent.mjs.map +1 -0
- package/locale/en.json +6 -0
- package/locale/zh.json +6 -0
- package/package.json +106 -3
- package/static/icon.png +0 -0
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
### Visual parity
|
|
2
|
+
|
|
3
|
+
**You own pixel-exact equivalence. The baseline is the spec. You do not touch it.** Equivalence is verified by image diff, not by eye.
|
|
4
|
+
|
|
5
|
+
1. Establish the baseline first, before any migration: a visual regression harness that screenshots the current component across its states, plus the target when matching two implementations. No baseline, no parity claim. A blocking prerequisite, not a follow-up. Freeze it before the first component moves: record the harness command and the baseline image paths, and do not regenerate a baseline mid-run, because a diff against a baseline you rewrote proves nothing.
|
|
6
|
+
2. Anti-shortcut clauses, stated and held: no harness modifications, no baseline tampering, no component restructuring to make a diff pass. If the baseline looks wrong, stop and ask, don't edit it. Editing it is the one shortcut that makes every later diff meaningless.
|
|
7
|
+
3. Migrate one component at a time. Parallelize across local worktrees, one owner per component, because two writers in one checkout overwrite each other (the **separate-before-serializing-shared-state** principle skill). Give each owner its own `create_worktree` checkout and pass that absolute path as its `cwd`; a worktree separates the working directory and the branch, not ports, caches, or services, so name whatever else the component shares. Shared primitives migrate first as a blocking phase.
|
|
8
|
+
4. Verify each component against its baseline with a real image diff on the matching surface. Name the comparison tool and prove it separates before migrating anything. A nonzero diff is a fail, so investigate the pixel delta and iterate until it reaches zero. If this machine has no image comparison that reports a per-image delta, stop and say so: without it the run can only report what the eye saw, and that is not a parity claim. A goal can drive the iteration when the run is long, with a zero-diff predicate and `update_goal` `complete` only when every component's diff is zero, but a goal measures nothing itself, so a completed goal is not a parity verdict.
|
|
9
|
+
5. Run **Opening a PR** per component or per safe batch.
|
|
10
|
+
|
|
11
|
+
**Reply:** components migrated, the diff result for each, the comparison tool and the frozen baseline harness location, what's left, and whether parity is claimed or unverified.
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
### Worktree and simulator cleanup
|
|
2
|
+
|
|
3
|
+
1. Snapshot and audit. Record `df -h /`, then run `pstack_script` with `id: "worktree-audit"` and `args: ["-b", "<pr-base-ref>", "--sessions", "<json>", "<repo-path>"]` (principle-build-the-lever). The tool runs the bundled script on the Host's own runtime, so no `bun`, `bash`, or `node` on the PATH is involved. Without `-b` it checks `MERGED` against `origin/HEAD`, so pass `-b origin/<branch>` when PRs target another branch. It reads paths from `git worktree list`, never hand-typed, since a hand-typed `myrepo-worktrees/x` misses one that lives elsewhere (principle-encode-lessons-in-structure). It classifies each worktree by size, age, merge state, uncommitted work, PR state, and the newest session that touched it, then suggests a bucket.
|
|
4
|
+
|
|
5
|
+
Its session input is an array you build from `pstack_history` results, one entry per session you read:
|
|
6
|
+
|
|
7
|
+
```json
|
|
8
|
+
[{"sessionId": "<session.id>", "cwd": "<session.cwd>", "lastEventTime": 0}]
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
Use the largest `events[].time` in that session's page as `lastEventTime`. The script never reads the session store, so an entry you omit is a worktree it cannot see, and omitting the whole array leaves `LAST_CHAT` as `-` and the bucket as `unknown-usage`. A `pstack_history` result passed through unchanged is refused: that tool returns one object, never an array. Pass `background: true` for a large tree.
|
|
12
|
+
2. The bucket is advice, not permission. The pinned and active sessions are the real artifact (principle-prove-it-works). Pinned state has no public source in this host, so the script prints `PINNED unknown` for every row; ask the user for the pinned and in-use set instead of inferring it, and cross-check every candidate. When the user names a pinned or in-use worktree, that set wins over any `safe` bucket.
|
|
13
|
+
3. Verify usage before deleting. For every `verify-recent-chat` or `unknown-usage` row, or anything you doubt, ask the user which sessions are still live, and read the ones you may with `pstack_history` to see which worktree paths those events touch. Do not scan the session store for paths (principle-guard-the-context-window: session logs are bulk). An active session can start arena and repro trees in sibling worktrees, so a worktree can be in use even when its name never appears in the user's own summary. Pick each subagent's role per the Subagents section of `poteto-mode`; read its `SKILL.md` at its absolute resource path. Start from the level the role line names, raise it for hard work and lower it for routine work by passing `reasoningEffort`; a child that cannot call `set_reasoning_effort` keeps the level you gave it.
|
|
14
|
+
4. Pause on irreversible loss. `wip:N` is N tracked uncommitted edits. Show the diff and get a decision first, since removing a clean worktree is recoverable from its branch but uncommitted work is gone. `scratch:N` is untracked throwaway, safe to drop, but name the files. Per Autonomy, clean and merged and not-in-use proceeds. `wip` and in-use pause.
|
|
15
|
+
5. Prune the confirmed set. Re-read `git worktree list` and confirm each absolute path with the user immediately before touching it, and remove only paths approved in this run. Per path, `git worktree remove --force <path>`. If the dir survives on ignored build artifacts, `rm -rf` that same verified path, then `git worktree prune`. Branch refs survive, so no commits are lost. Confirm with `df -h /` and re-list.
|
|
16
|
+
6. Simulators and other reclaimers. Simulators are usually the next-biggest win. `xcrun simctl --set testing delete all` (XCTestDevices clones), `xcrun simctl delete unavailable`, and `xcrun simctl runtime list` then `runtime delete <id>` for old runtimes. More when needed: Xcode `DerivedData` and `iOS DeviceSupport`, `~/Library/Application Support/Cursor` (`state.vscdb.backup`, and `snapshots/roots/<root>` where a `<root>` named for a folder you opened as a workspace balloons), package caches (pnpm, uv, brew, yarn). Clear only caches the user has not said to keep.
|
|
17
|
+
|
|
18
|
+
This is the one playbook that deletes user state with no code review to catch a slip, so the gates above are the review.
|
|
19
|
+
|
|
20
|
+
**Reply:** `df -h /` before and after with space reclaimed, the worktrees pruned, and a one-line reason for each held back (in use by which session, or which uncommitted work). State the pinned set as the user gave it, and name anything still unknown.
|
|
@@ -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 the active forge's PR list and diffs, upper-stack diffs, or PR context show 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,220 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import fs from "node:fs";
|
|
3
|
+
import process from "node:process";
|
|
4
|
+
//#region .runtime-sources/skills/poteto-mode/scripts/check-plan.mjs
|
|
5
|
+
const RULE = "Tests alone are not sufficient verification. A PR is verified only when its unit, live, and perf boxes are all checked.";
|
|
6
|
+
const LANES = /Ten lanes on `[^`<>]+` at the PR head/;
|
|
7
|
+
const SUB_BLOCKS = [
|
|
8
|
+
"Depends on.",
|
|
9
|
+
"Files.",
|
|
10
|
+
"Build.",
|
|
11
|
+
"You see.",
|
|
12
|
+
"Verify, unit.",
|
|
13
|
+
"Verify, live.",
|
|
14
|
+
"Verify, perf.",
|
|
15
|
+
"Review gate.",
|
|
16
|
+
"Merge."
|
|
17
|
+
];
|
|
18
|
+
const PROGRAM_H3 = [
|
|
19
|
+
"Arm the program",
|
|
20
|
+
"Spawn owners",
|
|
21
|
+
"PR mechanics",
|
|
22
|
+
"Verdict and merge",
|
|
23
|
+
"Boot recipe"
|
|
24
|
+
];
|
|
25
|
+
const PROGRAM_MARKERS = [
|
|
26
|
+
"create_goal",
|
|
27
|
+
"schedule_create",
|
|
28
|
+
"status message"
|
|
29
|
+
];
|
|
30
|
+
const HOW_TO_READ_MARKERS = [
|
|
31
|
+
"One box is one unit of work",
|
|
32
|
+
"names the evidence",
|
|
33
|
+
"Check a box only when its evidence exists",
|
|
34
|
+
"playbooks/",
|
|
35
|
+
RULE
|
|
36
|
+
];
|
|
37
|
+
const PERF_ITEMS = [
|
|
38
|
+
"Metric.",
|
|
39
|
+
"Probe.",
|
|
40
|
+
"Baseline.",
|
|
41
|
+
"Rule."
|
|
42
|
+
];
|
|
43
|
+
const BOX = /^\s*- \[[ x]\] (.*)$/;
|
|
44
|
+
const file = process.argv[2];
|
|
45
|
+
if (!file) {
|
|
46
|
+
console.error("Usage: node check-plan.mjs <plan.md>");
|
|
47
|
+
process.exit(2);
|
|
48
|
+
}
|
|
49
|
+
const raw = fs.readFileSync(file, "utf8").split(/\r?\n/);
|
|
50
|
+
const problems = [];
|
|
51
|
+
const fail = (line, message) => problems.push(`${file}:${line}: ${message}`);
|
|
52
|
+
let start = 0;
|
|
53
|
+
if (raw[0] === "---") start = raw.indexOf("---", 1) + 1;
|
|
54
|
+
const lines = [];
|
|
55
|
+
let fence = null;
|
|
56
|
+
const openingFence = (text) => {
|
|
57
|
+
const match = text.match(/^ {0,3}(`{3,}|~{3,})(.*)$/);
|
|
58
|
+
if (!match || match[1][0] === "`" && match[2].includes("`")) return null;
|
|
59
|
+
return {
|
|
60
|
+
char: match[1][0],
|
|
61
|
+
length: match[1].length
|
|
62
|
+
};
|
|
63
|
+
};
|
|
64
|
+
const closingFence = (text, opening) => {
|
|
65
|
+
const match = text.match(/^ {0,3}(`+|~+)[ \t]*$/);
|
|
66
|
+
return Boolean(match && match[1][0] === opening.char && match[1].length >= opening.length);
|
|
67
|
+
};
|
|
68
|
+
for (let i = start; i < raw.length; i++) {
|
|
69
|
+
const text = raw[i];
|
|
70
|
+
const n = i + 1;
|
|
71
|
+
let code = Boolean(fence);
|
|
72
|
+
if (fence && closingFence(text, fence)) {
|
|
73
|
+
code = false;
|
|
74
|
+
fence = null;
|
|
75
|
+
} else if (!fence) {
|
|
76
|
+
fence = openingFence(text);
|
|
77
|
+
code = Boolean(fence);
|
|
78
|
+
}
|
|
79
|
+
lines.push({
|
|
80
|
+
n,
|
|
81
|
+
text,
|
|
82
|
+
code
|
|
83
|
+
});
|
|
84
|
+
if (code) continue;
|
|
85
|
+
const prose = text.replace(/`[^`]*`/g, "`").replace(/!\[[^\]]*\]\([^)]*\)/g, "").replace(/\]\([^)]*\)/g, "]");
|
|
86
|
+
if (/[\u2013\u2014]/.test(prose)) fail(n, "long dash");
|
|
87
|
+
if (/[\u2018\u2019\u201c\u201d]/.test(prose)) fail(n, "curly quote");
|
|
88
|
+
if (/: \S/.test(prose)) fail(n, "mid-sentence colon");
|
|
89
|
+
}
|
|
90
|
+
const h2 = (l) => !l.code && l.text.startsWith("## ") ? l.text.slice(3).trim() : null;
|
|
91
|
+
const sections = [];
|
|
92
|
+
for (const l of lines) {
|
|
93
|
+
const title = h2(l);
|
|
94
|
+
if (title !== null) sections.push({
|
|
95
|
+
title,
|
|
96
|
+
n: l.n,
|
|
97
|
+
body: []
|
|
98
|
+
});
|
|
99
|
+
else if (sections.length) sections.at(-1).body.push(l);
|
|
100
|
+
}
|
|
101
|
+
const find = (title) => sections.find((s) => s.title === title);
|
|
102
|
+
const bodyText = (s) => s.body.map((l) => l.text).join("\n");
|
|
103
|
+
const boxes = (ls) => ls.filter((l) => !l.code && BOX.test(l.text)).map((l) => ({
|
|
104
|
+
n: l.n,
|
|
105
|
+
text: l.text.match(BOX)[1]
|
|
106
|
+
}));
|
|
107
|
+
const h1 = lines.findIndex((l) => !l.code && l.text.startsWith("# "));
|
|
108
|
+
if (h1 === -1) fail(1, "no H1 title");
|
|
109
|
+
const howToRead = find("How to read this");
|
|
110
|
+
if (!howToRead) fail(1, "no \"## How to read this\" section");
|
|
111
|
+
if (h1 !== -1 && howToRead) {
|
|
112
|
+
const intro = lines.slice(h1 + 1).filter((l) => l.n < howToRead.n && l.text.trim() !== "");
|
|
113
|
+
if (intro.length >= 10) fail(lines[h1].n, `intro is ${intro.length} lines, under ten required`);
|
|
114
|
+
for (const marker of HOW_TO_READ_MARKERS) if (!bodyText(howToRead).includes(marker)) fail(howToRead.n, `How to read this lacks "${marker}"`);
|
|
115
|
+
}
|
|
116
|
+
const program = find("Program checklist");
|
|
117
|
+
if (!program) fail(1, "no \"## Program checklist\" section");
|
|
118
|
+
else {
|
|
119
|
+
const h3s = program.body.filter((l) => !l.code && l.text.startsWith("### ")).map((l) => l.text.slice(4).trim());
|
|
120
|
+
let cursor = 0;
|
|
121
|
+
for (const name of PROGRAM_H3) {
|
|
122
|
+
const at = h3s.findIndex((t, i) => i >= cursor && t.startsWith(name));
|
|
123
|
+
if (at === -1) fail(program.n, `Program checklist lacks "### ${name}" in order`);
|
|
124
|
+
else cursor = at + 1;
|
|
125
|
+
}
|
|
126
|
+
for (const marker of PROGRAM_MARKERS) if (!bodyText(program).includes(marker)) fail(program.n, `Program checklist lacks "${marker}"`);
|
|
127
|
+
}
|
|
128
|
+
const close = find("Close the program");
|
|
129
|
+
if (!close) fail(1, "no \"## Close the program\" section");
|
|
130
|
+
const programIndex = sections.indexOf(program);
|
|
131
|
+
const closeIndex = sections.indexOf(close);
|
|
132
|
+
const prSections = programIndex === -1 || closeIndex === -1 ? [] : sections.slice(programIndex + 1, closeIndex);
|
|
133
|
+
if (prSections.length === 0) fail(1, "no PR sections between Program checklist and Close the program");
|
|
134
|
+
const report = [];
|
|
135
|
+
for (const pr of prSections) {
|
|
136
|
+
const heads = [];
|
|
137
|
+
for (const l of pr.body) {
|
|
138
|
+
if (l.code) continue;
|
|
139
|
+
const m = l.text.match(/^\*\*([^*]+)\*\*(.*)$/);
|
|
140
|
+
if (m && SUB_BLOCKS.includes(m[1])) heads.push({
|
|
141
|
+
name: m[1],
|
|
142
|
+
n: l.n,
|
|
143
|
+
rest: m[2].trim(),
|
|
144
|
+
lines: []
|
|
145
|
+
});
|
|
146
|
+
else if (heads.length) heads.at(-1).lines.push(l);
|
|
147
|
+
}
|
|
148
|
+
const names = heads.map((h) => h.name);
|
|
149
|
+
if (names.join("|") !== SUB_BLOCKS.join("|")) fail(pr.n, `${pr.title}: sub-blocks are [${names.join(", ")}], expected [${SUB_BLOCKS.join(", ")}]`);
|
|
150
|
+
const block = (name) => heads.find((h) => h.name === name);
|
|
151
|
+
const counts = {};
|
|
152
|
+
for (const h of heads) counts[h.name] = boxes(h.lines).length;
|
|
153
|
+
const depends = block("Depends on.");
|
|
154
|
+
if (depends && depends.rest === "") fail(depends.n, `${pr.title}: Depends on names nothing`);
|
|
155
|
+
for (const name of [
|
|
156
|
+
"Files.",
|
|
157
|
+
"Build.",
|
|
158
|
+
"You see.",
|
|
159
|
+
"Verify, unit.",
|
|
160
|
+
"Merge."
|
|
161
|
+
]) {
|
|
162
|
+
const b = block(name);
|
|
163
|
+
if (b && boxes(b.lines).length === 0) fail(b.n, `${pr.title}: ${name} has no box`);
|
|
164
|
+
}
|
|
165
|
+
for (const name of [
|
|
166
|
+
"Verify, unit.",
|
|
167
|
+
"Verify, live.",
|
|
168
|
+
"Verify, perf."
|
|
169
|
+
]) {
|
|
170
|
+
const b = block(name);
|
|
171
|
+
if (b && !b.rest.startsWith(RULE)) fail(b.n, `${pr.title}: ${name} does not open with the rule`);
|
|
172
|
+
}
|
|
173
|
+
const live = block("Verify, live.");
|
|
174
|
+
if (live) {
|
|
175
|
+
if (!LANES.test(live.rest)) fail(live.n, `${pr.title}: Verify, live lacks "Ten lanes on \`<swarm workers model>\` at the PR head" with the model filled in`);
|
|
176
|
+
const lanes = boxes(live.lines).map((b) => ({
|
|
177
|
+
...b,
|
|
178
|
+
m: b.text.match(/^Lane (\d+)\. /)
|
|
179
|
+
}));
|
|
180
|
+
const numbers = lanes.filter((b) => b.m).map((b) => Number(b.m[1])).sort((a, b) => a - b);
|
|
181
|
+
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`);
|
|
182
|
+
for (const lane of lanes) if (!lane.m) fail(lane.n, `${pr.title}: live box is not a lane`);
|
|
183
|
+
else if (!/Save `[^`]+`/.test(lane.text)) fail(lane.n, `${pr.title}: lane ${lane.m[1]} names no screenshot`);
|
|
184
|
+
else if (!lane.text.includes("Pass when")) fail(lane.n, `${pr.title}: lane ${lane.m[1]} has no pass predicate`);
|
|
185
|
+
}
|
|
186
|
+
const perf = block("Verify, perf.");
|
|
187
|
+
if (perf) {
|
|
188
|
+
const items = boxes(perf.lines).map((b) => b.text.split(" ")[0]);
|
|
189
|
+
if (items.join("|") !== PERF_ITEMS.join("|")) fail(perf.n, `${pr.title}: perf boxes are [${items.join(", ")}], expected [${PERF_ITEMS.join(", ")}]`);
|
|
190
|
+
}
|
|
191
|
+
const gate = block("Review gate.");
|
|
192
|
+
if (gate) {
|
|
193
|
+
const gateBoxes = boxes(gate.lines);
|
|
194
|
+
if (gate.rest.startsWith("None.")) {
|
|
195
|
+
if (gateBoxes.length) fail(gate.n, `${pr.title}: Review gate says None but has boxes`);
|
|
196
|
+
} else {
|
|
197
|
+
const text = gate.lines.map((l) => l.text).join("\n");
|
|
198
|
+
if (gateBoxes.length === 0) fail(gate.n, `${pr.title}: Review gate has no box`);
|
|
199
|
+
for (const word of [
|
|
200
|
+
"screenshot",
|
|
201
|
+
"video",
|
|
202
|
+
"operator"
|
|
203
|
+
]) if (!text.includes(word)) fail(gate.n, `${pr.title}: Review gate lacks "${word}"`);
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
const total = boxes(pr.body).length;
|
|
207
|
+
const cells = SUB_BLOCKS.filter((s) => s !== "Depends on.").map((s) => `${s.replace(/[ ,.]+/g, "-").replace(/-$/, "").toLowerCase()}=${counts[s] ?? 0}`);
|
|
208
|
+
report.push(`${pr.title} boxes=${total} ${cells.join(" ")}`);
|
|
209
|
+
}
|
|
210
|
+
if (closeIndex !== -1) {
|
|
211
|
+
const tail = sections.slice(closeIndex + 1);
|
|
212
|
+
for (const s of tail) if (!s.title.startsWith("Appendix")) fail(s.n, `"## ${s.title}" after Close the program is not an appendix`);
|
|
213
|
+
if (!tail.some((s) => s.title.includes("Prototype evidence"))) fail(close.n, "no \"## Appendix ... Prototype evidence\" section");
|
|
214
|
+
}
|
|
215
|
+
for (const line of report) console.log(line);
|
|
216
|
+
console.log(`${prSections.length} PR sections, ${problems.length} problems`);
|
|
217
|
+
for (const p of problems) console.error(p);
|
|
218
|
+
process.exit(problems.length ? 1 : 0);
|
|
219
|
+
//#endregion
|
|
220
|
+
export {};
|