instar 1.3.435 → 1.3.437
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/dashboard/index.html +2 -0
- package/dashboard/subscriptions.js +18 -8
- package/dist/commands/init.d.ts +17 -0
- package/dist/commands/init.d.ts.map +1 -1
- package/dist/commands/init.js +50 -0
- package/dist/commands/init.js.map +1 -1
- package/dist/commands/server.d.ts.map +1 -1
- package/dist/commands/server.js +6 -1
- package/dist/commands/server.js.map +1 -1
- package/dist/core/InUseAccountResolver.d.ts +77 -0
- package/dist/core/InUseAccountResolver.d.ts.map +1 -0
- package/dist/core/InUseAccountResolver.js +123 -0
- package/dist/core/InUseAccountResolver.js.map +1 -0
- package/dist/server/AgentServer.d.ts +1 -0
- package/dist/server/AgentServer.d.ts.map +1 -1
- package/dist/server/AgentServer.js +1 -0
- package/dist/server/AgentServer.js.map +1 -1
- package/dist/server/routes.d.ts +4 -0
- package/dist/server/routes.d.ts.map +1 -1
- package/dist/server/routes.js +20 -0
- package/dist/server/routes.js.map +1 -1
- package/package.json +2 -1
- package/skills/README.md +106 -0
- package/skills/agent-identity/SKILL.md +226 -0
- package/skills/agent-memory/SKILL.md +261 -0
- package/skills/agent-passport/SKILL.md +37 -0
- package/skills/agent-readiness/SKILL.md +55 -0
- package/skills/command-guard/SKILL.md +239 -0
- package/skills/credential-leak-detector/SKILL.md +377 -0
- package/skills/instar-dev/SKILL.md +223 -0
- package/skills/instar-dev/scripts/verify-proposal-derived-runbook.mjs +319 -0
- package/skills/instar-dev/scripts/write-trace.mjs +203 -0
- package/skills/instar-dev/templates/eli16-overview.md +43 -0
- package/skills/instar-dev/templates/side-effects-artifact.md +133 -0
- package/skills/instar-feedback/SKILL.md +285 -0
- package/skills/instar-identity/SKILL.md +290 -0
- package/skills/instar-scheduler/SKILL.md +259 -0
- package/skills/instar-session/SKILL.md +270 -0
- package/skills/instar-telegram/SKILL.md +259 -0
- package/skills/iterative-converging-audit/SKILL.md +96 -0
- package/skills/knowledge-base/SKILL.md +189 -0
- package/skills/smart-web-fetch/SKILL.md +241 -0
- package/skills/spec-converge/SKILL.md +249 -0
- package/skills/spec-converge/scripts/cross-model-review.mjs +155 -0
- package/skills/spec-converge/scripts/publish-spec-review.mjs +166 -0
- package/skills/spec-converge/scripts/write-convergence-tag.mjs +199 -0
- package/skills/spec-converge/templates/report.md +76 -0
- package/skills/spec-converge/templates/reviewer-adversarial.md +37 -0
- package/skills/spec-converge/templates/reviewer-cross-model.md +28 -0
- package/skills/spec-converge/templates/reviewer-integration.md +39 -0
- package/skills/spec-converge/templates/reviewer-lessons-aware.md +101 -0
- package/skills/spec-converge/templates/reviewer-scalability.md +35 -0
- package/skills/spec-converge/templates/reviewer-security.md +36 -0
- package/skills/systematic-debugging/SKILL.md +222 -0
- package/src/data/builtin-manifest.json +47 -47
- package/upgrades/1.3.436.md +19 -0
- package/upgrades/1.3.437.md +29 -0
- package/upgrades/side-effects/builtin-skill-install-single-source.md +80 -0
- package/upgrades/side-effects/subscription-inuse-account.md +26 -0
|
@@ -0,0 +1,249 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: spec-converge
|
|
3
|
+
description: Iteratively review an instar-development spec with multi-angle internal reviewers (security, scalability, adversarial, integration, lessons-aware) and a real cross-model external reviewer routed through the agent's own installed codex CLI (GPT-tier) until convergence, then produce a comprehensive ELI10 convergence report. Output is a spec tagged review-convergence — one of the two tags /instar-dev requires before it will touch instar source. NOT user-invocable; run by the instar-developing agent before any spec-driven /instar-dev work.
|
|
4
|
+
metadata:
|
|
5
|
+
user_invocable: "false"
|
|
6
|
+
audience: "instar-developing agent only — NOT end users"
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
# /spec-converge
|
|
10
|
+
|
|
11
|
+
**Audience:** the instar-developing agent. End users do not invoke this. Throughout this document, "the agent" refers to the instar-developing agent running the skill.
|
|
12
|
+
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
## What this skill does
|
|
16
|
+
|
|
17
|
+
Takes a spec file, runs multiple parallel reviewers against it, updates the spec to address their findings, runs another round, and repeats until convergence. Produces a final converged spec and a comprehensive human-readable report.
|
|
18
|
+
|
|
19
|
+
The purpose is to catch architectural, security, adversarial, scalability, and integration issues BEFORE code is written, not after. The single-reviewer pattern used by `/instar-dev`'s existing second-pass step has proven too narrow in practice — a four-reviewer parallel audit on the integrated-being ledger PR surfaced 14 serious issues that a single reviewer had missed. This skill bakes that multi-angle pattern in as the structural default for any substantive instar-dev work.
|
|
20
|
+
|
|
21
|
+
## When it runs
|
|
22
|
+
|
|
23
|
+
Before `/instar-dev` touches any instar source, the change's spec must pass through this skill. `/instar-dev`'s pre-commit hook refuses work unless:
|
|
24
|
+
|
|
25
|
+
1. The spec file has a `review-convergence: <timestamp>` entry in its frontmatter, written by this skill on successful convergence.
|
|
26
|
+
2. The spec file has an `approved: true` entry in its frontmatter, written by the user after reading the convergence report.
|
|
27
|
+
|
|
28
|
+
Both tags must be present. Without them, `/instar-dev` is blocked.
|
|
29
|
+
|
|
30
|
+
## Input
|
|
31
|
+
|
|
32
|
+
One argument: the path to the spec file, relative to the instar repo root.
|
|
33
|
+
|
|
34
|
+
The spec file is a markdown document with YAML frontmatter. Minimum required structure:
|
|
35
|
+
|
|
36
|
+
```markdown
|
|
37
|
+
---
|
|
38
|
+
title: "Short title"
|
|
39
|
+
slug: "url-friendly-slug"
|
|
40
|
+
author: "agent name"
|
|
41
|
+
---
|
|
42
|
+
|
|
43
|
+
# Title
|
|
44
|
+
|
|
45
|
+
## Problem statement
|
|
46
|
+
[what is being built and why]
|
|
47
|
+
|
|
48
|
+
## Proposed design
|
|
49
|
+
[how it works, at enough detail to review]
|
|
50
|
+
|
|
51
|
+
## Decision points touched
|
|
52
|
+
[any block/allow/route gates the design introduces, removes, or modifies]
|
|
53
|
+
|
|
54
|
+
## Open questions
|
|
55
|
+
[things that need human input]
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
The skill adds `review-convergence`, `review-iterations`, `review-completed-at`, and (if provided) `approved` fields to the frontmatter on successful convergence.
|
|
59
|
+
|
|
60
|
+
## Phases
|
|
61
|
+
|
|
62
|
+
### Phase 1 — Initial review round
|
|
63
|
+
|
|
64
|
+
The skill spawns reviewers in parallel:
|
|
65
|
+
|
|
66
|
+
**Internal reviewers (Claude subagents):**
|
|
67
|
+
|
|
68
|
+
- **Security.** Attack surfaces, leaks, privilege escalation, auth on endpoints, prompt injection vectors, rotation races.
|
|
69
|
+
- **Scalability/performance.** Hot-path cost, concurrent writes, memory churn, fail-open semantics, hook latency.
|
|
70
|
+
- **Adversarial.** Misbehaving-session scenarios — bad-entry poisoning, self-reinforcing loops, stale claims, authority ambiguity, kind gaming.
|
|
71
|
+
- **Integration/deployment.** Migration, backup/restore, multi-machine, config knobs, dashboard surface, rollback.
|
|
72
|
+
- **Lessons-aware.** Loads the canonical Instar Design Principles + Lessons Learned index (`docs/INSTAR-DESIGN-PRINCIPLES-AND-LESSONS.md`) plus the running agent's local `.instar/memory/feedback_*.md` entries, then checks the spec for (a) direct contradictions of documented principles/lessons, (b) applicable lessons the spec fails to engage with, and (c) behavioral lessons violated by agent-facing surfaces the spec proposes. Catches the "Phase 2" anti-pattern and the spec-converge-pre-auth-circular failure mode (see `feedback_spec_converge_pre_auth_circular`).
|
|
73
|
+
|
|
74
|
+
**External reviewer (cross-model, via the agent's own installed codex CLI):**
|
|
75
|
+
|
|
76
|
+
The external "cross-model" pass is a single independent GPT-tier read that sits *outside* the Claude family to catch the blind spots Claude models share. It is **real**, routed through the agent's own `codex login` (no new API key, no new network dependency), and implemented in code — NOT a hand-wave. Run it like this:
|
|
77
|
+
|
|
78
|
+
1. **Detect** whether a supported reviewer framework is installed + authed:
|
|
79
|
+
```bash
|
|
80
|
+
node skills/spec-converge/scripts/cross-model-review.mjs --spec <spec-path> --detect-only
|
|
81
|
+
```
|
|
82
|
+
Returns `{ available, framework?, model?, reason? }`. `available:false` → skip the external pass, set the fallback flag (see §"No-codex fallback" below / Phase 5), and continue internal-only. **Never block.**
|
|
83
|
+
|
|
84
|
+
2. **Run** the external review when available — pass the spec plus the same architectural context docs the internal reviewers receive (the docs the spec references):
|
|
85
|
+
```bash
|
|
86
|
+
node skills/spec-converge/scripts/cross-model-review.mjs \
|
|
87
|
+
--spec <spec-path> \
|
|
88
|
+
--context docs/foo.md --context docs/bar.md
|
|
89
|
+
```
|
|
90
|
+
It emits a JSON `ReviewerResult` on stdout: `{ status, framework?, model?, verdict?, findings?, reason?, flag }`. Fold its `findings` into the round alongside the internal reviewers'. `status:'degraded'` (codex present but the call failed — timeout / error / rate-limited) is a *partial* cross-model pass for the round: fold in whatever came back and record the `degraded` flag — it does **not** collapse to `unavailable`.
|
|
91
|
+
|
|
92
|
+
The detection + invocation + parsing live in the unit-tested `src/core/crossModelReviewer.ts` module (built to `dist/core/crossModelReviewer.js`); the script is a thin file-I/O wrapper. codex is the **first** supported framework in an extensible registry (`SUPPORTED_REVIEWER_FRAMEWORKS`) — gemini-cli and others plug in there later with **no skill change**.
|
|
93
|
+
|
|
94
|
+
Each internal reviewer receives the spec, the architectural context docs referenced in the spec (`docs/signal-vs-authority.md`, `docs/integrated-being.md`, relevant subsystem docs), and a prompt specific to their perspective. Each produces a structured finding list.
|
|
95
|
+
|
|
96
|
+
The **five internal reviewers + the cross-model external pass** run in parallel (the external pass is one cross-model read through the first available supported framework — the honest mechanism, not three phantom API models). Their findings are collected.
|
|
97
|
+
|
|
98
|
+
**Code-backed reviewer — the Standards-Conformance Gate (auto-invoked).** Alongside the internal reviewers + the cross-model external pass, call the live gate: `POST /spec/conformance-check` with the spec (body `{ "specPath": "<path-within-specsDir>" }`, or `{ "markdown": "<spec text>" }`). Unlike the prompt-driven reviewers, the gate is *code that reads the living constitution* (`docs/STANDARDS-REGISTRY.md`) and returns a per-standard report — `ok` / `at-risk` / `n/a` + a reason for every standing standard. Fold its `at-risk` entries into the round's findings. It is the structural complement to the Lessons-aware reviewer: lessons-aware reads the lessons doc + local memory (prompt-driven); the gate reads the constitution itself (code), so a registry edit can never be silently missed. **Signal-only:** advisory — it surfaces violations, it does not block (blocking authority is the separate, later `scg-blocking-authority` follow-up, per *Signal vs. Authority*). **Fail-open:** if the gate is disabled/unreachable (503) or returns `degraded: true`, note that the constitutional pass was not authoritative and continue — a down gate must never stall spec review. (This auto-invocation is the dogfood-to-ship enforcement of the **Self-Hosting** standard — the gate now *runs* at spec-review rather than being a step the author must remember.)
|
|
99
|
+
|
|
100
|
+
**The lessons-aware reviewer is not optional**, even in pattern-instance abbreviated convergence. Abbreviated convergence may skip external models (one round instead of multiple) but must NOT skip the lessons-aware pass — that's the only defense against the circular self-verify problem documented at `feedback_spec_converge_pre_auth_circular`. When a spec author runs convergence on their own spec, the lessons-aware reviewer is the structural check that catches what the author missed.
|
|
101
|
+
|
|
102
|
+
### Phase 2 — Spec update
|
|
103
|
+
|
|
104
|
+
The skill reads all findings, groups duplicates, prioritizes by severity, and rewrites the spec to address each substantive finding. Trivial/cosmetic findings are noted but may be batched.
|
|
105
|
+
|
|
106
|
+
The spec update is ONE coherent edit of the spec document — not one patch per reviewer. The agent treats the findings as a single synthesis input.
|
|
107
|
+
|
|
108
|
+
Every update preserves the spec's structure. Changes are additive (new sections for new concerns) or rewrites of existing sections (when a finding reveals a design flaw).
|
|
109
|
+
|
|
110
|
+
### Phase 3 — Convergence check
|
|
111
|
+
|
|
112
|
+
After the spec is updated, the skill runs another full review round (the five internal reviewers + the cross-model external pass, in parallel, on the updated spec).
|
|
113
|
+
|
|
114
|
+
Convergence criterion: **the new round produces no material new issues.** "Material" means any finding that would require a spec change if unaddressed. Cosmetic findings, repeats of already-addressed concerns, and minor phrasing quibbles are non-material.
|
|
115
|
+
|
|
116
|
+
A lightweight LLM (Haiku-class) compares the new round's findings to the prior round's findings and emits a boolean `converged: true|false` with reasoning. Human-readable comparison log is retained.
|
|
117
|
+
|
|
118
|
+
**Not converged** → back to Phase 2.
|
|
119
|
+
**Converged** → Phase 4.
|
|
120
|
+
|
|
121
|
+
Hard cap: 10 iterations. If the skill hits 10 iterations without convergence, it exits with a `convergence-failed` status and a report explaining why. Human input is required before retry.
|
|
122
|
+
|
|
123
|
+
#### Aggregating per-round cross-model outcomes into ONE spec-level flag
|
|
124
|
+
|
|
125
|
+
Convergence runs **multiple rounds**, but the spec gets **one** final `cross-model-review:` value. Each round's cross-model pass returns a `ReviewerResult` with a per-round status (`ok` / `degraded` / `unavailable`); the skill **tracks the per-round outcomes** and computes the final flag with `aggregateRoundOutcomes(rounds, { skippedAbbreviated })` (exported from `src/core/crossModelReviewer.ts`). The rule:
|
|
126
|
+
|
|
127
|
+
- **`skipped-abbreviated`** — the author opted out of the external pass entirely (abbreviated mode). No round attempted it. (Wins over everything; nothing was tried.)
|
|
128
|
+
- **`codex-cli:<model>`** (the clean RAN flag) — **any** round got a successful external pass. One genuine outside opinion is enough to say the spec received real cross-model review; the freshest successful round's flag is used.
|
|
129
|
+
- **`degraded-all-rounds`** — a framework was present in the rounds but **zero** rounds succeeded (every attempt degraded: timeout / error / rate-limited). This is the case the aggregate exists to surface: **"converged having never once received a real external opinion."** It is treated **as loud as `unavailable`** — it must show up at SPEC level (the banner above + the frontmatter flag), not hide in per-round degraded notes.
|
|
130
|
+
- **`unavailable`** — no supported framework was ever available across the rounds.
|
|
131
|
+
|
|
132
|
+
The point of the aggregate: a spec that degraded on every single round looks, from the per-round notes alone, like it "tried" — but it converged with the SAME assurance as one that had no reviewer at all. `degraded-all-rounds` makes that fact impossible to miss at the spec level. The skill passes the aggregated `flag`/`reason` to `write-convergence-tag.mjs` (Phase 5) and renders the matching banner (Phase 4).
|
|
133
|
+
|
|
134
|
+
### Phase 4 — Convergence report
|
|
135
|
+
|
|
136
|
+
Produce a final report at `docs/specs/reports/<slug>-convergence.md` with the following structure:
|
|
137
|
+
|
|
138
|
+
```markdown
|
|
139
|
+
# Convergence Report — <spec title>
|
|
140
|
+
|
|
141
|
+
## Cross-model review: <STATUS>
|
|
142
|
+
|
|
143
|
+
[A can't-miss banner stating the external (non-Claude) reviewer posture for this convergence — taken from the FINAL spec-level `cross-model-review:` value (see "Aggregating per-round outcomes" below: a single round produces an `ok`/`degraded`/`unavailable` result, but the SPEC gets one final value). **Every non-ran state carries the loud ⚠ marker** — a non-ran state must NEVER read as a clean pass. One of:
|
|
144
|
+
|
|
145
|
+
- `## Cross-model review: codex-cli:<model>` — RAN. A real GPT-tier external pass ran through the agent's codex CLI in at least one round. The ONLY state with no ⚠ (it is the clean pass).
|
|
146
|
+
- `## ⚠ Cross-model review: codex-cli:<model> (degraded: <reason>)` — codex is installed but THIS round's call failed (timeout / error / rate-limited); the external pass was partial. State the reason. (Per-round status; if some OTHER round succeeded, the spec-level flag is the clean `codex-cli:<model>` instead.)
|
|
147
|
+
- `## ⚠ Cross-model review: DEGRADED — ALL ROUNDS (degraded-all-rounds)` — codex was present every round but **ZERO rounds succeeded** (every attempt degraded). The spec converged having **never once received a real external opinion** — as loud as UNAVAILABLE. State the last round's reason. The user reads THIS before applying `approved: true`.
|
|
148
|
+
- `## ⚠ Cross-model review: UNAVAILABLE` — no supported external (non-Claude) reviewer was installed/authed. Convergence ran on the internal Claude reviewers + the constitutional gate ONLY. State the specific reason (`codex-not-installed` / `codex-not-authed` / `codex-auth-apikey-forbidden`) and the one-line remediation (`codex login`, or install `@openai/codex`). The user reads THIS before applying `approved: true`, so the reduced-assurance state is an informed choice, not a silent one.
|
|
149
|
+
- `## ⚠ Cross-model review: SKIPPED (abbreviated convergence)` — the author chose the fast path; the framework may be present but was deliberately skipped (the lessons-aware reviewer still ran). This is a **non-ran** state, so it carries the ⚠ too — "I skipped the outside opinion to save cost" must be as visible as "I had no outside opinion available," not a quiet footnote.]
|
|
150
|
+
|
|
151
|
+
## ELI10 Overview
|
|
152
|
+
|
|
153
|
+
[2-3 paragraph plain-English summary of what the spec is, why it matters, what changes for users if it ships, and what the main tradeoffs are. No jargon. Assume the reader is smart but not a systems engineer. "We're adding a way for different parts of the agent to know what each other is doing..." tone.]
|
|
154
|
+
|
|
155
|
+
## Original vs Converged
|
|
156
|
+
|
|
157
|
+
[A dedicated section describing the major differences between the initial spec and the converged version, also in ELI10 terms. "Originally, any session could write anything to the ledger. After review, we restricted writes to a curated set of server-side sources because an untrusted write surface made too many attacks too easy." This section is what the user reads to understand what the review process actually changed.]
|
|
158
|
+
|
|
159
|
+
## Iteration Summary
|
|
160
|
+
|
|
161
|
+
| Iteration | Reviewers who flagged | Material findings | Spec changes |
|
|
162
|
+
|-----------|-----------------------|-------------------|--------------|
|
|
163
|
+
| 1 | security, adversarial | 14 | ... |
|
|
164
|
+
| 2 | scalability | 3 | ... |
|
|
165
|
+
| 3 | (converged) | 0 | none |
|
|
166
|
+
|
|
167
|
+
## Full Findings Catalog
|
|
168
|
+
|
|
169
|
+
[Every iteration, every reviewer, every finding with: severity, reviewer perspective, original text, resolution taken. Structured for detail-oriented readers.]
|
|
170
|
+
|
|
171
|
+
## Convergence verdict
|
|
172
|
+
|
|
173
|
+
[Plain statement: "Converged at iteration N. No material findings in the final round. Spec is ready for user review and approval."]
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
### Phase 5 — Frontmatter tag
|
|
177
|
+
|
|
178
|
+
On successful convergence, the skill writes to the spec's frontmatter:
|
|
179
|
+
|
|
180
|
+
```yaml
|
|
181
|
+
review-convergence: "<ISO timestamp>"
|
|
182
|
+
review-iterations: <N>
|
|
183
|
+
review-completed-at: "<ISO timestamp>"
|
|
184
|
+
review-report: "docs/specs/reports/<slug>-convergence.md"
|
|
185
|
+
cross-model-review: "<flag>" # codex-cli:<model> | unavailable | degraded-all-rounds | skipped-abbreviated
|
|
186
|
+
cross-model-review-reason: "<reason>" # only when unavailable / degraded / degraded-all-rounds
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
The `cross-model-review` field records the **final spec-level** external-reviewer posture (the `aggregateRoundOutcomes` result — see "Aggregating per-round cross-model outcomes" in Phase 3) so the spec self-documents which external pass it received (or didn't). Pass it through the tag writer with the aggregated `flag`/`reason` (strip the leading `cross-model-review: ` prefix — the script writes the field name itself):
|
|
190
|
+
|
|
191
|
+
```bash
|
|
192
|
+
node skills/spec-converge/scripts/write-convergence-tag.mjs \
|
|
193
|
+
--spec <spec-path> --iterations <N> --report <report-path> \
|
|
194
|
+
--cross-model-review "codex-cli:gpt-5.5" # or "unavailable" / "degraded-all-rounds" / "skipped-abbreviated" / "codex-cli:gpt-5.5 (degraded: timeout)"
|
|
195
|
+
[--cross-model-reason "codex-not-installed"]
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
This is **DISCLOSURE, not a gate** — it does NOT change `/instar-dev`'s `review-convergence` + `approved: true` enforcement. An `unavailable` spec can still be approved (the user reads the report banner and makes an informed choice).
|
|
199
|
+
|
|
200
|
+
**Structural prerequisite — ELI16 overview.** Before the convergence tag is written, `skills/spec-converge/scripts/write-convergence-tag.mjs` verifies the spec ships with a plain-English ELI16 companion at `docs/specs/<slug>.eli16.md` (or the path declared via `eli16-overview:` frontmatter). The companion must be at least 800 characters. If the overview is missing or stub-length, convergence is refused — no tag is written. The dense technical spec is for reviewers; the ELI16 overview is the entry point for any reader who has to make a real decision. See `skills/instar-dev/templates/eli16-overview.md` for the expected shape.
|
|
201
|
+
|
|
202
|
+
The `approved: true` tag is NOT written by this skill. That's the user's step.
|
|
203
|
+
|
|
204
|
+
### Phase 6 — User handoff
|
|
205
|
+
|
|
206
|
+
When a spec is handed to the user for review, it MUST go out with a **rendered, verified ELI16 tunnel link** the user can tap and read — never a bare filename or a PR they have to dig through. This is the required delivery step, enforced structurally (not by memory):
|
|
207
|
+
|
|
208
|
+
```bash
|
|
209
|
+
node skills/spec-converge/scripts/publish-spec-review.mjs \
|
|
210
|
+
--spec docs/specs/<SPEC>.md \
|
|
211
|
+
--pr <github-pr-url> \
|
|
212
|
+
--topic <telegram-topic-id> --send
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
`publish-spec-review.mjs` is the sanctioned path. It (1) resolves the ELI16 companion via the same convention the commit-time gate enforces (`scripts/eli16-overview-check.mjs`) and refuses if it is missing or a stub; (2) renders it as an auth-gated Private View (`POST /view` on port 4042, `INSTAR_AUTH_TOKEN`); (3) **verifies the tunnel link returns HTTP 200 before it is ever sent** — no broken links; (4) composes and delivers the review message (rendered ELI16 link first, full-spec PR link second).
|
|
216
|
+
|
|
217
|
+
Backstop (Structure > Willpower): the pre-messaging gate (`convergence-check.sh`, criterion 8) **blocks** any hand-sent spec-review message that lacks a rendered `/view/` link, so the requirement cannot be silently skipped even if the script is bypassed.
|
|
218
|
+
|
|
219
|
+
The skill does NOT auto-apply `approved: true`. That requires explicit human action — editing the frontmatter or running `instar spec approve <path>` (follow-on CLI command).
|
|
220
|
+
|
|
221
|
+
## What this skill does NOT do
|
|
222
|
+
|
|
223
|
+
- It does not build code. That's `/instar-dev`'s job, after both tags are present.
|
|
224
|
+
- It does not relax convergence criteria to avoid iteration. 10-iteration cap exists to surface "this design is too confused to review" rather than to force false convergence.
|
|
225
|
+
- It does not skip reviewer perspectives. The five internal reviewers + the cross-model external pass run on every round (subject to the abbreviated-convergence exception, which still runs the non-skippable lessons-aware reviewer).
|
|
226
|
+
- It does not auto-approve on behalf of the user. Approval is the user's structural contribution to the process.
|
|
227
|
+
|
|
228
|
+
## Bootstrap exception
|
|
229
|
+
|
|
230
|
+
This skill itself is an instar-source change. The strict reading of the new rule would require IT to have a spec that passed through itself before it could ship. That's a chicken-and-egg loop the skill itself has to break.
|
|
231
|
+
|
|
232
|
+
Documented bootstrap exception: the first commit introducing `/spec-converge` is allowed to pass `/instar-dev`'s pre-commit gate without the spec tags, ONLY because the skill that installs the tag mechanism cannot be gated by itself. This is a one-time exception, visible in the commit history, and will not recur — every subsequent change to this skill or to anything else in instar source will require the full spec-convergence-approval chain.
|
|
233
|
+
|
|
234
|
+
## Anti-patterns the enforcement will catch
|
|
235
|
+
|
|
236
|
+
### "Just one more iteration"
|
|
237
|
+
The convergence check is structural. If the LLM comparator finds new material issues, iteration is required. The agent does not get to overrule this.
|
|
238
|
+
|
|
239
|
+
### "Convergence" with shrinking findings but not zero
|
|
240
|
+
A smaller finding count is NOT convergence. Convergence is zero material findings in a new round.
|
|
241
|
+
|
|
242
|
+
### Skipping a reviewer perspective to ship faster
|
|
243
|
+
All five internal reviewers (security, scalability, adversarial, integration, lessons-aware) AND the cross-model external pass run on every round. Skipping is visible in the iteration log and fails the report validation. During pattern-instance abbreviated convergence, the external cross-model pass may be skipped to save cost — record that honestly as `cross-model-review: skipped-abbreviated` (distinct from `unavailable`: the framework may be present, but the author chose the fast path) — but the lessons-aware reviewer MUST run; it's the only structural defense against the spec-converge-pre-auth-circular failure mode.
|
|
244
|
+
|
|
245
|
+
### Rewriting the spec between iterations to hide findings
|
|
246
|
+
Spec edits must address findings, not evade them. The iteration log records both the finding and the resolution. An edit that changes the spec to make the finding "not applicable" without actually solving the concern is caught at the next review round.
|
|
247
|
+
|
|
248
|
+
### Forging the review-convergence tag
|
|
249
|
+
The tag is written only by this skill's Phase 5. Manual writes of the tag without going through the skill will fail `/instar-dev`'s deeper check (the report file at `docs/specs/reports/<slug>-convergence.md` must exist and match).
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* cross-model-review.mjs — the thin script /spec-converge calls to run the
|
|
4
|
+
* external (non-Claude) cross-model reviewer through the agent's own installed
|
|
5
|
+
* codex CLI (Step B of the tiered development process,
|
|
6
|
+
* docs/specs/codex-crossreview-stepB-spec.md).
|
|
7
|
+
*
|
|
8
|
+
* This is the REAL mechanism that replaces the never-built "/crossreview"
|
|
9
|
+
* placeholder the skill prose referred to. It is a thin wrapper: all the
|
|
10
|
+
* detection, prompt-assembly, provider invocation, and result parsing live in
|
|
11
|
+
* the unit-tested `src/core/crossModelReviewer.ts` module (built to
|
|
12
|
+
* `dist/core/crossModelReviewer.js`). This script only does the file I/O —
|
|
13
|
+
* read the spec + referenced context docs from the repo and hand them to the
|
|
14
|
+
* module — because codex runs read-only in an empty scratch dir with no repo
|
|
15
|
+
* access, so context must be inlined before the spawn.
|
|
16
|
+
*
|
|
17
|
+
* Modes:
|
|
18
|
+
* --detect-only Print detection JSON and exit (no codex spawn).
|
|
19
|
+
* { available, framework?, model?, reason? }
|
|
20
|
+
* (default) Detect; if available, assemble the prompt + run
|
|
21
|
+
* the codex review; print the ReviewerResult JSON.
|
|
22
|
+
*
|
|
23
|
+
* Usage:
|
|
24
|
+
* node skills/spec-converge/scripts/cross-model-review.mjs \
|
|
25
|
+
* --spec docs/specs/<slug>.md \
|
|
26
|
+
* [--context docs/foo.md --context docs/bar.md ...] \
|
|
27
|
+
* [--detect-only] \
|
|
28
|
+
* [--timeout-ms 120000]
|
|
29
|
+
*
|
|
30
|
+
* Output: a single JSON object on stdout (machine-readable for the skill).
|
|
31
|
+
* On detect-only: the CrossModelDetectionResult.
|
|
32
|
+
* On full run: the ReviewerResult ({ status, framework?, model?, verdict?,
|
|
33
|
+
* findings?, reason?, flag }).
|
|
34
|
+
*
|
|
35
|
+
* Exit codes:
|
|
36
|
+
* 0 — ran successfully (INCLUDING the unavailable/degraded outcomes —
|
|
37
|
+
* those are valid disclosed states, never a failure of this script).
|
|
38
|
+
* 1 — usage error or the spec/template/context file could not be read.
|
|
39
|
+
*
|
|
40
|
+
* NEVER blocks convergence: an unavailable or degraded result is printed and
|
|
41
|
+
* exit 0. The skill reads `status` + `flag` to decide what to record.
|
|
42
|
+
*/
|
|
43
|
+
|
|
44
|
+
import fs from 'node:fs';
|
|
45
|
+
import path from 'node:path';
|
|
46
|
+
|
|
47
|
+
const ROOT = path.resolve(new URL('../../..', import.meta.url).pathname);
|
|
48
|
+
const REVIEWER_TEMPLATE_PATH = path.join(
|
|
49
|
+
ROOT,
|
|
50
|
+
'skills',
|
|
51
|
+
'spec-converge',
|
|
52
|
+
'templates',
|
|
53
|
+
'reviewer-cross-model.md',
|
|
54
|
+
);
|
|
55
|
+
|
|
56
|
+
function fail(msg) {
|
|
57
|
+
console.error(msg);
|
|
58
|
+
process.exit(1);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function parseArgs() {
|
|
62
|
+
const args = process.argv.slice(2);
|
|
63
|
+
const out = { spec: null, context: [], detectOnly: false, timeoutMs: null };
|
|
64
|
+
for (let i = 0; i < args.length; i++) {
|
|
65
|
+
const a = args[i];
|
|
66
|
+
if (a === '--spec') out.spec = args[++i];
|
|
67
|
+
else if (a === '--context') out.context.push(args[++i]);
|
|
68
|
+
else if (a === '--detect-only') out.detectOnly = true;
|
|
69
|
+
else if (a === '--timeout-ms') out.timeoutMs = parseInt(args[++i], 10);
|
|
70
|
+
else fail(`Unknown arg: ${a}`);
|
|
71
|
+
}
|
|
72
|
+
if (!out.detectOnly && !out.spec) {
|
|
73
|
+
fail(
|
|
74
|
+
'Usage: cross-model-review.mjs --spec PATH [--context PATH ...] ' +
|
|
75
|
+
'[--detect-only] [--timeout-ms N]',
|
|
76
|
+
);
|
|
77
|
+
}
|
|
78
|
+
return out;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
async function loadModule() {
|
|
82
|
+
// Import the built module. The dev tooling runs in the instar repo where
|
|
83
|
+
// `pnpm build` has produced dist/. (If dist is stale, rebuild first.)
|
|
84
|
+
const modUrl = new URL('../../../dist/core/crossModelReviewer.js', import.meta.url);
|
|
85
|
+
if (!fs.existsSync(modUrl)) {
|
|
86
|
+
fail(
|
|
87
|
+
'dist/core/crossModelReviewer.js not found. Run `pnpm build` (or `npm run build`) ' +
|
|
88
|
+
'before invoking the cross-model reviewer.',
|
|
89
|
+
);
|
|
90
|
+
}
|
|
91
|
+
return import(modUrl.href);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function readRepoFile(rel) {
|
|
95
|
+
const abs = path.resolve(ROOT, rel);
|
|
96
|
+
if (!fs.existsSync(abs)) fail(`File not found: ${rel}`);
|
|
97
|
+
return fs.readFileSync(abs, 'utf-8');
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
async function main() {
|
|
101
|
+
const { spec, context, detectOnly, timeoutMs } = parseArgs();
|
|
102
|
+
const mod = await loadModule();
|
|
103
|
+
|
|
104
|
+
// Detection is the same in both modes.
|
|
105
|
+
const detection = mod.detectCrossModelReviewer();
|
|
106
|
+
|
|
107
|
+
if (detectOnly) {
|
|
108
|
+
process.stdout.write(JSON.stringify(detection) + '\n');
|
|
109
|
+
process.exit(0);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// Unavailable → print the unavailable flag, exit 0. Never block.
|
|
113
|
+
if (!detection.available) {
|
|
114
|
+
const flag = mod.buildCrossModelFlag('unavailable', detection.reason);
|
|
115
|
+
process.stdout.write(
|
|
116
|
+
JSON.stringify({ status: 'unavailable', reason: detection.reason, flag: flag.flag }) + '\n',
|
|
117
|
+
);
|
|
118
|
+
process.exit(0);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// Available → assemble the prompt from disk + run the review.
|
|
122
|
+
const reviewerTemplate = fs.readFileSync(REVIEWER_TEMPLATE_PATH, 'utf-8');
|
|
123
|
+
const specMarkdown = readRepoFile(spec);
|
|
124
|
+
const contextDocs = context.map((rel) => ({ path: rel, content: readRepoFile(rel) }));
|
|
125
|
+
|
|
126
|
+
const assembled = mod.assembleReviewerPrompt({
|
|
127
|
+
reviewerTemplate,
|
|
128
|
+
specMarkdown,
|
|
129
|
+
specPath: spec,
|
|
130
|
+
context: contextDocs,
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
const result = await mod.runCrossModelReview({
|
|
134
|
+
assembled,
|
|
135
|
+
...(Number.isFinite(timeoutMs) ? { timeoutMs } : {}),
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
// Surface truncation in the emitted result so the skill/report can note it.
|
|
139
|
+
process.stdout.write(JSON.stringify({ ...result, promptTruncated: assembled.truncated }) + '\n');
|
|
140
|
+
process.exit(0);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
main().catch((err) => {
|
|
144
|
+
// Even an unexpected crash must not block convergence: emit a degraded
|
|
145
|
+
// result and exit 0 so the skill folds in internal-only + records degraded.
|
|
146
|
+
const reason = err instanceof Error ? err.message.slice(0, 200) : String(err);
|
|
147
|
+
process.stdout.write(
|
|
148
|
+
JSON.stringify({
|
|
149
|
+
status: 'degraded',
|
|
150
|
+
reason: `driver-error: ${reason}`,
|
|
151
|
+
flag: `cross-model-review: codex-cli (degraded: driver-error)`,
|
|
152
|
+
}) + '\n',
|
|
153
|
+
);
|
|
154
|
+
process.exit(0);
|
|
155
|
+
});
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* publish-spec-review.mjs — the sanctioned way to deliver a spec for review.
|
|
4
|
+
*
|
|
5
|
+
* Structure > Willpower: sending a spec for review WITHOUT a rendered,
|
|
6
|
+
* verified ELI16 tunnel link should be impossible, not something the agent
|
|
7
|
+
* remembers to attach. This script makes the correct delivery atomic:
|
|
8
|
+
*
|
|
9
|
+
* 1. Resolve the spec's ELI16 companion using the SAME convention the
|
|
10
|
+
* commit-time gate enforces (scripts/eli16-overview-check.mjs).
|
|
11
|
+
* 2. Refuse if the ELI16 is missing or a stub (reuses checkEli16Overview).
|
|
12
|
+
* 3. Render the ELI16 markdown as an auth-gated Private View (POST /view).
|
|
13
|
+
* 4. VERIFY the tunnel link returns HTTP 200 before it is ever sent —
|
|
14
|
+
* never hand the operator a broken link.
|
|
15
|
+
* 5. Compose the review message (rendered ELI16 link + full-spec PR link)
|
|
16
|
+
* and, with --send, deliver it via telegram-reply.sh.
|
|
17
|
+
*
|
|
18
|
+
* Usage:
|
|
19
|
+
* node skills/spec-converge/scripts/publish-spec-review.mjs \
|
|
20
|
+
* --spec docs/specs/FOO-SPEC.md \
|
|
21
|
+
* --pr https://github.com/JKHeadley/instar/pull/670 \
|
|
22
|
+
* --topic 12476 [--send]
|
|
23
|
+
*
|
|
24
|
+
* Auth: bearer API on port 4042 (NOT 4040 — that is dashboard/PIN auth),
|
|
25
|
+
* token from the INSTAR_AUTH_TOKEN env var (config.json authToken is
|
|
26
|
+
* externalized and will be rejected).
|
|
27
|
+
*/
|
|
28
|
+
|
|
29
|
+
import fs from 'node:fs';
|
|
30
|
+
import path from 'node:path';
|
|
31
|
+
import { spawnSync } from 'node:child_process';
|
|
32
|
+
import { checkEli16Overview } from '../../../scripts/eli16-overview-check.mjs';
|
|
33
|
+
|
|
34
|
+
export const API_PORT = Number(process.env.INSTAR_PORT) || 4042;
|
|
35
|
+
|
|
36
|
+
/** Split a spec file into its frontmatter body and the rest. */
|
|
37
|
+
export function extractFrontmatter(specText) {
|
|
38
|
+
const m = specText.match(/^---\n([\s\S]*?)\n---/);
|
|
39
|
+
return m ? m[1] : '';
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/** Pull a human title from the spec frontmatter (`title:`) or first H1. */
|
|
43
|
+
export function specTitle(specText, fallback) {
|
|
44
|
+
const fm = extractFrontmatter(specText);
|
|
45
|
+
const t = fm.match(/^\s*title\s*:\s*["']?([^"'\n]+)/m);
|
|
46
|
+
if (t) return t[1].trim();
|
|
47
|
+
const h1 = specText.match(/^#\s+(.+)$/m);
|
|
48
|
+
if (h1) return h1[1].trim();
|
|
49
|
+
return fallback;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Compose the operator-facing review message. Pure — no I/O.
|
|
54
|
+
* Leads with the rendered ELI16 link (what the operator reads first),
|
|
55
|
+
* then the full-spec PR for the deep read.
|
|
56
|
+
*/
|
|
57
|
+
export function composeReviewMessage({ title, eli16Url, prUrl }) {
|
|
58
|
+
const lines = [
|
|
59
|
+
`Spec ready for your review: ${title}`,
|
|
60
|
+
'',
|
|
61
|
+
`ELI16 overview (rendered — tap to read): ${eli16Url}`,
|
|
62
|
+
];
|
|
63
|
+
if (prUrl) lines.push(`Full spec + the decisions I need: ${prUrl}`);
|
|
64
|
+
lines.push('', 'Nothing builds until you approve it.');
|
|
65
|
+
return lines.join('\n');
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/** POST the ELI16 markdown to /view; returns { tunnelUrl, localUrl, id }. */
|
|
69
|
+
async function createView({ title, markdown }) {
|
|
70
|
+
const token = process.env.INSTAR_AUTH_TOKEN;
|
|
71
|
+
if (!token) throw new Error('INSTAR_AUTH_TOKEN not set — cannot authenticate to the local API');
|
|
72
|
+
const res = await fetch(`http://localhost:${API_PORT}/view`, {
|
|
73
|
+
method: 'POST',
|
|
74
|
+
headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${token}` },
|
|
75
|
+
body: JSON.stringify({ title, markdown }),
|
|
76
|
+
});
|
|
77
|
+
if (!res.ok) throw new Error(`/view returned HTTP ${res.status} (${await res.text().catch(() => '')})`);
|
|
78
|
+
const d = await res.json();
|
|
79
|
+
if (!d.tunnelUrl) {
|
|
80
|
+
throw new Error('view created but no tunnelUrl — is the tunnel running? (GET /tunnel)');
|
|
81
|
+
}
|
|
82
|
+
return d;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/** Verify a URL renders (HTTP 200) before it is ever sent to the operator. */
|
|
86
|
+
async function verifyUrl(url) {
|
|
87
|
+
const res = await fetch(url, { method: 'GET' });
|
|
88
|
+
return res.status;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function parseArgs(argv) {
|
|
92
|
+
const out = { send: false };
|
|
93
|
+
for (let i = 0; i < argv.length; i++) {
|
|
94
|
+
const a = argv[i];
|
|
95
|
+
if (a === '--spec') out.spec = argv[++i];
|
|
96
|
+
else if (a === '--pr') out.pr = argv[++i];
|
|
97
|
+
else if (a === '--topic') out.topic = argv[++i];
|
|
98
|
+
else if (a === '--send') out.send = true;
|
|
99
|
+
}
|
|
100
|
+
return out;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
async function main() {
|
|
104
|
+
const args = parseArgs(process.argv.slice(2));
|
|
105
|
+
if (!args.spec) {
|
|
106
|
+
console.error('Usage: publish-spec-review.mjs --spec <path> --pr <url> --topic <id> [--send]');
|
|
107
|
+
process.exit(2);
|
|
108
|
+
}
|
|
109
|
+
const specPath = path.resolve(args.spec);
|
|
110
|
+
if (!fs.existsSync(specPath)) {
|
|
111
|
+
console.error(`Spec not found: ${specPath}`);
|
|
112
|
+
process.exit(2);
|
|
113
|
+
}
|
|
114
|
+
const specText = fs.readFileSync(specPath, 'utf8');
|
|
115
|
+
const fm = extractFrontmatter(specText);
|
|
116
|
+
|
|
117
|
+
// GATE: the ELI16 companion must exist and be non-stub (same convention as
|
|
118
|
+
// the commit-time precommit gate). Refuse to publish without it.
|
|
119
|
+
const eli16 = checkEli16Overview(specPath, fm);
|
|
120
|
+
if (!eli16.ok) {
|
|
121
|
+
console.error(`REFUSING to publish: ELI16 overview ${eli16.reason}.`);
|
|
122
|
+
console.error(` Expected sibling: ${eli16.siblingPath} (or a frontmatter \`eli16-overview:\` pointer).`);
|
|
123
|
+
if (eli16.reason === 'too-short') {
|
|
124
|
+
console.error(` Found ${eli16.charCount} chars; need >= ${eli16.minChars}.`);
|
|
125
|
+
}
|
|
126
|
+
process.exit(1);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
const title = specTitle(specText, path.basename(specPath, '.md'));
|
|
130
|
+
const eli16Markdown = fs.readFileSync(eli16.eli16Path, 'utf8');
|
|
131
|
+
|
|
132
|
+
// Render + verify the tunnel link BEFORE composing/sending.
|
|
133
|
+
const view = await createView({ title: `${title} — ELI16`, markdown: eli16Markdown });
|
|
134
|
+
const status = await verifyUrl(view.tunnelUrl);
|
|
135
|
+
if (status !== 200) {
|
|
136
|
+
console.error(`REFUSING to send: rendered ELI16 link did not verify (HTTP ${status}). No broken links.`);
|
|
137
|
+
process.exit(1);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
const message = composeReviewMessage({ title, eli16Url: view.tunnelUrl, prUrl: args.pr });
|
|
141
|
+
|
|
142
|
+
if (args.send) {
|
|
143
|
+
if (!args.topic) {
|
|
144
|
+
console.error('--send requires --topic <id>');
|
|
145
|
+
process.exit(2);
|
|
146
|
+
}
|
|
147
|
+
const reply = spawnSync('bash', ['.instar/scripts/telegram-reply.sh', String(args.topic)], {
|
|
148
|
+
input: message, encoding: 'utf8',
|
|
149
|
+
});
|
|
150
|
+
process.stderr.write(reply.stdout || '');
|
|
151
|
+
process.stderr.write(reply.stderr || '');
|
|
152
|
+
console.error(`\n[published] ELI16 link verified (HTTP 200) and delivered to topic ${args.topic}.`);
|
|
153
|
+
} else {
|
|
154
|
+
// Print the message + the verified link for the caller to send.
|
|
155
|
+
console.log(message);
|
|
156
|
+
console.error(`\n[ok] ELI16 link verified (HTTP 200): ${view.tunnelUrl}`);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// Run only when invoked directly (not when imported by tests).
|
|
161
|
+
if (import.meta.url === `file://${process.argv[1]}`) {
|
|
162
|
+
main().catch(err => {
|
|
163
|
+
console.error(`publish-spec-review failed: ${err.message}`);
|
|
164
|
+
process.exit(1);
|
|
165
|
+
});
|
|
166
|
+
}
|