feat(ultraplan-local): seal Opus-4.7 schema-drift defense in Phase 8
Inline STEP_HEADING_REGEX, FORBIDDEN_HEADING_REGEX, the canonical step+manifest example, and the post-write plan-validator self-check directly into Phase 8 of commands/ultraplan-local.md. This eliminates the dependency on Opus 4.7 implicitly loading agents/planning-orchestrator.md — the format contract now travels with the command file itself. Source: research/04 D5 + plan-v2 Step 7. Pin in tests/lib/doc-consistency.test.mjs locks the substrings so future edits cannot silently regress the seal.
This commit is contained in:
parent
50f0629baf
commit
84eae1fad7
2 changed files with 92 additions and 0 deletions
|
|
@ -554,6 +554,69 @@ Do NOT write this synthesis to disk. It is internal working context only.
|
|||
|
||||
## Phase 8 — Deep planning
|
||||
|
||||
> **Schema-drift defense (sealed inline so this contract survives even when
|
||||
> `agents/planning-orchestrator.md` is not implicitly loaded by Opus 4.7).**
|
||||
>
|
||||
> The plan you write MUST satisfy these regexes. The executor parses with
|
||||
> strict regex matching; any deviation breaks parsing and forces a re-plan.
|
||||
>
|
||||
> ```
|
||||
> STEP_HEADING_REGEX = /^### Step (\d+):\s+(.+?)\s*$/m
|
||||
> FORBIDDEN_HEADING_REGEX = /^(?:##|###) (?:Fase|Phase|Stage|Steg) \d+/m
|
||||
> ```
|
||||
>
|
||||
> **FORBIDDEN headings** (parser rejects these — do not emit them under
|
||||
> Implementation Plan):
|
||||
> - `## Fase 1`, `### Fase 1` — Norwegian narrative format
|
||||
> - `## Phase 1`, `### Phase 1` — narrative phase format
|
||||
> - `## Stage 1`, `### Stage 1` — narrative stage format
|
||||
> - `## Steg 1`, `### Steg 1` — Norwegian step word
|
||||
> - `### 1.` or `### 1)` — numbered without "Step"
|
||||
> - `### Step 1 —` (em-dash instead of colon)
|
||||
> - Any heading that doesn't match `STEP_HEADING_REGEX`
|
||||
>
|
||||
> **REQUIRED step shape** — copy this canonical example verbatim, substituting
|
||||
> file paths, descriptions, and patterns. Preserve the exact heading format,
|
||||
> bullet field names, and Manifest YAML structure. Do not invent new field
|
||||
> names. Do not skip fields. Do not nest steps under sub-headings.
|
||||
>
|
||||
> ````markdown
|
||||
> ### Step 1: Add JWT verification middleware
|
||||
>
|
||||
> - **Files:** `src/middleware/jwt.ts`
|
||||
> - **Changes:** Create new middleware function `verifyJWT(req, res, next)` that reads `Authorization: Bearer <token>` header, verifies signature with `process.env.JWT_SECRET`, attaches decoded payload to `req.user`, and returns 401 on invalid/missing token. (new file)
|
||||
> - **Reuses:** `jsonwebtoken.verify()` (already in package.json), pattern from `src/middleware/cors.ts`
|
||||
> - **Test first:**
|
||||
> - File: `src/middleware/jwt.test.ts` (new)
|
||||
> - Verifies: valid token attaches user; invalid token returns 401; missing header returns 401
|
||||
> - Pattern: `src/middleware/cors.test.ts` (follow this style)
|
||||
> - **Verify:** `npm test -- jwt.test.ts` → expected: `3 passing`
|
||||
> - **On failure:** revert — `git checkout -- src/middleware/jwt.ts src/middleware/jwt.test.ts`
|
||||
> - **Checkpoint:** `git commit -m "feat(auth): add JWT verification middleware"`
|
||||
> - **Manifest:**
|
||||
> ```yaml
|
||||
> manifest:
|
||||
> expected_paths:
|
||||
> - src/middleware/jwt.ts
|
||||
> - src/middleware/jwt.test.ts
|
||||
> min_file_count: 2
|
||||
> commit_message_pattern: "^feat\\(auth\\): add JWT verification middleware$"
|
||||
> bash_syntax_check: []
|
||||
> forbidden_paths:
|
||||
> - src/middleware/cors.ts
|
||||
> must_contain:
|
||||
> - path: src/middleware/jwt.ts
|
||||
> pattern: "verifyJWT"
|
||||
> ```
|
||||
> ````
|
||||
>
|
||||
> **Validator self-check (mandatory after writing `plan.md`):** run
|
||||
> `node ${CLAUDE_PLUGIN_ROOT}/lib/validators/plan-validator.mjs --strict --json {plan_path}`
|
||||
> and re-revise the plan if it fails. The validator is the source of truth for
|
||||
> heading shape, manifest presence, and required-field coverage. If
|
||||
> `${CLAUDE_PLUGIN_ROOT}` is unset (rare in practice), fall back to the
|
||||
> equivalent path under your validators cache or the repo's `lib/validators/`.
|
||||
|
||||
Read the brief file (from `--brief` or `--project`).
|
||||
Read the plan template: @${CLAUDE_PLUGIN_ROOT}/templates/plan-template.md
|
||||
|
||||
|
|
|
|||
|
|
@ -157,3 +157,32 @@ test('rule-catalogue has exactly 12 entries', async () => {
|
|||
'lib/review/rule-catalogue.mjs RULE_CATALOGUE size invariant: must be 12 (v1.0 baseline)',
|
||||
);
|
||||
});
|
||||
|
||||
test('commands/ultraplan-local.md Phase 8 seals Opus-4.7 schema-drift defense', () => {
|
||||
const cmd = read('commands/ultraplan-local.md');
|
||||
// Locate Phase 8 section
|
||||
const phase8Start = cmd.indexOf('## Phase 8');
|
||||
assert.ok(phase8Start >= 0, 'Phase 8 heading missing');
|
||||
const phase8End = cmd.indexOf('## Phase 9', phase8Start);
|
||||
assert.ok(phase8End > phase8Start, 'Phase 9 heading missing — could not bound Phase 8');
|
||||
const phase8 = cmd.slice(phase8Start, phase8End);
|
||||
// Required regex source-of-truth references
|
||||
assert.ok(
|
||||
phase8.includes('STEP_HEADING_REGEX'),
|
||||
'Phase 8 should inline STEP_HEADING_REGEX so format contract survives without orchestrator-doc loading',
|
||||
);
|
||||
assert.ok(
|
||||
phase8.includes('FORBIDDEN_HEADING_REGEX'),
|
||||
'Phase 8 should inline FORBIDDEN_HEADING_REGEX (Step 7 — schema-drift seal)',
|
||||
);
|
||||
// Required validator self-check
|
||||
assert.ok(
|
||||
phase8.includes('plan-validator.mjs --strict'),
|
||||
'Phase 8 should mandate post-write `plan-validator.mjs --strict` self-check',
|
||||
);
|
||||
// Forbidden-headings list (literal "FORBIDDEN" appears more than once: in regex const + in human-readable list)
|
||||
assert.ok(
|
||||
/FORBIDDEN/.test(phase8),
|
||||
'Phase 8 should explicitly enumerate FORBIDDEN headings',
|
||||
);
|
||||
});
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue