pi-crew 0.10.3 → 0.10.4

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.
Files changed (62) hide show
  1. package/AGENTS.md +2 -1
  2. package/README.md +5 -1
  3. package/dist/index.mjs +10685 -6882
  4. package/docs/architecture.md +4 -4
  5. package/docs/commands-reference.md +3 -0
  6. package/docs/publishing.md +15 -3
  7. package/install.mjs +90 -39
  8. package/package.json +8 -3
  9. package/scripts/README.md +4 -3
  10. package/skills/real-test-pi-crew/REPORT-TEMPLATE.md +1 -0
  11. package/skills/real-test-pi-crew/SKILL.md +153 -6
  12. package/src/config/migration-validator.ts +113 -0
  13. package/src/extension/cross-extension-rpc.ts +3 -7
  14. package/src/extension/register.ts +13 -0
  15. package/src/extension/registration/observability.ts +3 -7
  16. package/src/extension/registration/subagent-tools.ts +3 -7
  17. package/src/extension/registration/team-tool.ts +3 -7
  18. package/src/extension/registration/ui.ts +3 -8
  19. package/src/extension/registration/viewers.ts +3 -10
  20. package/src/extension/team-manager-command.ts +3 -7
  21. package/src/extension/team-tool/api/agent-control.ts +17 -10
  22. package/src/extension/team-tool/api/heartbeat.ts +4 -3
  23. package/src/extension/team-tool/api/mailbox.ts +33 -20
  24. package/src/extension/team-tool/api/plan-approval.ts +5 -5
  25. package/src/extension/team-tool/api/task-claims.ts +8 -7
  26. package/src/extension/team-tool/cancel.ts +6 -0
  27. package/src/extension/team-tool/handle-settings.ts +4 -1
  28. package/src/extension/team-tool/run.ts +3 -7
  29. package/src/extension/team-tool/status.ts +5 -0
  30. package/src/extension/team-tool.ts +6 -14
  31. package/src/hooks/registry.ts +3 -0
  32. package/src/prompt/scratchpad-lifecycle.ts +3 -3
  33. package/src/runtime/background-runner.ts +30 -35
  34. package/src/runtime/broker/crew-broker.ts +112 -441
  35. package/src/runtime/broker/delegate/delegate-event.ts +37 -0
  36. package/src/runtime/broker/mailbox-observer/mailbox-fanout.ts +59 -0
  37. package/src/runtime/broker/protocol/connection-state.ts +103 -0
  38. package/src/runtime/broker/protocol/events-replay.ts +68 -0
  39. package/src/runtime/broker/protocol/manifest-loader.ts +20 -0
  40. package/src/runtime/broker/protocol/msg-inbox.ts +69 -0
  41. package/src/runtime/broker/protocol/request-parsers.ts +175 -0
  42. package/src/runtime/broker/protocol/wait-auth.ts +46 -0
  43. package/src/runtime/child-pi/child-pi.ts +15 -0
  44. package/src/runtime/finalize-run.ts +15 -7
  45. package/src/runtime/foreground-control.ts +19 -6
  46. package/src/runtime/goal-workflow/dynamic-workflow-context.ts +6 -0
  47. package/src/runtime/goal-workflow/dynamic-workflow-runner.ts +3 -0
  48. package/src/runtime/goal-workflow/goal-loop-runner.ts +29 -27
  49. package/src/runtime/goal-workflow/goal-state-store.ts +3 -0
  50. package/src/runtime/heartbeat/heartbeat-watcher.ts +3 -3
  51. package/src/runtime/model/pi-args.ts +6 -1
  52. package/src/runtime/plan-replan.ts +3 -0
  53. package/src/runtime/stale-reconciler.ts +28 -3
  54. package/src/runtime/supervisor-contact.ts +3 -0
  55. package/src/runtime/task-runner/child-executor.ts +33 -0
  56. package/src/runtime/team-runner.ts +3 -3
  57. package/src/state/stores/ownership-map.ts +5 -4
  58. package/src/state/stores/plan-store.ts +12 -0
  59. package/src/state/stores/state-store.ts +5 -0
  60. package/src/ui/powerbar-publisher.ts +3 -7
  61. package/src/ui/run-action-dispatcher.ts +7 -10
  62. package/src/ui/settings-overlay.ts +4 -1
@@ -2,7 +2,7 @@
2
2
 
3
3
  `pi-crew` is a Pi package for coordinated multi-agent work. It is intentionally durable-first: every run is represented on disk, every task has a state record, and child workers stream progress into JSONL/status files so foreground sessions, background jobs, dashboards, and later restarts all read the same source of truth.
4
4
 
5
- **Current version:** v0.9.0 — 100+ rounds of code review hardening (see [CHANGELOG.md](../CHANGELOG.md)).
5
+ **Current version:** v0.10.3 — 100+ rounds of code review hardening (see [CHANGELOG.md](../CHANGELOG.md)).
6
6
 
7
7
  ## Layers
8
8
 
@@ -79,11 +79,11 @@ The extension layer should remain thin: user input is normalized into tool param
79
79
 
80
80
  ### Task runner
81
81
 
82
- `src/runtime/task-runner.ts` executes one task. It prepares workspace/worktree context, renders a task prompt, chooses model candidates from Pi configuration, launches a child Pi process by default, and writes result artifacts. Scaffold mode is explicit dry-run only.
82
+ `src/runtime/task-runner.ts` (thin entry, ~230 lines) executes one task; branch bodies live in `src/runtime/task-runner/` modules (`pre-execution.ts`, `child-executor.ts`, `post-execution.ts`, `live-executor.ts`, `scaffold-executor.ts`, …). It prepares workspace/worktree context, renders a task prompt, chooses model candidates from Pi configuration, launches a child Pi process by default, and writes result artifacts. Scaffold mode is explicit dry-run only.
83
83
 
84
84
  ### Child Pi runtime
85
85
 
86
- `src/runtime/child-pi.ts` is the default worker runtime. It:
86
+ `src/runtime/child-pi/child-pi.ts` is the default worker runtime (split into the `src/runtime/child-pi/` module — `child-pi-spawn.ts`, `child-pi-streams.ts`, `child-pi-kill.ts`, `child-pi-steering.ts`, `child-pi-timers.ts`, `child-pi-transcript.ts`, `child-pi-constants.ts`). It:
87
87
 
88
88
  - launches real `pi` child processes,
89
89
  - hides Windows console windows with `windowsHide: true`,
@@ -98,7 +98,7 @@ The extension layer should remain thin: user input is normalized into tool param
98
98
 
99
99
  ### Concurrency and policy
100
100
 
101
- `src/runtime/concurrency.ts` picks batch size from explicit limits, team settings, workflow settings, or built-in defaults. User-provided `limits.maxConcurrentWorkers` is hard-capped by default to prevent local DoS; `limits.allowUnboundedConcurrency=true` is an explicit opt-out and emits an observability event.
101
+ `src/runtime/scheduling/concurrency.ts` picks batch size from explicit limits, team settings, workflow settings, or built-in defaults. User-provided `limits.maxConcurrentWorkers` is hard-capped by default to prevent local DoS; `limits.allowUnboundedConcurrency=true` is an explicit opt-out and emits an observability event.
102
102
 
103
103
  `src/runtime/policy-engine.ts` applies closeout and safety policy decisions such as limit exceeded, failed task blocking, stale workers, and green-contract failures.
104
104
 
@@ -41,6 +41,9 @@ Slash commands are manual actions triggered from the Pi chat. Autonomous tool us
41
41
  | `/team-mascot` | Toggle the mascot overlay |
42
42
  | **`/team-goal`** | **v0.9.0** Start autonomous goal loop (sub-actions: `start/status/pause/resume/stop/step/clear`) |
43
43
  | **`/workflows`** | **v0.9.0** List static + dynamic workflows (`.dwf.ts`) |
44
+ | `/crew-view <runId> <taskId>` | Open an agent's live full-screen transcript view (thin alias for the inline-panel pane wiring; never switches sessions) |
45
+ | `/crew-back` | Close the agent transcript view and return to the main conversation |
46
+ | `/team-vibes [on\|off\|speed on\|off\|capacity on\|off]` | Toggle crew-vibes speed + context meters (on/off, speed, capacity) |
44
47
 
45
48
  **Removed (v0.10.1 docs hygiene):** `/team-orchestrate`, `/team-schedule`,
46
49
  `/team-scheduled`, `/team-search`, `/team-graph` — phantom entries; none of
@@ -31,7 +31,19 @@ of the full ~6,500-test `npm run check` which takes minutes):
31
31
  npm run test:critical && npm run typecheck && npm run build:bundle
32
32
  ```
33
33
 
34
- 4. Verify package contents:
34
+ 4. Verify dist is clean and current ("dist clean check", WI-1.3):
35
+
36
+ ```bash
37
+ git status --short dist/ # must print NOTHING (clean)
38
+ node scripts/check-bundle-staleness.mjs --committed-hash # must exit 0
39
+ ```
40
+
41
+ A dirty or stale committed dist silently ships old code (the v0.9.x
42
+ stale-bundle incident class). If either check fails: `npm run build:bundle`,
43
+ then commit the bundle — `git add -f dist/ && git commit -- dist`
44
+ (dist/ is gitignored, so `-f` is required).
45
+
46
+ 5. Verify package contents:
35
47
 
36
48
  ```bash
37
49
  npm pack --dry-run
@@ -44,7 +56,7 @@ Confirm bundled skills ship (the `real-test-pi-crew` skill references
44
56
  npm pack --dry-run 2>&1 | grep -E 'skills/|pty_probe'
45
57
  ```
46
58
 
47
- 5. Verify local install in Pi:
59
+ 6. Verify local install in Pi:
48
60
 
49
61
  ```bash
50
62
  pi install ./pi-crew
@@ -52,7 +64,7 @@ pi install ./pi-crew
52
64
  /team-validate
53
65
  ```
54
66
 
55
- 6. Publish when ready:
67
+ 7. Publish when ready:
56
68
 
57
69
  ```bash
58
70
  npm publish --access public
package/install.mjs CHANGED
@@ -2,13 +2,32 @@
2
2
  import * as fs from "node:fs";
3
3
  import * as os from "node:os";
4
4
  import * as path from "node:path";
5
+ import { pathToFileURL } from "node:url";
5
6
 
6
7
  const home = process.env.PI_TEAMS_HOME?.trim() || os.homedir();
7
8
  const agentDir = path.join(home, ".pi", "agent");
8
9
  const configPath = path.join(agentDir, "pi-crew.json");
9
10
  const legacyConfigPath = path.join(agentDir, "extensions", "pi-crew", "config.json");
10
- const defaultConfig = {
11
- // Keep generated config non-invasive: runtime/limits use pi-crew internal defaults.
11
+
12
+ /**
13
+ * Default config written to `~/.pi/agent/pi-crew.json` on fresh installs.
14
+ *
15
+ * WI-1b.2 (G17): this object is intentionally an embedded literal — install.mjs
16
+ * runs under bare `node` as the `pi-crew` bin (engines floor is >=22.0.0; .ts
17
+ * imports would require >=22.18 or `--experimental-strip-types`, which the npm
18
+ * bin shim cannot pass). Runtime jiti/esbuild loading was rejected as a
19
+ * postinstall failure surface; build-time codegen would need package.json /
20
+ * prepack wiring. Instead, drift is enforced by
21
+ * `test/unit/install-defaults-sync.test.ts`, which derives the source of truth
22
+ * from `src/config/defaults.ts` (DEFAULT_UI) and
23
+ * `src/config/config-validation.ts` (effectiveAutonomousConfig) and goes red
24
+ * the moment this literal diverges (mutation-demo verified).
25
+ *
26
+ * Keys mirror the curated subset in `src/extension/project-init.ts`
27
+ * (DEFAULT_PI_CREW_CONFIG) — deliberately non-invasive: runtime/limits use
28
+ * pi-crew internal defaults and are not pinned here.
29
+ */
30
+ export const defaultConfig = {
12
31
  autonomous: {
13
32
  enabled: true,
14
33
  injectPolicy: true,
@@ -30,7 +49,9 @@ const defaultConfig = {
30
49
  }
31
50
  },
32
51
  ui: {
33
- widgetPlacement: "aboveEditor",
52
+ // G17 drift fix (2026-09): was "aboveEditor" while DEFAULT_UI.widgetPlacement
53
+ // is "bottom" — fresh installs were pinning a stale default into user config.
54
+ widgetPlacement: "bottom",
34
55
  widgetMaxLines: 8,
35
56
  powerbar: true,
36
57
  dashboardPlacement: "center",
@@ -44,44 +65,74 @@ const defaultConfig = {
44
65
  }
45
66
  };
46
67
 
47
- fs.mkdirSync(agentDir, { recursive: true });
48
- if (!fs.existsSync(configPath)) {
49
- if (fs.existsSync(legacyConfigPath)) {
50
- fs.copyFileSync(legacyConfigPath, configPath);
51
- console.log(`Migrated pi-crew global config to: ${configPath}`);
68
+ /**
69
+ * Postinstall / bin behavior: create the agent dir, migrate or write the
70
+ * default global config, and print install + uninstall guidance.
71
+ * Kept side-effect-for-side-effect identical to the pre-refactor top-level
72
+ * script (WI-1b.2 refactor only moved it behind a main-guard).
73
+ */
74
+ export function main() {
75
+ fs.mkdirSync(agentDir, { recursive: true });
76
+ if (!fs.existsSync(configPath)) {
77
+ if (fs.existsSync(legacyConfigPath)) {
78
+ fs.copyFileSync(legacyConfigPath, configPath);
79
+ console.log(`Migrated pi-crew global config to: ${configPath}`);
80
+ } else {
81
+ fs.writeFileSync(configPath, `${JSON.stringify(defaultConfig, null, 2)}\n`, "utf-8");
82
+ console.log(`Created default pi-crew global config: ${configPath}`);
83
+ }
52
84
  } else {
53
- fs.writeFileSync(configPath, `${JSON.stringify(defaultConfig, null, 2)}\n`, "utf-8");
54
- console.log(`Created default pi-crew global config: ${configPath}`);
85
+ console.log(`pi-crew global config already exists: ${configPath}`);
55
86
  }
56
- } else {
57
- console.log(`pi-crew global config already exists: ${configPath}`);
87
+
88
+ console.log("\nInstall the published package in Pi with:");
89
+ console.log(" pi install npm:pi-crew");
90
+ console.log("\nFor local development from a cloned repo:");
91
+ console.log(" pi install .");
92
+ console.log("\nChild workers are enabled by default. For dry runs, set runtime.mode=scaffold or executeWorkers=false.");
93
+ console.log("To force-disable or force-enable workers in a shell, use PI_TEAMS_EXECUTE_WORKERS=0/1.");
94
+
95
+ // Side-effects warning (Issue #35): be upfront about what pi-crew writes and
96
+ // how to fully uninstall it. Nothing runs on install/registration itself; the
97
+ // writes below only happen when you explicitly invoke `team action=init`.
98
+ console.log("\n--- What pi-crew writes (and how to undo it) ---");
99
+ console.log("pi-crew itself writes nothing on install. The following only happens when you");
100
+ console.log("explicitly run `team action=init` in a project:");
101
+ console.log(" - A `.crew/` runtime state dir is created in the project (run history + artifacts).");
102
+ console.log(" - With --copy-builtins: bundled agents/teams/workflows are copied into the project.");
103
+ console.log("This install also created the global config above (`~/.pi/agent/pi-crew.json`).");
104
+ console.log("Note: pi-crew v0.8.14+ no longer injects a guidance block into AGENTS.md on init");
105
+ console.log(" (it was redundant — the `team` tool self-describes via tool registration).");
106
+ console.log(" Versions <0.8.14 did inject one; `team action=cleanup` removes it.");
107
+ console.log("\nFull uninstall (in order):");
108
+ console.log(" team action=cleanup dryRun=true # preview what would be removed (project)");
109
+ console.log(" team action=cleanup # remove the AGENTS.md guidance block");
110
+ console.log(" team action=cleanup force=true # also remove the .crew/ project state dir");
111
+ console.log(" team action=cleanup scope=user # remove pi-crew user-scope junk");
112
+ console.log(" # (~/.pi/agent/extensions/pi-crew/ + test .bak files)");
113
+ console.log(" team action=cleanup scope=user force=true # also remove ~/.pi/agent/pi-crew.json");
114
+ console.log(" pi uninstall npm:pi-crew # remove the package itself");
115
+ console.log("See the README 'Uninstall' section for details.");
58
116
  }
59
117
 
60
- console.log("\nInstall the published package in Pi with:");
61
- console.log(" pi install npm:pi-crew");
62
- console.log("\nFor local development from a cloned repo:");
63
- console.log(" pi install .");
64
- console.log("\nChild workers are enabled by default. For dry runs, set runtime.mode=scaffold or executeWorkers=false.");
65
- console.log("To force-disable or force-enable workers in a shell, use PI_TEAMS_EXECUTE_WORKERS=0/1.");
118
+ /**
119
+ * Main-guard: run main() only when this file is executed directly
120
+ * (`node install.mjs` or via the npm `pi-crew` bin shim), never when imported
121
+ * as a module (e.g. by test/unit/install-defaults-sync.test.ts).
122
+ * realpathSync both sides: import.meta.url is the realpath (Node resolves
123
+ * symlinks by default), while argv[1] may be a symlinked bin path — e.g. the
124
+ * dev-install layout where node_modules/pi-crew symlinks into a source clone.
125
+ */
126
+ function invokedDirectly() {
127
+ const invoked = process.argv[1];
128
+ if (!invoked) return false;
129
+ try {
130
+ return import.meta.url === pathToFileURL(fs.realpathSync(invoked)).href;
131
+ } catch {
132
+ return false;
133
+ }
134
+ }
66
135
 
67
- // Side-effects warning (Issue #35): be upfront about what pi-crew writes and
68
- // how to fully uninstall it. Nothing runs on install/registration itself; the
69
- // writes below only happen when you explicitly invoke `team action=init`.
70
- console.log("\n--- What pi-crew writes (and how to undo it) ---");
71
- console.log("pi-crew itself writes nothing on install. The following only happens when you");
72
- console.log("explicitly run `team action=init` in a project:");
73
- console.log(" - A `.crew/` runtime state dir is created in the project (run history + artifacts).");
74
- console.log(" - With --copy-builtins: bundled agents/teams/workflows are copied into the project.");
75
- console.log("This install also created the global config above (`~/.pi/agent/pi-crew.json`).");
76
- console.log("Note: pi-crew v0.8.14+ no longer injects a guidance block into AGENTS.md on init");
77
- console.log(" (it was redundant — the `team` tool self-describes via tool registration).");
78
- console.log(" Versions <0.8.14 did inject one; `team action=cleanup` removes it.");
79
- console.log("\nFull uninstall (in order):");
80
- console.log(" team action=cleanup dryRun=true # preview what would be removed (project)");
81
- console.log(" team action=cleanup # remove the AGENTS.md guidance block");
82
- console.log(" team action=cleanup force=true # also remove the .crew/ project state dir");
83
- console.log(" team action=cleanup scope=user # remove pi-crew user-scope junk");
84
- console.log(" # (~/.pi/agent/extensions/pi-crew/ + test .bak files)");
85
- console.log(" team action=cleanup scope=user force=true # also remove ~/.pi/agent/pi-crew.json");
86
- console.log(" pi uninstall npm:pi-crew # remove the package itself");
87
- console.log("See the README 'Uninstall' section for details.");
136
+ if (invokedDirectly()) {
137
+ main();
138
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-crew",
3
- "version": "0.10.3",
3
+ "version": "0.10.4",
4
4
  "description": "Pi extension for coordinated AI teams, workflows, worktrees, and async task orchestration",
5
5
  "author": "baphuongna",
6
6
  "license": "MIT",
@@ -68,7 +68,8 @@
68
68
  ],
69
69
  "scripts": {
70
70
  "check": "npm run ci",
71
- "ci": "npm run check:lockfile-sync && npm run typecheck && npm run lint && npm run format:check && npm run check:conflict-markers && npm run check:decision-drift && npm run check:env-vars && npm run check:event-types && npm run check:lazy-imports && npm run check:bundle-staleness && npm run build:bundle && npm run check:bundle-size && npm run test:bundle && npm test && npm pack --dry-run",
71
+ "ci": "npm run check:lockfile-sync && npm run typecheck && npm run lint && npm run format:check && npm run check:conflict-markers && npm run check:decision-drift && npm run check:env-vars && npm run check:event-types && npm run check:lazy-imports && npm run check:wc-gate && npm run check:bundle-staleness && npm run build:bundle && npm run check:bundle-size && npm run test:bundle && npm test && npm pack --dry-run",
72
+ "ci:fast": "npm run check:lockfile-sync && npm run typecheck && npm run lint && npm run format:check && npm run check:conflict-markers && npm run check:decision-drift && npm run check:env-vars && npm run check:event-types && npm run check:lazy-imports && npm run check:wc-gate && npm run check:bundle-staleness && npm run build:bundle && npm run check:bundle-size && npm run test:bundle && npm run test:critical && npm pack --dry-run",
72
73
  "check:lockfile-sync": "node scripts/check-lockfile-sync.mjs",
73
74
  "check:lazy-imports": "node scripts/check-lazy-imports.mjs",
74
75
  "check:bundle-staleness": "node scripts/check-bundle-staleness.mjs",
@@ -81,10 +82,12 @@
81
82
  "lint": "biome check --linter-enabled=true --formatter-enabled=false --max-diagnostics=50 .",
82
83
  "format:check": "biome format .",
83
84
  "test": "npm run test:unit && npm run test:integration",
85
+ "test:full": "npm run test:unit && npm run test:integration && npm run test:integration:slow",
84
86
  "test:unit": "node scripts/test-runner.mjs --test-concurrency=4 --test-timeout=180000 --test-force-exit 'test/unit/**/*.test.ts'",
85
87
  "test:critical": "node scripts/test-runner.mjs --test-concurrency=4 --test-timeout=30000 --test-force-exit test/unit/runtime/broker/crew-broker-handshake.test.ts test/unit/runtime/broker/crew-broker-stale-socket.test.ts test/unit/runtime/broker/crew-broker-feature-flag.test.ts test/unit/runtime/broker/crew-broker-server-gate.test.ts test/unit/runtime/broker/crew-broker-client-fallback.test.ts test/unit/runtime/broker/crew-broker-mailbox-observer.test.ts test/unit/runtime/broker/crew-broker-close-during-reconnect.test.ts test/unit/runtime/broker/crew-broker-steer-dedup.test.ts test/unit/runtime/broker/crew-broker-symlink-steering.test.ts test/unit/ui/keybinding-map.parity.test.ts test/unit/ui/pi-tui-dispatch-probe.test.ts test/unit/utils/session-utils-extract.test.ts test/unit/config/config-schema-sync.test.ts test/unit/runtime/child-pi/child-pi-env-spread.test.ts",
86
88
  "test:watch": "node scripts/test-runner.mjs --watch --test-concurrency=4 --test-timeout=30000 'test/unit/**/*.test.ts'",
87
89
  "test:integration": "node scripts/test-runner.mjs --test-concurrency=1 --test-timeout=300000 test/integration/*.test.ts",
90
+ "test:integration:slow": "node scripts/test-runner.mjs --test-concurrency=1 --test-timeout=600000 test/integration/slow/*.test.ts",
88
91
  "test:system": "node scripts/test-runner.mjs --test-concurrency=1 --test-timeout=180000 --test-force-exit test/system/*.e2e.test.ts",
89
92
  "test:smoke": "node scripts/test-runner.mjs --test-concurrency=1 --test-timeout=180000 test/smoke/*.smoke.ts",
90
93
  "test:spike": "node scripts/test-runner.mjs --test-concurrency=2 --test-timeout=120000 --test-force-exit test/runtime/scratchpad/*.test.ts",
@@ -106,7 +109,9 @@
106
109
  "profile:startup": "node scripts/profile-startup.mjs",
107
110
  "smoke:pi": "pi install .",
108
111
  "smoke:release": "node scripts/release-smoke.mjs",
109
- "prepack": "node scripts/clean-strip-types.mjs"
112
+ "prepack": "node scripts/clean-strip-types.mjs",
113
+ "test:fast": "npm run test:critical && npm run test:bundle",
114
+ "check:wc-gate": "node scripts/wc-gate.mjs"
110
115
  },
111
116
  "exports": {
112
117
  "./schema.json": "./schema.json",
package/scripts/README.md CHANGED
@@ -1,6 +1,7 @@
1
1
  # Scripts
2
2
 
3
- > **Note:** Many scripts in this directory are one-off fix scripts (e.g.,
4
- > `fix-*.cjs`, `patch-*.cjs`) that were used for specific migrations or
5
- > bugfixes and could be archived. Only `postinstall.mjs`, `build-bundle.mjs`,
3
+ > **Note:** Some scripts in this directory are one-off fix/patch scripts
4
+ > (e.g., `patch-*.cjs`) that were used for specific migrations or bugfixes
5
+ > and could be archived. The 14 `fix-*.cjs` one-offs were removed in the
6
+ > 2026-09 M1 hygiene sweep. Only `postinstall.mjs`, `build-bundle.mjs`,
6
7
  > and `watch-bundle.mjs` are actively used by the project.
@@ -37,6 +37,7 @@ This artifact exists so past runs are verifiable (see SKILL.md "Output report").
37
37
  | 10a surface E2E | ✅/❌/⏭️ | tmux 3/3 (spawn+self-close / kill-pane→degrade / doctor orphan) + herdr 3/3 — or skip reason per backend (no `$TMUX`/CI/no socket/in tmux for herdr) |
38
38
  | 10b live surface run | ✅/❌/⏭️ | `visibleAgents='<set>'`, pane ids+titles during run, `worker.surface_spawned`/`worker.surface_closed` events (+ no `surface.degraded`), panes auto-closed. NOTE: `manifest.surface.panes` is `{}` at run END even on success (released on pane close) — engage-evidence is the EVENTS + `manifest.surface.provider`/`workerPids` |
39
39
  | 10c herdr path | ✅/❌/⏭️ | ran in herdr pane / skipped (not in herdr pane) |
40
+ | 11 remediation regression | ✅/❌/⏭️ | sub-checks run + evidence: 11a buffered-site census `<n>` files (full test:unit `<pass>/<tests>` if run) · 11b wc-gate max `<n>`/2000 + in `ci` · 11c validator warn `<key>` → severity · 11d slow tier 3 files disjoint · 11e nightly SMOKE not-set · 11f reject format sites · 11g widgetPlacement bottom · 11h twins 3× green · 11i dead-export 0 + MUST_INCLUDE 5 · 11j committed-hash OK |
40
41
 
41
42
  Legend: ✅ pass with evidence · ❌ fail (root cause below) · ⏭️ skipped (justify why)
42
43
 
@@ -32,14 +32,20 @@ triggers:
32
32
  - "delegate tool test"
33
33
  - "ask tool test"
34
34
  - "nested agent test"
35
- - "tier 1 / tier 2 / tier 3 / tier 4 / tier 5 / tier 6 / tier 7 / tier 8 / tier 9 / tier 10"
35
+ - "tier 1 / tier 2 / tier 3 / tier 4 / tier 5 / tier 6 / tier 7 / tier 8 / tier 9 / tier 10 / tier 11"
36
+ - "read-your-writes"
37
+ - "delayed write regression"
38
+ - "coalesce regression"
39
+ - "wc-gate"
40
+ - "migration validator warning"
41
+ - "slow tier"
36
42
  ---
37
43
 
38
44
  # real-test-pi-crew
39
45
 
40
46
  End-to-end verification discipline for pi-crew changes. Distilled from the broker Phase-4 rollout (commits `1cb2dca` → `d599578` → `612e18b` → `4186284`, July 2026). The pain this skill prevents: shipping code that compiles + unit-tests-green but breaks in the user's live Pi session, or hangs the verifier worker.
41
47
 
42
- **When to use**: after any change to `src/runtime/broker/*.ts` (broker + tokens + issuer), `src/ui/`, `src/config/`, `src/extension/registration/lifecycle-handlers.ts`, `src/runtime/child-pi/*.ts` (worker spawn/kill/steering), `src/runtime/surface/*.ts` (MuxSurface providers, degrade, launch script), `src/prompt/*.ts` (worker-side tools: ask / message / delegate / surface-worker recorder), `src/runtime/goal-workflow/plan-templates.ts`, `src/runtime/team-runner.ts` or `src/runtime/task-runner/**` (scheduler / execution — Tier 7 smoke), `src/state/**` (durable state — Tier 7 + 9a events/status), `src/runtime/live-session/**` + `src/runtime/custom-tools/*` (live-session mode + worker custom tools), `src/schema/team-tool-schema.ts` (or any `Type.Unsafe({...})` schema definition), `src/extension/registration/team-tool.ts`, `workflows/*.workflow.md`, or before any commit touching these paths. Schema changes additionally require Tier 9 (feature battery) because the team tool's TypeBox schema is validated by pi-ai BEFORE the handler runs — a too-strict or malformed schema breaks every action silently. Surface changes additionally require Tier 10 (surface-mode battery) because surface is fail-closed: every failure degrades to headless and the run still goes green — only pane-level evidence proves the panes engaged.
48
+ **When to use**: after any change to `src/runtime/broker/*.ts` (broker + tokens + issuer), `src/ui/`, `src/config/` (incl. `src/config/migration-validator.ts`), `src/extension/registration/lifecycle-handlers.ts`, `src/runtime/child-pi/*.ts` (worker spawn/kill/steering), `src/runtime/surface/*.ts` (MuxSurface providers, degrade, launch script), `src/prompt/*.ts` (worker-side tools: ask / message / delegate / surface-worker recorder), `src/runtime/goal-workflow/plan-templates.ts`, `src/runtime/team-runner.ts` or `src/runtime/task-runner/**` (scheduler / execution — Tier 7 smoke), `src/state/**` (durable state — Tier 7 + 9a events/status + **Tier 11a read-your-writes**), `src/runtime/live-session/**` + `src/runtime/custom-tools/*` (live-session mode + worker custom tools), `src/schema/team-tool-schema.ts` (or any `Type.Unsafe({...})` schema definition), `src/extension/registration/team-tool.ts`, `workflows/*.workflow.md`, `.github/workflows/*.yml` (CI env — Tier 11e), `scripts/wc-gate.mjs` (Tier 11b), or before any commit touching these paths. Schema changes additionally require Tier 9 (feature battery) because the team tool's TypeBox schema is validated by pi-ai BEFORE the handler runs — a too-strict or malformed schema breaks every action silently. Surface changes additionally require Tier 10 (surface-mode battery) because surface is fail-closed: every failure degrades to headless and the run still goes green — only pane-level evidence proves the panes engaged.
43
49
 
44
50
  > **Path map (2026-08-26 reorg + A1)**: `src/runtime/crew-broker*.ts` → `src/runtime/broker/`; `src/runtime/child-pi*.ts` → `src/runtime/child-pi/`; `src/runtime/plan-templates.ts` (flat) → `src/runtime/goal-workflow/plan-templates.ts`; NEW dirs `src/runtime/surface/` and `src/prompt/`. Test files moved with them (`test/unit/crew-broker-*.test.ts` → `test/unit/runtime/broker/`, `test/unit/keybinding-map.parity.test.ts` → `test/unit/ui/`, ...).
45
51
 
@@ -95,9 +101,10 @@ The skill maps to existing CI gates as follows:
95
101
  | `npm test:critical` (manual / pre-commit) | Tier 1 | n/a — not in CI by default |
96
102
  | `PI_CREW_BROKER=0 npm run test:critical` | Tier 2 (env kill switch path) | n/a — manual |
97
103
  | `npm run typecheck` | Tier 3 | `.github/workflows/*.yml` (every PR) |
98
- | Bundle-staleness check | Tier 3 last step | `scripts/check-bundle-staleness.mjs` |
99
- | Multi-OS CI | n/a (skill is local) | `.github/workflows/*.yml` — Linux + macOS + Windows |
100
- | Full `npm test` (>5 min) | n/a — too slow for in-loop | CI only |
104
+ | `npm run check:wc-gate` | Tier 11b | **in BOTH `ci` and `ci:fast` scripts** (`package.json:71-72`) + explicit step in `.github/workflows/ci.yml:66-71` (since `09dda842` — was `ci:fast`-only, i.e. advisory) |
105
+ | Bundle-staleness check | Tier 3 last step | `scripts/check-bundle-staleness.mjs`; `--committed-hash` mode = Tier 11j release gate |
106
+ | Full `npm test` (= unit 819 files + integration 31) | n/a — too slow for in-loop | CI only; slow tier (3 files) is a SEPARATE glob `test:integration:slow` — only `npm run test:full` includes it |
107
+ | `PI_CREW_SMOKE=1` env | Tier 11e | set ONLY in `weekly-smoke.yml` (auth-gated); nightly.yml deliberately does NOT (comment at `:24`) |
101
108
 
102
109
  To add Tier 1 to a pre-commit hook:
103
110
 
@@ -127,7 +134,7 @@ To add Tier 1 to CI as a fast-feedback gate (under 30s):
127
134
 
128
135
  **What**: run the curated 14-file fast subset.
129
136
 
130
- **Why this exists**: full `npm run test:unit` runs 810 files (was 642 at skill-writing time — it keeps growing), several minutes. Verifier worker response timeout would kill the worker mid-run → run = "hang". The fix (introduced in commit `1cb2dca`) splits out a `test:critical` subset covering exactly what changed in the broker/UI work.
137
+ **Why this exists**: full `npm run test:unit` runs 819 files (was 642 at skill-writing time — it keeps growing), several minutes. Verifier worker response timeout would kill the worker mid-run → run = "hang". The fix (introduced in commit `1cb2dca`) splits out a `test:critical` subset covering exactly what changed in the broker/UI work.
131
138
 
132
139
  **How**:
133
140
 
@@ -591,6 +598,114 @@ herdr chỉ được detect khi **chính pi session đang chạy trong một her
591
598
 
592
599
  ---
593
600
 
601
+ ## Tier 11 — Remediation regression battery (v0.10.5 deep-review fixes)
602
+
603
+ **What**: verify the v0.10.5 remediation invariants hold — the P0 read-your-writes revert (`b6eba80f`), the P1 enforcement/wiring hardening (`09dda842`), the broker doc-nit de-stack (`4ebd2ce4`), and the CI/test-tier reshuffle (`09dda842` + `2fb2b426`).
604
+
605
+ **Why this is its own tier**: the remediation fixed bugs that ALL of Tiers 1–10 missed — 18 hidden test failures from a delayed-write conversion (test:critical contains no stores/dwf/recovery tests), a production default-drift (`ui.widgetPlacement`), a deterministic-red CI env (`PI_CREW_SMOKE=1` in nightly), and a gate that existed but was never enforced (wc-gate). These need their own pin checks so the same classes don't regress.
606
+
607
+ **When required**: any change to `src/state/**` write paths (stores, atomic-write, event-log buffering), `src/config/migration-validator.ts` or its wiring, `scripts/wc-gate.mjs` or the `ci` scripts, `.github/workflows/*` env, `src/ui/settings-overlay.ts` / `handle-settings.ts` EFFECTIVE_DEFAULTS, or before a release cut.
608
+
609
+ ### 11a. Read-your-writes (the P0 revert core)
610
+
611
+ **The rule (hard-won, `b6eba80f`)**: MỌI site có reader đồng bộ ngay sau write — test read-your-writes assertion, same-poll display, reload-inside-lock, cross-process reader — KHÔNG được convert sang buffered/coalesced, KỂ CẢ terminal-type buffered (flushPromise.then microtask chain ≠ same-tick). The WI-2.2 coalesce ("last value wins" + 50ms window) broke 3 public sync APIs: `plan-store loadPlanRecords`, `readOwnershipMap`, `loadRunManifestById` → 18 hidden test failures (plan-store 11, ownership-map 3, state-store 4).
612
+
613
+ ```bash
614
+ # 1. stores stay on sync atomicWriteJson (coalesce reverted):
615
+ grep -c "atomicWriteJson" src/state/stores/plan-store.ts src/state/stores/ownership-map.ts # >=1 each
616
+ # 2. terminal-state events are sync appendEvent (crash-recovery design comment at :221
617
+ # (file lives at src/runtime/recovery/crash-recovery.ts after the runtime reorg)
618
+ grep -n "Log the event first" src/runtime/recovery/crash-recovery.ts # design intent: sync
619
+ # 3. buffered-site census — snapshot & audit:
620
+ grep -rln "appendEventBuffered" src/ | wc -l # 16 files / ~70 raw matches (incl. imports+definition) at v0.10.5; audited live conversions = 43; EVERY new site needs the reader-audit
621
+ # 4. the full gate — test:critical has NO stores/dwf/recovery coverage:
622
+ npm run test:unit # 819 files, ~7500 tests, 15-18 min under load — MANDATORY after any delayed-write conversion program
623
+ ```
624
+
625
+ ### 11b. wc-gate enforcement (M4 done-gate)
626
+
627
+ ```bash
628
+ npm run check:wc-gate # exit 0, "max NNNN lines (limit 2000)"
629
+ node -e "const p=require('./package.json').scripts; console.log(p.ci.includes('check:wc-gate'), p['ci:fast'].includes('check:wc-gate'))" # true true
630
+ grep -n "wc-gate" .github/workflows/ci.yml # explicit step (since 09dda842; was ci:fast-only = advisory)
631
+ ```
632
+
633
+ ### 11c. Migration validator (M5 WI-5.6, warn-only)
634
+
635
+ Wired in `register.ts:62-74` — AFTER `installChildProcessAbortShield`, BEFORE `startRuntimeWarmup`; `console.warn`, never throws (spec: "warning không fail").
636
+
637
+ ```bash
638
+ # offline (no pi session needed):
639
+ node --experimental-strip-types --no-warnings -e \
640
+ 'import("./src/config/migration-validator.ts").then(m=>console.log(JSON.stringify(m.validateEnv({PI_CREW_BROKER_DIAG_UI:"1",PI_CREW_SAFE_BASH:"1"}))))'
641
+ # expect: warnings[] with severity "removed" for BOTH keys; hasWarnings true
642
+ # live: PI_CREW_BROKER_DIAG_UI=1 pi … → startup prints "[pi-crew] 1 deprecated env var(s) in use:" and boots normally
643
+ ```
644
+
645
+ ### 11d. Slow-tier hygiene (M3 tiering)
646
+
647
+ ```bash
648
+ ls test/integration/slow/ # exactly 3: full-feature-smoke, phase5-observability, ui-performance
649
+ # the fast globs must NOT match slow/ (disjoint globs — test:full dup was a real bug):
650
+ grep -o "'test/unit/\*\*/\*.test.ts'\|'test/integration/\*.test.ts'" package.json
651
+ npm run test:integration:slow # separate glob, 600s timeout
652
+ ```
653
+
654
+ ### 11e. Nightly env regression (deterministic-red trap)
655
+
656
+ `PI_CREW_SMOKE=1` arms HB-003a real-binary smoke which needs a pi binary + `PI_AUTH_JSON` — GH runners have neither → deterministic red. It is set ONLY in `weekly-smoke.yml` (auth-gated arm).
657
+
658
+ ```bash
659
+ grep -n "PI_CREW_SMOKE" .github/workflows/nightly.yml # comment ONLY (":24 deliberately NOT setting")
660
+ grep -n "PI_CREW_SMOKE" .github/workflows/weekly-smoke.yml # PI_CREW_SMOKE: "1" (:25)
661
+ ```
662
+
663
+ ### 11f. Event-log reject format
664
+
665
+ Buffered-append rejections must carry `type=<event-type>[<distinguisher>]` so ops can bisect a failed append to its event kind:
666
+
667
+ ```bash
668
+ grep -rn 'type=\${' src/prompt/scratchpad-lifecycle.ts src/runtime/finalize-run.ts
669
+ # scratchpad-lifecycle:92 type=${type}
670
+ # finalize-run:242 ternary escalate/policy.action · :260 recovery.escalated/recovery.attempted
671
+ ```
672
+
673
+ ### 11g. widgetPlacement G17 default-drift
674
+
675
+ The drift: duplicated EFFECTIVE_DEFAULTS maps hardcoded `"aboveEditor"` while `defaults.ts`/`install.mjs`/`project-init` said `"bottom"` — the suite never compared them. NOTE `aboveEditor` remains a VALID enum value (schema/types/pi-widget mapping); only the DEFAULT was wrong.
676
+
677
+ ```bash
678
+ grep -rn '"ui.widgetPlacement"' src/ui/settings-overlay.ts src/extension/team-tool/handle-settings.ts # both :"bottom"
679
+ # live: team-settings get ui.widgetPlacement → bottom
680
+ ```
681
+
682
+ ### 11h. Worktree-twins stability (flaky→fixed)
683
+
684
+ Pre-fix flaked ~8% idle / ~50% under load (`try{return promise}finally{rmSync}` cleanup race); fixed by awaiting INSIDE the try. Run 3× consecutively — all green:
685
+
686
+ ```bash
687
+ for i in 1 2 3; do node --experimental-strip-types --no-warnings --test test/unit/worktree/worktree-twins-contract.test.ts 2>&1 | grep -E "^# (pass|fail)"; done
688
+ ```
689
+
690
+ ### 11i. Export-surface + slash parity pinning
691
+
692
+ ```bash
693
+ grep -nE "export.*(HELLO_DEADLINE_MS)|export \{ BROKER_PROTOCOL" src/runtime/broker/crew-broker.ts # 0 hits (dead exports removed, 09dda842)
694
+ grep -n "MUST_INCLUDE" test/unit/extension/slash-command-parity.test.ts # 5 core: team-run, teams, team-help, crew-view, crew-brief
695
+ ```
696
+
697
+ ### 11j. Bundle committed-hash (release gate)
698
+
699
+ Tier 3 checks disk-vs-session; this checks COMMITTED dist vs a fresh build — the gate that catches "src edited, bundle forgot":
700
+
701
+ ```bash
702
+ node scripts/check-bundle-staleness.mjs --committed-hash # "OK: committed dist matches a fresh build"
703
+ ```
704
+
705
+ **Acceptance**: every sub-check above returns the expected value; 11a item 4 is the ONLY expensive one (mandatory after conversion programs, skippable for doc-only changes).
706
+
707
+ ---
708
+
594
709
  ## Anti-patterns (the cost is real, observed in this session)
595
710
 
596
711
  | Anti-pattern | Cost | Where fixed | Reference |
@@ -619,6 +734,12 @@ herdr chỉ được detect khi **chính pi session đang chạy trong một her
619
734
  | chain run with `workflow:"chain"` forwarded to steps | Every chain step fails in ~58ms with an EMPTY error string — looks like a parse failure but isn't. `chain-dispatch` forwards `params.workflow` ("chain") into executor overrides; each step then runs the "chain" workflow via the normal `executeTeamRun` path and fails fast + silently. | Open (issue #44) | Omit `workflow` when invoking `action:'run' chain=...` — chain then runs 2/2 success (~308s). See `docs/bugs/chain-workflow-forward-quirk.md`. |
620
735
  | **Env allow-list strip mux vars — async surface chết ở tầng env, không phải tầng gate** (battery 2026-08-30 Finding 2): gate async-run đã bỏ nhưng `BACKGROUND_RUNNER_ENV_ALLOWLIST` vẫn strip `TMUX`/`HERDR_*` → detached runner thấy `no-mux` → async headless mãi mãi. Gate telemetry (`asyncRun:true` trong env snapshot) nói đúng — không gate async — nhưng env detection fail vì biến bị cắt trước khi process chào. Unit test allow-list không catch (list "đúng" theo nghĩa cũ); chỉ async run LIVE với mux mới lộ. | `f0a41a16` (2026-08-30) | Mọi env var mà `src/runtime/surface/*` đọc phải có trong `BACKGROUND_RUNNER_ENV_ALLOWLIST` (pin test `test/unit/runtime/core/async-runner.test.ts` "forwards mux env"). Thêm env detection mới → thêm vào allow-list + pin test cùng lúc. |
621
736
  | **`set <array-key> []` là no-op** (battery 2026-08-30 Finding 3): `parseStringList` normalize `[]` → `undefined` → patch mất key → `mergeConfig` giữ list cũ trên đĩa; `Effective` hiển thị sai giá trị đã set. `unset` vẫn hoạt động (workaround). | `5a31ccf6` (2026-08-30) | `[]` tường minh là GIÁ TRỊ, không phải unset. Test round-trip: set → get → soi config trên đĩa (test/unit/config/surface-config.test.ts F3 block). |
737
+ | **Convert write-site sang buffered/coalesced khi CÓ reader đồng bộ ngay sau write** (P0 remediation 2026-09-10, `b6eba80f`): 29 site bị revert. WI-2.2 coalesce ("last value wins" + 50ms window) làm hỏng 3 public sync APIs (plan-store `loadPlanRecords`, `readOwnershipMap`, `loadRunManifestById`) → 18 hidden test failures mà test:critical KHÔNG bắt (không chứa stores/dwf/recovery). Kể cả terminal-type `appendEventBuffered` vẫn qua `flushPromise.then(...)` microtask chain → KHÔNG same-tick → cancel.ts task.cancelled phải revert về sync. | `b6eba80f` | **Read-your-writes exclusion rule**: reader-after-write = never convert (any flush-latency > 0 breaks the contract). Sau MỌI conversion program: full `npm run test:unit` bắt buộc. Xem Tier 11a. |
738
+ | **Gate tồn tại nhưng không được enforce** (P1 remediation, `09dda842`): wc-gate wired chỉ vào `ci:fast` — full `ci` script và tất cả GitHub workflows không chạy nó → một commit phình crew-broker.ts quá 2000 dòng vẫn PR-green. "Có script check" ≠ "gate được enforce". | `09dda842` | Gate mới phải vào: (1) script `ci`, (2) workflow yml step, (3) done-criteria của skill. Xem Tier 11b. |
739
+ | **Set CI env cho arm không có dependency của nó** (P0/P1 remediation): `PI_CREW_SMOKE=1` trong nightly.yml arm HB-003a cần pi binary + `PI_AUTH_JSON` — GH runner không có → deterministic red, không phải flake. | `b6eba80f` | Mỗi env var CI: liệt kê dependency (binary/auth/socket) trước khi set; arm auth-gated (weekly-smoke) mới được set. Xem Tier 11e. |
740
+ | **Fix finding của reviewer mà không tự verify** (deep review 2026-09-10): 1 trong 4 HIGH findings là false positive — "background-runner exit-loss" thực tế được cover bởi EL-2 `flushBufferedQueuesSync()` (sync lock + appendFileSync + fsync) trên `process.on("exit")` tại event-log.ts:1227. Fix theo finding mù quáng sẽ ĐÃ THÊM regression. | n/a (process) | Mọi finding trước khi fix: trace counter-evidence (exit handlers, sync flush paths). Finding = hypothesis, không phải fact. |
741
+ | **Duplicated defaults map drift (G17-class)** (P0 remediation, `b6eba80f`): 2 bản EFFECTIVE_DEFAULTS (`settings-overlay.ts`, `handle-settings.ts`) hardcode `"aboveEditor"` trong khi nguồn chân lý (defaults.ts/install.mjs) nói `"bottom"` — suite không có test so 2 bản với nhau, drift sống sót qua 7500 tests. | `b6eba80f` | Defaults phải có MỘT nguồn chân lý, hoặc test so các bản sao. Live probe: `team-settings get <key>`. Xem Tier 11g. |
742
+ | **Test vacuous — assert trên fixture chứ không trên wiring** (P1 remediation, `09dda842`): migration-validator test 2 từng assert key tự chế không có trong registry → luôn pass dù validator chưa được wire vào registerPiTeams. | `09dda842` | Test phải dùng key THẬT từ registry (`PI_CREW_BROKER_DIAG_UI` severity "removed"), và wiring test phải prove call-site (register.ts:68), không chỉ prove pure function. |
622
743
 
623
744
  ---
624
745
 
@@ -647,6 +768,7 @@ When a tier fails, the recovery is usually quick. Match the symptom to the cause
647
768
  | `ask` tool fast-fails "proceed with best judgment" | `broker.waitMethodsEnabled: false` somewhere (user config can re-close the default) | `team-settings get broker.waitMethodsEnabled`; expect `true` (default since `ceb9a68d`); grep events.jsonl for `policy.action` |
648
769
  | `delegate` rejects with a policy message | By design when depth cap hit (`maxDepth: 4`) or `nesting.enabled: false` in USER config (sensitive — project cannot flip) | Check depth in the rejection payload; `delegate.rejected` event in events.jsonl confirms the structured (non-silent) path |
649
770
  | herdr provider never engages | pi is not itself running inside a herdr pane (design: no socket guessing) | Run pi inside herdr, then `runtime.surface.mode` auto/`herdr`; verify `~/.config/herdr/herdr.sock` responds |
771
+ | Surface run >5 phút bị stale-reconcile giết oan (worker khỏe, pane sống) | F1 (đã fix f12f4f5d + af2f8eb4): recorder chỉ flush ở turn boundary → lastSeen đóng băng giữa turn; reconciler cũ time-based không pid-gate. **Bẫy đa host**: MỘT pi session chạy bundle cũ cũng đủ giết run của session khác (sweep quét mọi runs) — tát cả host phải cùng version | Kiểm tra mọi pi process cùng bundle (`ps` lstart vs dist mtime); `PI_CREW_DEBUG_STALE=1` sidecar /tmp/pi-crew-f1-debug.log ghi mọi verdict STALE để bắt hung thủ; kỳ vọng sidecar rỗng khi mọi host đã fix |
650
772
 
651
773
  ## Performance budget (per-tier soft limits)
652
774
 
@@ -774,6 +896,15 @@ Use this to answer "đủ full tính năng chưa?" without re-deriving. Every us
774
896
  | Doctor / health / zombies + orphan panes | `src/extension/team-tool/doctor.ts` | 9a doctor + T10a test #3 |
775
897
  | Model fallback chain | `src/config/types.ts` (modelFallback) | unit tests + 9b sync run (auto-tail chay ngầm) |
776
898
  | State perf (fsync coalescing, event-log tail) | `src/state/` | bench `scripts/run-bench.mjs` (b5/b11-b13) — không cần battery live |
899
+ | Delayed-write conversions / read-your-writes (v0.10.5 remediation) | `src/state/` atomic-write, `appendEventBuffered` sites | T11a (stores sync + census + full test:unit gate) |
900
+ | wc-gate ≤ 2000 lines (M4 done-gate) | `scripts/wc-gate.mjs` | T11b + CI (`ci` script + ci.yml step) |
901
+ | Migration validator (deprecated env warn) | `src/config/migration-validator.ts`, wired `register.ts:68` | T11c (offline validateEnv + live startup warn) |
902
+ | Slow tier (3 heavy tests) | `test/integration/slow/` | T11d (disjoint globs + separate run) |
903
+ | CI env sanity (SMOKE arm) | `.github/workflows/nightly.yml` / `weekly-smoke.yml` | T11e (grep pins) |
904
+ | Event-log reject format | `scratchpad-lifecycle.ts:92`, `finalize-run.ts:242,260` | T11f |
905
+ | ui.widgetPlacement default | `settings-overlay.ts:351`, `handle-settings.ts:43` | T11g + live team-settings get |
906
+ | Worktree twins contract | `src/worktree/worktree-manager.ts` | T11h (3× consecutive runs) |
907
+ | Bundle committed-hash gate | `scripts/check-bundle-staleness.mjs --committed-hash` | T11j |
777
908
 
778
909
  ---
779
910
 
@@ -790,6 +921,9 @@ The skill mentions specific commits, line numbers, and version pins. As the code
790
921
  | Verify Tier 7 verifier prompts still say `test:critical` | Each workflow file edit | `grep "Run FAST checks" workflows/*.workflow.md` |
791
922
  | Verify Tier 10 surface refs | Each `src/runtime/surface/**` edit | `ls test/system/surface-*.e2e.test.ts` + grep `MAX_SURFACE_WORKERS` in resolve-surface.ts — cap/config shape may drift between A1 → A2 |
792
923
  | Verify herdr wire details | Each herdr release bump | `herdr api schema --json` vs `src/runtime/surface/herdr-provider.ts` (envelope/pane.read source/1-conn-per-request were verified on herdr 0.8.2) |
924
+ | Verify Tier 11 census numbers | Each `src/state/**` write-path commit | `grep -rln "appendEventBuffered" src/ \| wc -l` — update the 16-file / 43-conversion anchor in 11a when it drifts |
925
+ | Verify wc-gate still enforced | Each `package.json` / ci.yml edit | `node -e "require('./package.json').scripts.ci.includes('check:wc-gate')"` + grep ci.yml — a gate removed from `ci` reverts to advisory |
926
+ | Verify migration-validator wiring | Each `register.ts` refactor | `grep -n validateEnv src/extension/register.ts` — must stay after `installChildProcessAbortShield`, before `startRuntimeWarmup`, warn-only |
793
927
 
794
928
  The skill does NOT need to be updated for every commit — only when the cited lines/files move. Consider it a "living reference" not a "live spec".
795
929
 
@@ -836,6 +970,18 @@ tmux list-panes -a -F '#{pane_id} #{pane_title} #{pane_pid}' # during run: pane
836
970
  # E2E suite (must run inside tmux):
837
971
  node --experimental-strip-types --test --test-concurrency=1 --test-timeout=120000 test/system/surface-tmux.e2e.test.ts
838
972
  # doctor orphan panes: team action='doctor' focus='zombies'
973
+ # Tier 11 (v0.10.5 remediation regression)
974
+ npm run check:wc-gate # 11b: exit 0, max <= 2000
975
+ node -e "const p=require('./package.json').scripts; console.log(p.ci.includes('check:wc-gate'))" # 11b: true
976
+ node --experimental-strip-types --no-warnings -e 'import("./src/config/migration-validator.ts").then(m=>console.log(JSON.stringify(m.validateEnv({PI_CREW_BROKER_DIAG_UI:"1"}))))' # 11c
977
+ ls test/integration/slow/ # 11d: 3 files
978
+ grep -n "PI_CREW_SMOKE" .github/workflows/nightly.yml # 11e: comment only, NOT set
979
+ grep -rn 'type=\${' src/prompt/scratchpad-lifecycle.ts # 11f: reject format
980
+ grep -rn '"ui.widgetPlacement"' src/ui/settings-overlay.ts src/extension/team-tool/handle-settings.ts # 11g: bottom
981
+ for i in 1 2 3; do node --experimental-strip-types --no-warnings --test test/unit/worktree/worktree-twins-contract.test.ts 2>&1 | grep -E '^# (pass|fail)'; done # 11h
982
+ grep -rln 'appendEventBuffered' src/ | wc -l # 11a: census (16 files @ v0.10.5)
983
+ node scripts/check-bundle-staleness.mjs --committed-hash # 11j: OK
984
+ # 11a full gate (after ANY delayed-write conversion program): npm run test:unit # ~7500 tests, 15-18 min
839
985
  ```
840
986
 
841
987
  ---
@@ -854,6 +1000,7 @@ Before claiming "tested":
854
1000
  - [ ] Tier 9: feature battery — **required if you touched `src/schema/team-tool-schema.ts`, `src/extension/registration/team-tool.ts`, any `Type.Unsafe({...})` schema, or any armed-role tool list (`agents/*.md` / `src/config/role-tools.ts`)**. 9a read-only batch all return clean; one probe per 9b spawn path (sync / async / chain / `Agent` / `crew_agent`+`get_subagent_result`) completes with `consistency=1`. Run 9c–9f only when the change touches their code path; **at least one full 9c/9e/9f sweep per release is recommended so the battery stays proven** (see `real-test-2026-08-11-scratchpad-I-batch.md`); 9d (destructive) requires explicit user confirmation. **After every run: `git status` to catch unauthorized agent edits.**
855
1001
  - [ ] **Output report**: save `docs/real-test/reports/real-test-<YYYY-MM-DD>-<slug>.md` from `skills/real-test-pi-crew/REPORT-TEMPLATE.md`, filled DURING the run with per-tier evidence (counts/md5/runId) — not reconstructed from memory afterward. This is what makes past runs verifiable instead of trust-the-summary.
856
1002
  - [ ] Tier 10: surface battery — **required if you touched `src/runtime/surface/**`, `src/prompt/surface-worker.ts`, the surface branch of `src/runtime/child-pi/child-pi.ts`, or the surface config keys**. 10a E2E 3/3 per backend available (tmux trong tmux; herdr ngoài tmux + socket sống — skip vì thiếu mux là correct-by-design nhưng KHÔNG tính pass cho backend đó); 10b live run với session ĐÃ reload bundle mới (xem Anti-patterns "file-md5 only") + `visibleAgents` set + pane-level evidence (pane id/title during run, `worker.surface_spawned`/`worker.surface_closed` events, pane auto-closed after — KHÔNG dùng `manifest.surface.panes` làm evidence engage, xem Anti-patterns "panes == {}"); 10c herdr live chỉ khi pi chạy trong herdr pane (skip kèm lý do nếu không).
1003
+ - [ ] Tier 11: remediation regression battery — **required if you touched `src/state/**` write paths, `migration-validator.ts`/its wiring, `scripts/wc-gate.mjs` or `ci` scripts, `.github/workflows/*` env, EFFECTIVE_DEFAULTS maps, or you are cutting a release**. Sub-checks a–j per Tier 11; 11a item 4 (full `test:unit`) mandatory after any delayed-write conversion program, skippable for doc-only changes. Record: buffered-site census count, wc-gate max, staleness `--committed-hash` result.
857
1004
 
858
1005
  **"All tiers pass" is a claim that needs per-row evidence.** Tier 9 means 9a **and** 9b **and** whichever of 9c–9f applies to the change — not "9a passed, therefore 9 passed". Tier 10 means pane-level evidence exists, not "run went green" (surface fail-closes to headless on every failure, so green proves nothing). If any required item above is unchecked or lacks concrete evidence (a number, an md5, a runId, a pane id), the answer to "is it tested?" is **no** — say so explicitly instead of rounding up to "pass".
859
1006