jorgex-stack 1.9.1 → 1.9.3
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/README.md
CHANGED
|
@@ -6,7 +6,7 @@ Portable multi-agent harness: one configuration source — 15 agents, 18 skills,
|
|
|
6
6
|
|
|
7
7
|
## Skills: release snapshot and supply chain
|
|
8
8
|
|
|
9
|
-
The 1.9.
|
|
9
|
+
The 1.9.2 release carries a fixed **18-skill snapshot**: **6 stack-owned** skills and **12 vendored** skills. Runtime adapters execute only the local copies committed under `stack/skills`; they do not fetch, install, or execute upstream content at runtime.
|
|
10
10
|
|
|
11
11
|
| Set | Skills |
|
|
12
12
|
| --- | --- |
|
|
@@ -106,21 +106,23 @@ Programmatic mode does **not** provide:
|
|
|
106
106
|
|
|
107
107
|
### Pi runtime
|
|
108
108
|
|
|
109
|
-
Pi combines the frozen **snapshot v2** package with a Stack-owned shared projection. This
|
|
109
|
+
Pi combines the frozen **snapshot v2** package with a Stack-owned shared projection. This section targets the exact published package **`jorgex-pi@0.8.0`** and keeps Pi out of the adapter/component manifest and model map. Stack 1.9.2 recognizes `npm:jorgex-pi@0.8.0`; this is the managed Stack release, not a claim that every end-user scope has already consumed it.
|
|
110
110
|
|
|
111
111
|
```bash
|
|
112
|
-
pnpm dlx jorgex-stack install --agents pi
|
|
113
|
-
pnpm dlx jorgex-stack doctor --agents pi
|
|
114
|
-
pnpm dlx jorgex-stack models --agents pi
|
|
115
|
-
pnpm dlx jorgex-stack sync --agents pi
|
|
116
|
-
pnpm dlx jorgex-stack uninstall --agents pi
|
|
112
|
+
pnpm dlx jorgex-stack@1.9.2 install --agents pi
|
|
113
|
+
pnpm dlx jorgex-stack@1.9.2 doctor --agents pi
|
|
114
|
+
pnpm dlx jorgex-stack@1.9.2 models --agents pi
|
|
115
|
+
pnpm dlx jorgex-stack@1.9.2 sync --agents pi
|
|
116
|
+
pnpm dlx jorgex-stack@1.9.2 uninstall --agents pi
|
|
117
117
|
```
|
|
118
118
|
|
|
119
119
|
Stack downloads the frozen registry tarball, verifies its exact size plus SHA-256/SHA-512, backs up Pi's `settings.json`, and only then asks Pi to install that local file. For `0.8.0`, the frozen tarball is `89128340` bytes; the URL is derived from the version and the authoritative size/SHA-256/SHA-512 pin remains in `src/lib/pi-runtime.ts` rather than being duplicated here. Pi's own package-manager invocation is the narrow runtime exception to the repository's pnpm-only rule; the Stack lifecycle never launches npm directly. After the package is healthy, Stack projects the shared resources into Pi: marked `jorgex:system-prompt` and `jorgex:engram-protocol` sections in `~/.pi/agent/AGENTS.md`, canonical skills under `~/.agents/skills`, and `~/.pi/agent/prompts/lean-audit.md`. When the managed Playwright preference is active, the projection also adds or removes the marked `jorgex:browser` section dynamically. The Pi-only `install --agents pi --playwright` flow installs and persists that Playwright capability just like the other harnesses. Chrome DevTools MCP and Context7 remain outside the Pi scope. The managed Pi package entry is the exact object `{ "source": "npm:jorgex-pi@0.8.0", "skills": [], "prompts": [] }`; filters are applied only after this projection exists, so the package does not duplicate shared resources. Package ownership is recorded separately in `~/.jorgex-stack/pi-receipt.json`; projection ownership is recorded in `~/.jorgex-stack/pi-projection-receipt.json`. Both receipts are scope-bound and fail closed for manual, duplicate, divergent, partial, corrupt, copied-to-another-scope, or unknown-history state.
|
|
120
120
|
|
|
121
121
|
The published Pi 0.8.0 direct-package snapshot adds `work-audit`: the snapshot grows from **17 to 18 skill trees** (96 to 97 files), and the active runtime allowlist grows from **16 to 17 skills**. `playwright-cli` remains in the snapshot but inactive because browser automation is a separate opt-in integration.
|
|
122
122
|
|
|
123
|
-
The published artifact has two separate provenance anchors
|
|
123
|
+
The published artifact has two separate provenance anchors. The local size/SHA-256/SHA-512 checks bind the downloaded bytes to Stack's accepted candidate; they are checks within that checkout, not independent trust roots. npm's external provenance/attestation is outside Stack runtime verification, and `provenance.commit` is informative unless that external attestation is independently verified.
|
|
124
|
+
|
|
125
|
+
The published artifact records two distinct commit identities: the release checkout and tarball producer is `9f999747df3e335947a61d38e581555367973b09` (`main`, release `0.8.0`); and the Stack parity source is `11e7666ea4e40bde1de8bc434610747eb797ab9c`. Registry metadata has no `gitHead`; README does not invent a separate source identity, attestation or signature.
|
|
124
126
|
|
|
125
127
|
Install, sync and uninstall back up every managed file before changing it and are idempotent. `doctor` reports package and projection drift without repairing it. Uninstall removes only receipt-owned package/projection state, retains shared files also owned by another runtime, and preserves user content outside marked sections. Engram remains user-owned and is never removed; the receipts only carry the verified executable hand-off required by the package.
|
|
126
128
|
|
|
@@ -128,9 +130,9 @@ The package owns Pi's native primary-model projection: `openai-codex/gpt-5.6-sol
|
|
|
128
130
|
|
|
129
131
|
Engram remains mandatory and user-owned. An existing binary is preserved. Interactive install may offer the native `brew`/`go`/release channel with explicit confirmation; `--yes` and non-TTY installs fail with a remedy when Engram is absent. The database and memories are never updated or deleted, and uninstall never deletes the Engram binary. Under `--target-dir`, Stack accepts only `<target>/bin/engram`, isolates Pi/Home/XDG/AppData/temp/npm-cache paths inside the target, and never consults the host Engram or Pi configuration.
|
|
130
132
|
|
|
131
|
-
|
|
133
|
+
Stack 1.9.2 recognizes the exact Pi receipt for `npm:jorgex-pi@0.8.0`; use exact versions, never `latest`, and never edit receipts or hashes or delete `HOME`, Engram, or another runtime's projection to force trust. The rollback examples are in [docs/references/pi-runtime.md](docs/references/pi-runtime.md).
|
|
132
134
|
|
|
133
|
-
The 24-hour
|
|
135
|
+
Stack 1.9.2 was accepted by npm and its readback confirmed public registry metadata and tarball availability. The 24-hour managed-consumption maturity rule applies only to real installation or consumption of the new Pi package; development, PR validation, merge and Stack publication may proceed immediately. Installing it on a real user scope before 24 hours requires Jorge's explicit exception.
|
|
134
136
|
|
|
135
137
|
`update --agents pi` only runs the Pi package lifecycle; it does not enter the global Stack updater. `update --check --agents pi` is a read-only Pi doctor. Uninstall runs package cleanup, backs up Pi's settings before removal, removes only the exact receipt-owned package after verifying absence, and preserves all companion/user state. Full behavior, failure states and troubleshooting are in [docs/references/pi-runtime.md](docs/references/pi-runtime.md).
|
|
136
138
|
|
|
@@ -226,6 +228,7 @@ El preflight condiciona únicamente la preparación de toolchain, la instalació
|
|
|
226
228
|
- **Automatic patch**: if the push to `main` contains publishable changes and the current `package.json` version already exists on npm, the workflow finds the first free patch (`x+1`, `x+2`, ...), commits `chore(release): bump version to v...`, and publishes. If tag `v<package.version>` already exists, it uses that point as the accumulated base; otherwise, it falls back to `github.event.before`. Obsolete runs are aborted after `git fetch origin main --tags` if `origin/main` no longer matches `GITHUB_SHA`.
|
|
227
229
|
- **Automatic patch guard**: before committing or pushing an automatic bump, the workflow validates the real working-tree/index diff. Only the expected `version` change in the root `package.json` is allowed; unrelated tracked, staged or untracked files, other package metadata, or an unexpected version fail closed. The bot's automatic bump commit carries the single `[skip ci]` marker to prevent a recursive publish run; manual minor/major bumps do not use that marker. This guard does not create a second push and does not skip the real validation gate that produced the candidate.
|
|
228
230
|
- **Manual recovery**: a manual run on `main` with `release_sha` publishes that SHA if it does not exist on npm yet, without bumping again; if the version already exists on npm but tag `v<version>` is missing, the workflow fails and forces a rerun with `release_sha=<published sha>` to avoid tagging `origin/main`. `release_sha` must be a full 40-hex SHA and belong to `main`; mutable refs (`main`, tags, `main~1`) are rejected. If you do not pass `release_sha`, `validate` resolves `origin/main` once, exposes it as `target_sha`, and `bump` uses that validated SHA. Recovery does not bypass the `.github/workflows/*` guard: if the diff mixes workflows with publishable changes, split the release or perform the tag/publish manually with elevated permissions. If there is no reachable previous release tag to reconstruct the range, the workflow fails closed and requires manual intervention.
|
|
231
|
+
- **Rejected rerun recovery**: if a run is rejected because it is a rerun (GITHUB_RUN_ATTEMPT), do not rerun that execution. Start a new `workflow_dispatch` on `main` with `release_sha` set to the accepted/published SHA that still needs publication or tagging; the new workflow validates that immutable SHA before mutating anything.
|
|
229
232
|
- **No release**: changes only in `work/`, `worktrees/`, tests, or docs (`README.md`, `docs/`) do not create a release. The publishable set that does trigger one is `src/`, `stack/`, `upstreams.json`, `package.json`, `pnpm-lock.yaml`, `tsconfig.json`, and `tsup.config.ts`.
|
|
230
233
|
- **Manual minor and major**: explicit bump in `package.json` in the PR (the workflow detects that the next patch already exists on npm and requires the bump).
|
|
231
234
|
- **OIDC / trusted publishing**: the publishing job uses `id-token: write` and `setup-node` `registry-url`; it has no repository-write permission. The `bump` job mints a short-lived GitHub App token only for `jorgex-stack`, from the `stack-release` environment, with `contents: write`, and uses it for the checkout/push of the automatic bump; its job-level `GITHUB_TOKEN` remains read-only. `tag-release` uses the ordinary `GITHUB_TOKEN` with `contents: write` and does not use the App or OIDC. There is no `NPM_TOKEN` or `NODE_AUTH_TOKEN` in any secret. `tag-release` only runs if `publish` was `success` or `skipped` with `tag_needed=true`, and keeps its SHA validation as the final defense. The only exception to the "always pnpm" rule is `npm pack --dry-run --ignore-scripts` and `npm publish --ignore-scripts --provenance` in the final step, for registry compatibility and hardening.
|
package/package.json
CHANGED
|
@@ -16,6 +16,32 @@ interface ScriptResult {
|
|
|
16
16
|
scriptPath: string;
|
|
17
17
|
}
|
|
18
18
|
|
|
19
|
+
const logToOpenCode = async (
|
|
20
|
+
client: unknown,
|
|
21
|
+
level: "warn" | "error",
|
|
22
|
+
message: string,
|
|
23
|
+
extra?: unknown,
|
|
24
|
+
) => {
|
|
25
|
+
if (typeof client !== "object" || client === null) return;
|
|
26
|
+
const app = (client as { app?: unknown }).app;
|
|
27
|
+
if (typeof app !== "object" || app === null) return;
|
|
28
|
+
const log = (app as { log?: unknown }).log;
|
|
29
|
+
if (typeof log !== "function") return;
|
|
30
|
+
|
|
31
|
+
const safeExtra =
|
|
32
|
+
extra instanceof Error
|
|
33
|
+
? { message: extra.message, stack: extra.stack }
|
|
34
|
+
: extra;
|
|
35
|
+
|
|
36
|
+
try {
|
|
37
|
+
await log.call(app, {
|
|
38
|
+
body: { service: "hooks", level, message, extra: safeExtra },
|
|
39
|
+
});
|
|
40
|
+
} catch {
|
|
41
|
+
// Logging must not break the OpenCode TUI hook.
|
|
42
|
+
}
|
|
43
|
+
};
|
|
44
|
+
|
|
19
45
|
const isAbsolutePath = (value: string) =>
|
|
20
46
|
/^[a-zA-Z]:[\\/]/.test(value) || value.startsWith("/");
|
|
21
47
|
|
|
@@ -43,6 +69,22 @@ const resolveProjectPath = (directory: string, target?: string) => {
|
|
|
43
69
|
const isPlainObject = (value: unknown): value is Record<string, unknown> =>
|
|
44
70
|
typeof value === "object" && value !== null && !Array.isArray(value);
|
|
45
71
|
|
|
72
|
+
const validateConfig = (config: Record<string, unknown>) => {
|
|
73
|
+
for (const field of ["setupScript", "docsReminderScript", "pathContains"]) {
|
|
74
|
+
if (field in config && typeof config[field] !== "string") {
|
|
75
|
+
return `Worktree config field "${field}" must be a string.`;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
if (
|
|
80
|
+
"reminderLines" in config &&
|
|
81
|
+
(!Array.isArray(config.reminderLines) ||
|
|
82
|
+
!config.reminderLines.every((line) => typeof line === "string"))
|
|
83
|
+
) {
|
|
84
|
+
return 'Worktree config field "reminderLines" must be an array of strings.';
|
|
85
|
+
}
|
|
86
|
+
};
|
|
87
|
+
|
|
46
88
|
const getPayloadWorktreePath = (payload: unknown) => {
|
|
47
89
|
if (!isPlainObject(payload)) return undefined;
|
|
48
90
|
|
|
@@ -142,15 +184,14 @@ const runScript = async (
|
|
|
142
184
|
}
|
|
143
185
|
|
|
144
186
|
if (!command) {
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
}
|
|
153
|
-
return { exitCode: 0, stdout: "", stderr: "", script, scriptPath };
|
|
187
|
+
const stderr = `Unsupported worktree hook script extension for ${scriptPath}.`;
|
|
188
|
+
await logToOpenCode(
|
|
189
|
+
client,
|
|
190
|
+
"warn",
|
|
191
|
+
"Unsupported worktree hook script extension",
|
|
192
|
+
{ script, scriptPath },
|
|
193
|
+
);
|
|
194
|
+
return { exitCode: 1, stdout: "", stderr, script, scriptPath };
|
|
154
195
|
}
|
|
155
196
|
|
|
156
197
|
try {
|
|
@@ -173,35 +214,26 @@ const runScript = async (
|
|
|
173
214
|
]);
|
|
174
215
|
|
|
175
216
|
if (exitCode !== 0) {
|
|
176
|
-
await
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
stdout: stdout.trim(),
|
|
187
|
-
},
|
|
217
|
+
await logToOpenCode(
|
|
218
|
+
client,
|
|
219
|
+
"error",
|
|
220
|
+
"Worktree hook script execution failed",
|
|
221
|
+
{
|
|
222
|
+
script,
|
|
223
|
+
scriptPath,
|
|
224
|
+
exitCode,
|
|
225
|
+
stderr: stderr.trim(),
|
|
226
|
+
stdout: stdout.trim(),
|
|
188
227
|
},
|
|
189
|
-
|
|
228
|
+
);
|
|
190
229
|
}
|
|
191
230
|
|
|
192
231
|
return { exitCode, stdout, stderr, script, scriptPath };
|
|
193
232
|
} catch (error) {
|
|
194
|
-
await client
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
message: "Worktree hook script execution failed",
|
|
199
|
-
extra: {
|
|
200
|
-
script,
|
|
201
|
-
scriptPath,
|
|
202
|
-
error: error instanceof Error ? error.message : String(error),
|
|
203
|
-
},
|
|
204
|
-
},
|
|
233
|
+
await logToOpenCode(client, "error", "Worktree hook script execution failed", {
|
|
234
|
+
script,
|
|
235
|
+
scriptPath,
|
|
236
|
+
error: error instanceof Error ? error.message : String(error),
|
|
205
237
|
});
|
|
206
238
|
return {
|
|
207
239
|
exitCode: 1,
|
|
@@ -285,28 +317,67 @@ const getCommandCwd = (args: Record<string, unknown>, directory: string) => {
|
|
|
285
317
|
const replaceToken = (value: string, token: string, replacement: string) =>
|
|
286
318
|
value.split(token).join(replacement);
|
|
287
319
|
|
|
320
|
+
const isMissingFileError = (error: unknown) => {
|
|
321
|
+
const code =
|
|
322
|
+
typeof error === "object" && error !== null && "code" in error
|
|
323
|
+
? (error as { code?: unknown }).code
|
|
324
|
+
: undefined;
|
|
325
|
+
return (
|
|
326
|
+
code === "ENOENT" ||
|
|
327
|
+
(error instanceof Error && /not found|no such file/i.test(error.message))
|
|
328
|
+
);
|
|
329
|
+
};
|
|
330
|
+
|
|
288
331
|
export const WorktreePlugin: Plugin = async ({ $, client, directory }) => {
|
|
289
332
|
let config: WorktreePluginConfig = {
|
|
290
|
-
setupScript: "scripts/setup-worktree.ps1",
|
|
291
333
|
pathContains: "worktrees/",
|
|
292
334
|
reminderLines: [],
|
|
293
335
|
};
|
|
336
|
+
let configError: string | undefined;
|
|
294
337
|
|
|
295
338
|
try {
|
|
296
339
|
const configPath = `${directory}/.opencode/worktree.json`;
|
|
297
|
-
const
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
340
|
+
const configFile = (globalThis as any).Bun.file(configPath);
|
|
341
|
+
if (
|
|
342
|
+
typeof configFile.exists === "function" &&
|
|
343
|
+
!(await configFile.exists())
|
|
344
|
+
) {
|
|
345
|
+
// Optional project config.
|
|
346
|
+
} else {
|
|
347
|
+
const parsed = JSON.parse(await configFile.text()) as unknown;
|
|
348
|
+
if (!isPlainObject(parsed)) {
|
|
349
|
+
configError = "Worktree config must be a JSON object.";
|
|
350
|
+
} else {
|
|
351
|
+
configError = validateConfig(parsed);
|
|
352
|
+
if (!configError) {
|
|
353
|
+
config = { ...config, ...(parsed as WorktreePluginConfig) };
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
} catch (error) {
|
|
358
|
+
if (!isMissingFileError(error)) {
|
|
359
|
+
const couldNotParse = error instanceof SyntaxError;
|
|
360
|
+
configError = couldNotParse
|
|
361
|
+
? "Worktree config could not be parsed as JSON."
|
|
362
|
+
: "Worktree config could not be read.";
|
|
363
|
+
await logToOpenCode(
|
|
364
|
+
client,
|
|
365
|
+
"warn",
|
|
366
|
+
couldNotParse
|
|
367
|
+
? "Worktree config could not be parsed"
|
|
368
|
+
: "Worktree config could not be read",
|
|
369
|
+
error,
|
|
370
|
+
);
|
|
371
|
+
}
|
|
304
372
|
}
|
|
305
373
|
|
|
306
374
|
return {
|
|
307
375
|
"tool.execute.after": async (input: any, output: any) => {
|
|
308
376
|
try {
|
|
309
377
|
const tool = (input.tool || "").toLowerCase();
|
|
378
|
+
const args = input.args || {};
|
|
379
|
+
const command = args.command || "";
|
|
380
|
+
|
|
310
381
|
const payload = {
|
|
311
382
|
event: "tool.execute.after",
|
|
312
383
|
directory,
|
|
@@ -315,6 +386,11 @@ export const WorktreePlugin: Plugin = async ({ $, client, directory }) => {
|
|
|
315
386
|
};
|
|
316
387
|
|
|
317
388
|
if (tool === "enterworktree") {
|
|
389
|
+
if (configError) {
|
|
390
|
+
appendToolOutput(output, [configError]);
|
|
391
|
+
return;
|
|
392
|
+
}
|
|
393
|
+
|
|
318
394
|
const docsReminderScript = resolveProjectPath(
|
|
319
395
|
directory,
|
|
320
396
|
config.docsReminderScript,
|
|
@@ -337,8 +413,6 @@ export const WorktreePlugin: Plugin = async ({ $, client, directory }) => {
|
|
|
337
413
|
|
|
338
414
|
if (tool !== "bash") return;
|
|
339
415
|
|
|
340
|
-
const args = input.args || {};
|
|
341
|
-
const command = args.command || "";
|
|
342
416
|
const commandLower = command.toLowerCase();
|
|
343
417
|
const pathContains = toSlashes(
|
|
344
418
|
config.pathContains || "worktrees/",
|
|
@@ -366,12 +440,24 @@ export const WorktreePlugin: Plugin = async ({ $, client, directory }) => {
|
|
|
366
440
|
`worktrees/${worktreeName}`,
|
|
367
441
|
);
|
|
368
442
|
|
|
369
|
-
|
|
443
|
+
const isCanonicalPath = samePath(
|
|
444
|
+
absoluteWorktreePath,
|
|
445
|
+
expectedWorktreePath,
|
|
446
|
+
);
|
|
447
|
+
if (!isCanonicalPath) {
|
|
370
448
|
appendToolOutput(output, [
|
|
371
449
|
`Worktree path is not canonical: ${absoluteWorktreePath}`,
|
|
372
450
|
`Use the project-local path instead: ${expectedWorktreePath}`,
|
|
373
451
|
"Canonical rule: <project-root>/worktrees/<canonical-name> or <project-root>/worktrees/<canonical-name>-prNN.",
|
|
374
452
|
]);
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
if (configError) {
|
|
456
|
+
appendToolOutput(output, [configError]);
|
|
457
|
+
return;
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
if (!isCanonicalPath) {
|
|
375
461
|
return;
|
|
376
462
|
}
|
|
377
463
|
|
|
@@ -430,15 +516,16 @@ export const WorktreePlugin: Plugin = async ({ $, client, directory }) => {
|
|
|
430
516
|
appendToolOutput(output, [banner]);
|
|
431
517
|
}
|
|
432
518
|
} catch (error) {
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
519
|
+
const details = truncateMessage(
|
|
520
|
+
error instanceof Error ? error.message : String(error),
|
|
521
|
+
);
|
|
522
|
+
appendToolOutput(output, [
|
|
523
|
+
"Worktree plugin could not process this worktree command.",
|
|
524
|
+
"Run `git rev-parse --show-toplevel` from the project root and retry.",
|
|
525
|
+
...(details ? [`Details: ${details}`] : []),
|
|
526
|
+
]);
|
|
527
|
+
await logToOpenCode(client, "error", "Worktree plugin execution failed", {
|
|
528
|
+
error: error instanceof Error ? error.message : String(error),
|
|
442
529
|
});
|
|
443
530
|
}
|
|
444
531
|
},
|
|
@@ -83,6 +83,7 @@ If the work is large enough to benefit from explicit vertical slices, use the `t
|
|
|
83
83
|
- One task = one agent = one scope.
|
|
84
84
|
- For tasks that add or grow code, record the lean-code outcome in the task spec/acceptance criteria so implementer and simplifier apply the same ladder.
|
|
85
85
|
- The PRD does not replace the plan or task breakdown: the PRD captures decisions; the plan and tasks turn those decisions into executable work.
|
|
86
|
+
- **Change-first**: for intentional material contract changes discovered in EXECUTE or VERIFY—not bugfixes that restore the approved contract—return to SPEC before further implementation. Update the PRD first, then propagate it to the plan, task specs, `SC-*` success criteria and testing decisions; rerun PRE until `clean`, obtain human approval of the delta, then resume EXECUTE and repeat VERIFY.
|
|
86
87
|
- Materialize the plan per the Work state rules: `work/{name}/plan.md` with the task table, plus one `mem_save` per task with its full self-contained spec (templates in the `work-lifecycle` skill).
|
|
87
88
|
- Load and run the `work-audit` skill in **PRE** mode after the plan and task specs exist and before presenting the final plan. Pass the exact active `work/{name}` path and the exact PR/checkpoint scope; never infer either from the branch or scan other work folders. PRE is read-only: during audit remediation you are the only writer of active work artifacts. Route every finding to its owner artifact, correct it, and rerun PRE until it reports `clean`.
|
|
88
89
|
- An unresolved `[NEEDS CLARIFICATION: ...]` marker blocks PRE. Return to SPEC and resolve the ambiguity with the user only when existing context cannot answer it; never approve or execute a plan while PRE is not clean.
|
|
@@ -196,6 +197,7 @@ An early review during EXECUTE is an **exception**, not a default phase. Use it
|
|
|
196
197
|
|
|
197
198
|
- Run the minimum verification that is sufficient.
|
|
198
199
|
- Reserve heavy suites for cases where they provide real value or the project requires them.
|
|
200
|
+
- If POST identifies an intentional material contract change, follow the PLAN's change-first procedure before further implementation.
|
|
199
201
|
- Load and run the `work-audit` skill in **POST** mode after deterministic checks. Pass the exact active `work/{name}` path and the exact current checkpoint scope. POST is read-only and must report `converged`; when it reports `gaps`, during audit remediation you are the only writer of active work artifacts: add normal plan tasks and Engram specs when needed, return to the phase that owns each gap, and rerun POST after the fixes.
|
|
200
202
|
- Only after POST reports `converged`, validate against the plan's **Success criteria** and mark the success criteria complete. Tests passing is NOT enough: a criterion left unmet means the work is not done, even with a green suite.
|
|
201
203
|
- Before SHIP, ensure all applicable preflight work is complete: code, version bump, local tests, the project's quality command (`pnpm qa:quality` when defined), and Vercel preview review when the project uses Vercel. React Doctor is manual/local, never assumed to be a GitHub Actions gate.
|
|
@@ -28,6 +28,7 @@ Run after the plan and task specs exist, before presenting the final plan for ap
|
|
|
28
28
|
Check:
|
|
29
29
|
|
|
30
30
|
1. No unresolved `[NEEDS CLARIFICATION: ...]` marker remains.
|
|
31
|
+
- Report a latent material semantic ambiguity as a gap only when plausible interpretations differ materially in observable behavior, scope, success criteria or testing. Only low-impact implementation preferences, defaults, wording and paths are excluded from gaps or clarification; material alternatives still block.
|
|
31
32
|
2. Success criteria use unique IDs such as `SC-01`; report duplicate or malformed `SC-*` IDs.
|
|
32
33
|
3. Every SC is verifiable and has task coverage in the plan table.
|
|
33
34
|
4. Every task references known SCs and has one agent, one bounded scope, affected files, dependencies and a wave consistent with those dependencies.
|
|
@@ -49,13 +50,14 @@ Check:
|
|
|
49
50
|
1. Every planned task for the checkpoint has the expected status and bounded outcome.
|
|
50
51
|
2. Every in-scope SC has concrete evidence in its canonical checkpoint: command/setup, scope, result and relevant limits.
|
|
51
52
|
3. The implementation diff and observed behavior stay within the approved PRD, plan and task scopes.
|
|
53
|
+
- POST cannot legitimize scope changes retroactively. For intentional material contract changes, send scope drift to SPEC through change-first. Defects or bugfixes restoring the approved contract return to EXECUTE.
|
|
52
54
|
4. Tests, typecheck/build, manual checks and external gates are not over-claimed; missing or incomplete execution remains explicit.
|
|
53
55
|
5. No accepted requirement, edge case, testing decision, documentation change or cross-repo contract assigned to the current checkpoint is left without implementation or evidence. Future checkpoints remain out of scope.
|
|
54
56
|
|
|
55
57
|
POST verdicts:
|
|
56
58
|
|
|
57
59
|
- `converged` — the available evidence satisfies the approved contract. This does not replace tests, human review, configured Quality Gates or manual validation when applicable.
|
|
58
|
-
- `gaps` — return actionable findings to the orchestrator;
|
|
60
|
+
- `gaps` — return actionable findings to the orchestrator and route each to its owning phase; never send every gap unconditionally to EXECUTE. Rerun POST after the fix.
|
|
59
61
|
|
|
60
62
|
## Read-only boundary
|
|
61
63
|
|