instar 1.3.482 → 1.3.483
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/package.json +1 -1
- package/skills/spec-converge/SKILL.md +20 -7
- package/skills/spec-converge/scripts/write-convergence-tag.mjs +111 -5
- package/skills/spec-converge/templates/reviewer-decision-completeness.md +83 -0
- package/src/data/builtin-manifest.json +2 -2
- package/upgrades/1.3.483.md +28 -0
- package/upgrades/side-effects/decision-completeness-gate.md +85 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
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.
|
|
3
|
+
description: Iteratively review an instar-development spec with multi-angle internal reviewers (security, scalability, adversarial, integration, decision-completeness, 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
4
|
metadata:
|
|
5
5
|
user_invocable: "false"
|
|
6
6
|
audience: "instar-developing agent only — NOT end users"
|
|
@@ -69,6 +69,7 @@ The skill spawns reviewers in parallel:
|
|
|
69
69
|
- **Scalability/performance.** Hot-path cost, concurrent writes, memory churn, fail-open semantics, hook latency.
|
|
70
70
|
- **Adversarial.** Misbehaving-session scenarios — bad-entry poisoning, self-reinforcing loops, stale claims, authority ambiguity, kind gaming.
|
|
71
71
|
- **Integration/deployment.** Migration, backup/restore, multi-machine, config knobs, dashboard surface, rollback.
|
|
72
|
+
- **Decision-Completeness.** (Autonomy Principle 2 — `docs/specs/AUTONOMY-PRINCIPLES-ENFORCEMENT-SPEC.md` Piece 2.) Enumerates every point where the building agent would have to **stop mid-run and ask the user**. Each must be either **frontloaded** into a `## Frontloaded Decisions` section or explicitly tagged **cheap-to-change-after** because the work ships behind a named dark/dry-run/read-only phase. The reviewer **CONTESTS every cheap tag** — it independently asserts reversibility, and a closed non-cheap taxonomy overrides any tag: anything touching **durable external side-effects, money, identity, or a published/user-visible interface is NEVER cheap**, regardless of a "ships dark" label. A contested tag the reviewer rejects is a **material finding that blocks convergence**. Prompt: `templates/reviewer-decision-completeness.md`. Applies to ALL specs through this skill (D7) — no size gate, no per-spec override (D11; the rejected `disposition: override` escape hatch would reopen the exact skip-hatch Principle 2 closes).
|
|
72
73
|
- **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, (c) behavioral lessons violated by agent-facing surfaces the spec proposes, and **(d) FOUNDATION/SUBSYSTEM AUDIT — the design the spec TESTS, EXTENDS, or BUILDS ON, not just the spec's own surface: does that foundation violate a known standard or repeat a known mistake?** The audit MUST reach one layer below the spec boundary. A spec can be internally clean while faithfully testing or extending a flawed foundation — e.g. a test-harness spec that correctly proves a permission gate which *itself* holds brittle blocking authority in violation of Signal-vs-Authority, or an extension spec built on a subsystem with an unaddressed gap. Taking the underlying subsystem "as given" is exactly how a standards violation survives review: the spec passes, the foundation's flaw is never weighed. When the foundation is flawed, the finding is "this spec is sound but the subsystem it depends on violates standard X / repeats mistake Y — surface it before building on/around it." Catches the "Phase 2" anti-pattern, the spec-converge-pre-auth-circular failure mode (see `feedback_spec_converge_pre_auth_circular`), and the foundation-not-audited gap (the Slack-permission-gate brittle-enum-as-authority that the harness convergence cleared because it audited only the harness, 2026-06-09).
|
|
73
74
|
|
|
74
75
|
**External reviewer (cross-model, via the agent's own installed codex CLI):**
|
|
@@ -93,7 +94,7 @@ The detection + invocation + parsing live in the unit-tested `src/core/crossMode
|
|
|
93
94
|
|
|
94
95
|
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
|
|
|
96
|
-
The **
|
|
97
|
+
The **six 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
|
|
|
98
99
|
**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
|
|
|
@@ -109,9 +110,12 @@ Every update preserves the spec's structure. Changes are additive (new sections
|
|
|
109
110
|
|
|
110
111
|
### Phase 3 — Convergence check
|
|
111
112
|
|
|
112
|
-
After the spec is updated, the skill runs another full review round (the
|
|
113
|
+
After the spec is updated, the skill runs another full review round (the six internal reviewers + the cross-model external pass, in parallel, on the updated spec).
|
|
113
114
|
|
|
114
|
-
Convergence
|
|
115
|
+
Convergence criteria (BOTH must hold — additive, per Autonomy Principle 2):
|
|
116
|
+
|
|
117
|
+
1. **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. (A cheap-to-change-after tag the Decision-Completeness reviewer contested and rejected IS a material finding.)
|
|
118
|
+
2. **Zero unresolved user-decisions remain in `## Open questions`.** A spec cannot converge while a live decision is still parked on the user — every open question must be resolved into a `## Frontloaded Decisions` entry (or a contested-and-surviving cheap-to-change-after tag) before convergence. This is enforced STRUCTURALLY: `write-convergence-tag.mjs` refuses to stamp the tag while `## Open questions` contains unresolved entries, so the criterion cannot be skipped by prose (Structure > Willpower).
|
|
115
119
|
|
|
116
120
|
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
121
|
|
|
@@ -184,17 +188,26 @@ review-completed-at: "<ISO timestamp>"
|
|
|
184
188
|
review-report: "docs/specs/reports/<slug>-convergence.md"
|
|
185
189
|
cross-model-review: "<flag>" # codex-cli:<model> | unavailable | degraded-all-rounds | skipped-abbreviated
|
|
186
190
|
cross-model-review-reason: "<reason>" # only when unavailable / degraded / degraded-all-rounds
|
|
191
|
+
single-run-completable: true # earned, not minted — written only when Open questions reached zero
|
|
192
|
+
frontloaded-decisions: <N> # count from the Decision-Completeness reviewer's final round
|
|
193
|
+
cheap-to-change-tags: <N> # cheap-to-change-after tags that survived contest
|
|
194
|
+
contested-then-cleared: <N> # cheap tags the reviewer contested that were then justified
|
|
187
195
|
```
|
|
188
196
|
|
|
197
|
+
**The tag carries its evidence.** `single-run-completable: true` plus the three counts let a downstream reader (and an audit ratchet on the cheap-tag ratio across specs) see WHAT was frontloaded, not just that a boolean is true. The counts come from the Decision-Completeness reviewer's final-round report; pass them via `--frontloaded-decisions N --cheap-tags N --contested-cleared N`. Scope of the guarantee, honestly: the STRUCTURALLY-earned part is the open-questions invariant — the tag writer REFUSES to stamp convergence while `## Open questions` still contains unresolved entries (entries other than a none-marker like `*(none)*` / `None`), so the tag can never coexist with a live user-decision. The counts themselves are caller-supplied disclosure (the same trust model as `--cross-model-review`); their honesty is the convergence process's, not the script's. Two known parser limits, both backstopped by the Decision-Completeness reviewer: blockquote lines (`>`) under Open questions are treated as commentary by convention (a real question formatted as a blockquote evades the deterministic gate but not the reviewer), and the gate validates the section, not prose elsewhere in the document.
|
|
198
|
+
|
|
189
199
|
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
200
|
|
|
191
201
|
```bash
|
|
192
202
|
node skills/spec-converge/scripts/write-convergence-tag.mjs \
|
|
193
203
|
--spec <spec-path> --iterations <N> --report <report-path> \
|
|
194
204
|
--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"]
|
|
205
|
+
[--cross-model-reason "codex-not-installed"] \
|
|
206
|
+
--frontloaded-decisions <N> --cheap-tags <N> --contested-cleared <N> # Decision-Completeness counts (final round)
|
|
196
207
|
```
|
|
197
208
|
|
|
209
|
+
(Specs converged BEFORE the Decision-Completeness reviewer shipped carry no `single-run-completable` field — that is honest, not a defect; the field binds only specs converged after it. A spec that modifies spec-converge itself runs under the old spec-converge, per the documented bootstrap exception extended in spirit.)
|
|
210
|
+
|
|
198
211
|
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
212
|
|
|
200
213
|
**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.
|
|
@@ -222,7 +235,7 @@ The skill does NOT auto-apply `approved: true`. That requires explicit human act
|
|
|
222
235
|
|
|
223
236
|
- It does not build code. That's `/instar-dev`'s job, after both tags are present.
|
|
224
237
|
- 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
|
|
238
|
+
- It does not skip reviewer perspectives. The six 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 and decision-completeness reviewers).
|
|
226
239
|
- It does not auto-approve on behalf of the user. Approval is the user's structural contribution to the process.
|
|
227
240
|
|
|
228
241
|
## Bootstrap exception
|
|
@@ -240,7 +253,7 @@ The convergence check is structural. If the LLM comparator finds new material is
|
|
|
240
253
|
A smaller finding count is NOT convergence. Convergence is zero material findings in a new round.
|
|
241
254
|
|
|
242
255
|
### Skipping a reviewer perspective to ship faster
|
|
243
|
-
All
|
|
256
|
+
All six internal reviewers (security, scalability, adversarial, integration, decision-completeness, 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
257
|
|
|
245
258
|
### Rewriting the spec between iterations to hide findings
|
|
246
259
|
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.
|
|
@@ -46,6 +46,7 @@
|
|
|
46
46
|
|
|
47
47
|
import fs from 'node:fs';
|
|
48
48
|
import path from 'node:path';
|
|
49
|
+
import { fileURLToPath } from 'node:url';
|
|
49
50
|
import { checkEli16Overview } from '../../../scripts/eli16-overview-check.mjs';
|
|
50
51
|
|
|
51
52
|
function parseArgs() {
|
|
@@ -56,6 +57,11 @@ function parseArgs() {
|
|
|
56
57
|
report: null,
|
|
57
58
|
crossModelReview: null,
|
|
58
59
|
crossModelReason: null,
|
|
60
|
+
// Decision-Completeness counts (Autonomy Principle 2, Piece 2). Optional —
|
|
61
|
+
// when provided, the spec earns `single-run-completable: true` + the counts.
|
|
62
|
+
frontloadedDecisions: null,
|
|
63
|
+
cheapTags: null,
|
|
64
|
+
contestedCleared: null,
|
|
59
65
|
};
|
|
60
66
|
for (let i = 0; i < args.length; i++) {
|
|
61
67
|
const a = args[i];
|
|
@@ -64,6 +70,9 @@ function parseArgs() {
|
|
|
64
70
|
else if (a === '--report') out.report = args[++i];
|
|
65
71
|
else if (a === '--cross-model-review') out.crossModelReview = args[++i];
|
|
66
72
|
else if (a === '--cross-model-reason') out.crossModelReason = args[++i];
|
|
73
|
+
else if (a === '--frontloaded-decisions') out.frontloadedDecisions = parseInt(args[++i], 10);
|
|
74
|
+
else if (a === '--cheap-tags') out.cheapTags = parseInt(args[++i], 10);
|
|
75
|
+
else if (a === '--contested-cleared') out.contestedCleared = parseInt(args[++i], 10);
|
|
67
76
|
else {
|
|
68
77
|
console.error(`Unknown arg: ${a}`);
|
|
69
78
|
process.exit(1);
|
|
@@ -72,22 +81,79 @@ function parseArgs() {
|
|
|
72
81
|
if (!out.spec || !out.report || !Number.isFinite(out.iterations)) {
|
|
73
82
|
console.error(
|
|
74
83
|
'Usage: write-convergence-tag.mjs --spec PATH --iterations N --report PATH ' +
|
|
75
|
-
'[--cross-model-review VALUE] [--cross-model-reason REASON]'
|
|
84
|
+
'[--cross-model-review VALUE] [--cross-model-reason REASON] ' +
|
|
85
|
+
'[--frontloaded-decisions N --cheap-tags N --contested-cleared N]',
|
|
76
86
|
);
|
|
77
87
|
process.exit(1);
|
|
78
88
|
}
|
|
79
89
|
return out;
|
|
80
90
|
}
|
|
81
91
|
|
|
92
|
+
/**
|
|
93
|
+
* Decision-Completeness convergence criterion 2 (Autonomy Principle 2):
|
|
94
|
+
* a spec CANNOT converge while an unresolved user-decision remains in
|
|
95
|
+
* `## Open questions`. Returns the list of unresolved entry lines (empty = ok).
|
|
96
|
+
*
|
|
97
|
+
* Resolution markers that do NOT count as unresolved:
|
|
98
|
+
* - a none-marker line: `*(none)*`, `(none)`, `None`, `None.`, `N/A`
|
|
99
|
+
* - blockquote commentary (`> …`) explaining the section
|
|
100
|
+
* - blank lines / horizontal rules
|
|
101
|
+
* Anything else with content (e.g. a `- **Q1:** …` bullet or a paragraph posing
|
|
102
|
+
* a question) is an unresolved entry.
|
|
103
|
+
*/
|
|
104
|
+
export function findOpenQuestions(specBody) {
|
|
105
|
+
// \b…[^\n]*$ (not \s*$) so heading variants like "## Open questions (round 2)"
|
|
106
|
+
// or "## Open Questions & Decisions" are still recognized — a variant heading
|
|
107
|
+
// must not make the section invisible to the gate (reviewer finding, PR 2).
|
|
108
|
+
const m = specBody.match(/^##\s+Open questions\b[^\n]*$/im);
|
|
109
|
+
if (!m) return []; // no section → nothing parked on the user
|
|
110
|
+
const start = m.index + m[0].length;
|
|
111
|
+
const restAfter = specBody.slice(start);
|
|
112
|
+
const nextHeading = restAfter.search(/^##\s+/m);
|
|
113
|
+
const section = nextHeading === -1 ? restAfter : restAfter.slice(0, nextHeading);
|
|
114
|
+
const NONE_RE = /^\s*[*_]*\(?\s*(none|n\/a)\s*\.?\)?[*_]*\s*$/i;
|
|
115
|
+
return section
|
|
116
|
+
.split('\n')
|
|
117
|
+
.map((l) => l.trim())
|
|
118
|
+
.filter((l) => l.length > 0)
|
|
119
|
+
.filter((l) => !l.startsWith('>')) // blockquote commentary
|
|
120
|
+
.filter((l) => !/^-{3,}$/.test(l)) // horizontal rule
|
|
121
|
+
.filter((l) => !NONE_RE.test(l));
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// ─── main (guarded so the module is importable for tests) ────────────────
|
|
125
|
+
// fileURLToPath (not URL.pathname) so %-encoded paths (spaces) compare correctly,
|
|
126
|
+
// and realpathSync so a symlinked invocation still matches — a mismatch here
|
|
127
|
+
// would otherwise silently exit 0 having done NOTHING (fail-loud lesson).
|
|
128
|
+
const IS_MAIN = (() => {
|
|
129
|
+
if (!process.argv[1]) return false;
|
|
130
|
+
try {
|
|
131
|
+
return (
|
|
132
|
+
fs.realpathSync(path.resolve(process.argv[1])) ===
|
|
133
|
+
fs.realpathSync(fileURLToPath(import.meta.url))
|
|
134
|
+
);
|
|
135
|
+
} catch {
|
|
136
|
+
// @silent-fallback-ok — an unresolvable argv path simply isn't this module.
|
|
137
|
+
return false;
|
|
138
|
+
}
|
|
139
|
+
})();
|
|
140
|
+
if (IS_MAIN) {
|
|
141
|
+
main();
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
function main() {
|
|
82
145
|
const {
|
|
83
146
|
spec: specArg,
|
|
84
147
|
iterations,
|
|
85
148
|
report: reportArg,
|
|
86
149
|
crossModelReview,
|
|
87
150
|
crossModelReason,
|
|
151
|
+
frontloadedDecisions,
|
|
152
|
+
cheapTags,
|
|
153
|
+
contestedCleared,
|
|
88
154
|
} = parseArgs();
|
|
89
155
|
|
|
90
|
-
const ROOT = path.resolve(new URL('../../..', import.meta.url)
|
|
156
|
+
const ROOT = path.resolve(fileURLToPath(new URL('../../..', import.meta.url)));
|
|
91
157
|
const specPath = path.resolve(ROOT, specArg);
|
|
92
158
|
const reportPath = path.resolve(ROOT, reportArg);
|
|
93
159
|
|
|
@@ -130,6 +196,21 @@ if (!_eli16Result.ok) {
|
|
|
130
196
|
process.exit(1);
|
|
131
197
|
}
|
|
132
198
|
|
|
199
|
+
// ─── Open-questions gate (Decision-Completeness, Autonomy Principle 2) ────
|
|
200
|
+
// Convergence criterion 2: a spec CANNOT converge while an unresolved
|
|
201
|
+
// user-decision remains in `## Open questions`. Structural — prose can't skip it.
|
|
202
|
+
const openQuestions = findOpenQuestions(content);
|
|
203
|
+
if (openQuestions.length > 0) {
|
|
204
|
+
console.error(
|
|
205
|
+
`Spec ${specArg} still has ${openQuestions.length} unresolved entr${openQuestions.length === 1 ? 'y' : 'ies'} in ## Open questions:\n` +
|
|
206
|
+
openQuestions.map((q) => ` • ${q.slice(0, 120)}`).join('\n') +
|
|
207
|
+
`\n\nA spec cannot converge while a user-decision is still open (Autonomy Principle 2).\n` +
|
|
208
|
+
`Resolve each into ## Frontloaded Decisions (or a contested-and-surviving\n` +
|
|
209
|
+
`cheap-to-change-after tag), leave the section reading "*(none)*", then re-run.`,
|
|
210
|
+
);
|
|
211
|
+
process.exit(1);
|
|
212
|
+
}
|
|
213
|
+
|
|
133
214
|
// Parse YAML frontmatter manually (no dependency).
|
|
134
215
|
// Expect: /^---\n<body>\n---\n<rest>/
|
|
135
216
|
const FM_RE = /^---\n([\s\S]*?)\n---\n([\s\S]*)$/;
|
|
@@ -143,8 +224,9 @@ if (!match) {
|
|
|
143
224
|
}
|
|
144
225
|
const [, fmBody, rest] = match;
|
|
145
226
|
|
|
146
|
-
// Strip any existing managed lines (review-* chain + cross-model-review chain
|
|
147
|
-
// so re-runs are idempotent — the field is
|
|
227
|
+
// Strip any existing managed lines (review-* chain + cross-model-review chain +
|
|
228
|
+
// the single-run-completable chain) so re-runs are idempotent — the field is
|
|
229
|
+
// rewritten, never duplicated.
|
|
148
230
|
const preservedLines = fmBody
|
|
149
231
|
.split('\n')
|
|
150
232
|
.filter(
|
|
@@ -154,7 +236,11 @@ const preservedLines = fmBody
|
|
|
154
236
|
!/^\s*review-completed-at\s*:/.test(l) &&
|
|
155
237
|
!/^\s*review-report\s*:/.test(l) &&
|
|
156
238
|
!/^\s*cross-model-review\s*:/.test(l) &&
|
|
157
|
-
!/^\s*cross-model-review-reason\s*:/.test(l)
|
|
239
|
+
!/^\s*cross-model-review-reason\s*:/.test(l) &&
|
|
240
|
+
!/^\s*single-run-completable\s*:/.test(l) &&
|
|
241
|
+
!/^\s*frontloaded-decisions\s*:/.test(l) &&
|
|
242
|
+
!/^\s*cheap-to-change-tags\s*:/.test(l) &&
|
|
243
|
+
!/^\s*contested-then-cleared\s*:/.test(l),
|
|
158
244
|
)
|
|
159
245
|
.join('\n')
|
|
160
246
|
.trim();
|
|
@@ -186,6 +272,25 @@ if (crossModelReview) {
|
|
|
186
272
|
}
|
|
187
273
|
}
|
|
188
274
|
|
|
275
|
+
// Decision-Completeness evidence (Autonomy Principle 2). The tag is EARNED:
|
|
276
|
+
// it is only written here, after the open-questions gate above passed, and it
|
|
277
|
+
// carries the reviewer's final-round counts so a downstream reader sees WHAT
|
|
278
|
+
// was frontloaded, not just that a boolean is true.
|
|
279
|
+
if (
|
|
280
|
+
Number.isFinite(frontloadedDecisions) ||
|
|
281
|
+
Number.isFinite(cheapTags) ||
|
|
282
|
+
Number.isFinite(contestedCleared)
|
|
283
|
+
) {
|
|
284
|
+
newFmLines.push('single-run-completable: true');
|
|
285
|
+
if (Number.isFinite(frontloadedDecisions)) {
|
|
286
|
+
newFmLines.push(`frontloaded-decisions: ${frontloadedDecisions}`);
|
|
287
|
+
}
|
|
288
|
+
if (Number.isFinite(cheapTags)) newFmLines.push(`cheap-to-change-tags: ${cheapTags}`);
|
|
289
|
+
if (Number.isFinite(contestedCleared)) {
|
|
290
|
+
newFmLines.push(`contested-then-cleared: ${contestedCleared}`);
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
|
|
189
294
|
const newFm = newFmLines.join('\n');
|
|
190
295
|
|
|
191
296
|
const newContent = `---\n${newFm}\n---\n${rest}`;
|
|
@@ -197,3 +302,4 @@ console.log(
|
|
|
197
302
|
` review-iterations=${iterations}\n` +
|
|
198
303
|
` review-report=${reportRel}`,
|
|
199
304
|
);
|
|
305
|
+
}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
# Reviewer Prompt — Decision Completeness Perspective
|
|
2
|
+
|
|
3
|
+
You are the decision-completeness reviewer for an instar spec under convergence review.
|
|
4
|
+
|
|
5
|
+
Read these in order:
|
|
6
|
+
|
|
7
|
+
1. The spec file at {SPEC_PATH}
|
|
8
|
+
2. Any architectural doc the spec references.
|
|
9
|
+
|
|
10
|
+
Your DECISION-COMPLETENESS perspective (Autonomy Principle 2 — "frontload all user
|
|
11
|
+
decisions into the spec so the agent completes it in a SINGLE autonomous run"): your
|
|
12
|
+
ONLY job is to find every point where the building agent would have to **stop
|
|
13
|
+
mid-run and ask the user**, and force each one to be settled NOW, in the spec —
|
|
14
|
+
because agents build at 100–1000x behind dark/dry-run/read-only phases, a decision is
|
|
15
|
+
cheaper to change *after* a completed run than to stop-and-wait mid-run for.
|
|
16
|
+
|
|
17
|
+
Enumerate every mid-run stop-and-ask point. Each MUST be one of:
|
|
18
|
+
|
|
19
|
+
1. **Frontloaded** — pulled into the spec's `## Frontloaded Decisions` section as a
|
|
20
|
+
concrete decision (made by the author, attributable, reversible-or-not stated). A
|
|
21
|
+
decision the user must make belongs in `## Open questions` — and the spec CANNOT
|
|
22
|
+
converge while any remain (the convergence criterion enforces this; your job is to
|
|
23
|
+
FIND the buried ones that never made it into either section).
|
|
24
|
+
|
|
25
|
+
2. **Cheap-to-change-after** — explicitly tagged as a default safe to pick now and
|
|
26
|
+
change post-run, *because* the work ships behind a named dark / dry-run /
|
|
27
|
+
read-only phase.
|
|
28
|
+
|
|
29
|
+
**CONTEST every cheap-to-change-after tag.** Do not merely check that a phase is
|
|
30
|
+
named — independently assert reversibility. A closed NON-CHEAP taxonomy overrides any
|
|
31
|
+
tag: anything touching
|
|
32
|
+
|
|
33
|
+
- **durable external side-effects** (sent messages, published pages, external-system
|
|
34
|
+
writes, deleted data),
|
|
35
|
+
- **money** (spend, billing, subscriptions),
|
|
36
|
+
- **identity** (keys, principals, operator binding, trust levels), or
|
|
37
|
+
- **a published / user-visible interface** (API contracts others consume, dashboards
|
|
38
|
+
users rely on, on-disk formats other tools read)
|
|
39
|
+
|
|
40
|
+
is NEVER cheap-to-change-after, regardless of a "ships dark" label — the dark phase
|
|
41
|
+
ends, and what happened during it does not un-happen. A contested tag you reject is a
|
|
42
|
+
**MATERIAL finding that blocks convergence** — same authority as any other material
|
|
43
|
+
issue.
|
|
44
|
+
|
|
45
|
+
Specifically check:
|
|
46
|
+
|
|
47
|
+
1. **Buried decisions** — sentences like "the implementer can decide", "TBD", "we
|
|
48
|
+
could either X or Y", "depending on user preference", "ask the user when" anywhere
|
|
49
|
+
in the body. Each is an un-frontloaded decision hiding outside `## Open questions`.
|
|
50
|
+
|
|
51
|
+
2. **Unstated defaults** — config values, thresholds, naming, storage choices the
|
|
52
|
+
spec leaves open. Each needs a concrete default (frontloaded) or a contested-and-
|
|
53
|
+
surviving cheap tag.
|
|
54
|
+
|
|
55
|
+
3. **Conditional scope** — "if X turns out to be true, then…" branches whose
|
|
56
|
+
resolution requires a human answer mid-build. Force the branch to be decided now
|
|
57
|
+
or restructured so either outcome is buildable without asking.
|
|
58
|
+
|
|
59
|
+
4. **Approval points** — any step where the spec says to pause for sign-off mid-run
|
|
60
|
+
(deploy gates, "confirm with the user before…"). Distinguish a genuine
|
|
61
|
+
operator-only authority (legitimate — but then the spec must scope the single run
|
|
62
|
+
to END before it, with the gate as the run boundary) from ceremony (a material
|
|
63
|
+
finding: remove it or convert it to a post-run review).
|
|
64
|
+
|
|
65
|
+
5. **Cheap-tag audit** — for every existing cheap-to-change-after tag: does the named
|
|
66
|
+
dark/dry-run/read-only phase actually exist in the spec's rollout plan? Does the
|
|
67
|
+
tagged decision hit the non-cheap taxonomy above? Reject violators.
|
|
68
|
+
|
|
69
|
+
6. **Open-questions hygiene** — does `## Open questions` exist, and is every entry a
|
|
70
|
+
genuine user decision (not a design question the author should answer)? An
|
|
71
|
+
author-answerable question parked on the user is a deferral, not a decision.
|
|
72
|
+
|
|
73
|
+
Produce a SHORT report (under 400 words):
|
|
74
|
+
|
|
75
|
+
- **Verdict: CLEAN, MINOR ISSUES, or SERIOUS ISSUES**
|
|
76
|
+
- Counts: `frontloaded-decisions: N`, `cheap-tags: N`, `contested-then-cleared: N`,
|
|
77
|
+
`contested-rejected: N`, `open-user-decisions: N`
|
|
78
|
+
- Specific findings with spec-section references and concrete resolutions (which
|
|
79
|
+
section the decision must move to, what default you recommend, which cheap tag you
|
|
80
|
+
reject and why).
|
|
81
|
+
|
|
82
|
+
Be rigorous. A spec converges only when an agent could complete it in ONE autonomous
|
|
83
|
+
run without a single mid-run "what do you want me to do?"
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$schema": "./builtin-manifest.schema.json",
|
|
3
3
|
"schemaVersion": 1,
|
|
4
|
-
"generatedAt": "2026-06-10T23:
|
|
5
|
-
"instarVersion": "1.3.
|
|
4
|
+
"generatedAt": "2026-06-10T23:23:57.878Z",
|
|
5
|
+
"instarVersion": "1.3.483",
|
|
6
6
|
"entryCount": 201,
|
|
7
7
|
"entries": {
|
|
8
8
|
"hook:session-start": {
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# Upgrade Guide — vNEXT
|
|
2
|
+
|
|
3
|
+
<!-- assembled-by: assemble-next-md -->
|
|
4
|
+
<!-- bump: patch -->
|
|
5
|
+
|
|
6
|
+
## What Changed
|
|
7
|
+
|
|
8
|
+
Implements Piece 2 of `docs/specs/AUTONOMY-PRINCIPLES-ENFORCEMENT-SPEC.md` (Autonomy Principle 2 — frontload all user decisions so a spec completes in ONE autonomous run). Three parts, all in the agent-private spec-converge skill (no runtime `src/` surface):
|
|
9
|
+
|
|
10
|
+
1. **New internal reviewer** (`reviewer-decision-completeness.md`, 6th of six): enumerates every mid-run stop-and-ask-the-user point; each must be frontloaded into `## Frontloaded Decisions` or tagged cheap-to-change-after behind a named dark/dry-run/read-only phase. The reviewer CONTESTS every cheap tag against a closed non-cheap taxonomy (durable external side-effects, money, identity, published/user-visible interface — never cheap); a rejected tag is a material finding that blocks convergence. Applies to ALL specs (D7), no per-spec override (D11).
|
|
11
|
+
2. **New convergence criterion** (Phase 3): a spec cannot converge while `## Open questions` contains an unresolved user-decision.
|
|
12
|
+
3. **Structural enforcement + earned evidence** (`write-convergence-tag.mjs`): the tag writer refuses to stamp `review-convergence` while open questions remain, and writes `single-run-completable: true` plus the reviewer's counts (`frontloaded-decisions`, `cheap-to-change-tags`, `contested-then-cleared`) — the tag carries its evidence, earned not minted. Pre-existing converged specs are unaffected (the gate fires at stamp time only).
|
|
13
|
+
|
|
14
|
+
The spec's migration decision is resolved and recorded: spec-converge stays **agent-private** (matching `/instar-dev`'s not-user-facing status) — deliberately NOT promoted to the fleet builtin skill set, so no fleet migration surface exists.
|
|
15
|
+
|
|
16
|
+
## What to Tell Your User
|
|
17
|
+
|
|
18
|
+
None — internal change (no user-facing surface).
|
|
19
|
+
|
|
20
|
+
## Summary of New Capabilities
|
|
21
|
+
|
|
22
|
+
None — internal change (no user-facing surface).
|
|
23
|
+
|
|
24
|
+
## Evidence
|
|
25
|
+
|
|
26
|
+
- `tests/unit/write-convergence-tag-decision-completeness.test.ts`: 15 new tests — none-marker variants, section scoping, refuse-on-live-question (tag NOT written), stamp-on-resolved, earned counts, no-counts → no minted tag, idempotent re-runs.
|
|
27
|
+
- `tests/unit/write-convergence-tag-crossmodel.test.ts`: 16 existing tests green (no regression from the import-safe main-guard restructure).
|
|
28
|
+
- Live functional run: refused a spec with `- **Q1:** should we do A or B?` (exit 1 + remediation message); stamped `single-run-completable: true` + counts once the section read `*(none)*`.
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
# Side-Effects Review — Decision-Completeness Gate in spec-converge (Autonomy Principles Enforcement, Piece 2)
|
|
2
|
+
|
|
3
|
+
**Version / slug:** `decision-completeness-gate`
|
|
4
|
+
**Date:** `2026-06-10`
|
|
5
|
+
**Author:** `echo`
|
|
6
|
+
**Second-pass reviewer:** `independent reviewer subagent — concern raised, all items resolved (see below)`
|
|
7
|
+
|
|
8
|
+
## Summary of the change
|
|
9
|
+
|
|
10
|
+
Implements Piece 2 of `docs/specs/AUTONOMY-PRINCIPLES-ENFORCEMENT-SPEC.md`: makes single-run-completability **provable** in spec-converge. Three parts:
|
|
11
|
+
|
|
12
|
+
1. **New internal reviewer** (`templates/reviewer-decision-completeness.md`, 6th of six): enumerates every mid-run stop-and-ask-the-user point; each must be **frontloaded** into `## Frontloaded Decisions` or tagged **cheap-to-change-after** behind a named dark/dry-run/read-only phase. The reviewer **CONTESTS every cheap tag** against a closed non-cheap taxonomy (durable external side-effects, money, identity, published/user-visible interface — NEVER cheap); a rejected tag is a material finding that blocks convergence.
|
|
13
|
+
2. **New convergence criterion** (SKILL.md Phase 3): a spec cannot converge while `## Open questions` contains an unresolved user-decision — additive to "no material new issues."
|
|
14
|
+
3. **Structural enforcement + earned evidence** (`write-convergence-tag.mjs`): the tag writer now REFUSES to stamp `review-convergence` while open questions remain (criterion 2 cannot be skipped by prose), and writes `single-run-completable: true` + the reviewer's counts (`frontloaded-decisions`, `cheap-to-change-tags`, `contested-then-cleared`) so the tag carries its evidence — earned, not minted. The script gained an import-safe main guard so the parser (`findOpenQuestions`) is unit-testable.
|
|
15
|
+
|
|
16
|
+
Files: `skills/spec-converge/SKILL.md`, `skills/spec-converge/templates/reviewer-decision-completeness.md` (new), `skills/spec-converge/scripts/write-convergence-tag.mjs`, `tests/unit/write-convergence-tag-decision-completeness.test.ts` (new, 15 tests).
|
|
17
|
+
|
|
18
|
+
## Decision-point inventory
|
|
19
|
+
|
|
20
|
+
- `write-convergence-tag.mjs` open-questions gate — **add** — a deterministic, commit-time text validator: refuses the convergence stamp while `## Open questions` has unresolved entries. (Hard-invariant class, see §4.)
|
|
21
|
+
- Convergence criterion 2 (SKILL.md) — **add** — prose criterion backed by the structural gate above.
|
|
22
|
+
- Decision-Completeness reviewer — **add** — produces findings (signals) folded into the round; blocking authority remains the convergence process itself.
|
|
23
|
+
- `single-run-completable` frontmatter — **add** — disclosure only; does NOT change /instar-dev's `review-convergence` + `approved` enforcement.
|
|
24
|
+
|
|
25
|
+
---
|
|
26
|
+
|
|
27
|
+
## 1. Over-block
|
|
28
|
+
|
|
29
|
+
The open-questions gate could refuse a spec whose `## Open questions` section contains commentary that *looks* unresolved. Mitigated: blockquote lines (`>`), none-markers (`*(none)*`, `(none)`, `None`, `None.`, `N/A`, emphasis variants), blank lines, and horizontal rules are all recognized as resolved; only contentful entries refuse. Unit-tested per variant. Residual: a spec author writing prose commentary as plain (non-blockquote) text under Open questions gets refused — acceptable, the error message names the fix and the convention (blockquote commentary) is already what existing specs use (this very spec's Piece-1 sibling used `> Per Principle 2…` + `*(none)*`, which passes).
|
|
30
|
+
|
|
31
|
+
## 2. Under-block
|
|
32
|
+
|
|
33
|
+
Two honest gaps, both by design: (a) the gate validates the SECTION, not the whole document — a user-decision buried in prose outside `## Open questions` is the REVIEWER's job to find (LLM judgment), not the deterministic gate's; (b) an author could delete the question instead of resolving it — but the Decision-Completeness reviewer re-reads the full spec every round and the deleted-but-unresolved decision resurfaces as a finding (the same "rewriting the spec to hide findings" anti-pattern the skill already documents and catches).
|
|
34
|
+
|
|
35
|
+
## 3. Level-of-abstraction fit
|
|
36
|
+
|
|
37
|
+
Correct: the deterministic part (is the section empty?) is a cheap structural validator at the tag-writer boundary — the same layer as the existing ELI16-presence check it sits beside. The judgment part (is this REALLY cheap-to-change-after? is that REALLY the user's decision?) lives in the LLM reviewer. Neither re-implements the other.
|
|
38
|
+
|
|
39
|
+
## 4. Signal vs authority compliance
|
|
40
|
+
|
|
41
|
+
**Required reference:** docs/signal-vs-authority.md
|
|
42
|
+
|
|
43
|
+
- [x] No — the new blocking surface is **hard-invariant validation at a tool boundary**, not a judgment gate.
|
|
44
|
+
|
|
45
|
+
The open-questions gate is the "structural validators at the boundary of the system" case the principle explicitly allows (like "this field must be a number"): it checks section emptiness against an enumerable marker set — zero judgment, deterministic, with a clear remediation message. The *judgment* calls (contesting cheap tags, finding buried decisions) are the reviewer's — and the reviewer produces findings (signals) that block only through the existing convergence process, exactly like the other five reviewers. No brittle check gained judgment authority.
|
|
46
|
+
|
|
47
|
+
## 5. Interactions
|
|
48
|
+
|
|
49
|
+
- **Shadowing:** the gate runs after the ELI16 check inside the same script — ordering is irrelevant (both must pass; neither consumes the other's input).
|
|
50
|
+
- **Existing callers:** `write-convergence-tag.mjs` is invoked only by the spec-converge skill flow. Its existing tests (`write-convergence-tag-crossmodel.test.ts`, 5 tests) pass unchanged — the new args are optional and the main-guard restructure preserves CLI behavior (IS_MAIN hardened with realpath + fileURLToPath after second-pass review so a symlinked or %-encoded invocation cannot silently no-op).
|
|
51
|
+
- **Pre-existing converged specs:** unaffected — the gate fires at stamp time only; already-stamped specs are never re-validated. Specs converged before this ships simply lack `single-run-completable` (honest, documented in SKILL.md).
|
|
52
|
+
- **Idempotency:** re-runs strip and rewrite the new fields exactly like the review-* chain (tested).
|
|
53
|
+
|
|
54
|
+
## 6. External surfaces
|
|
55
|
+
|
|
56
|
+
None at runtime. This is skill-content + a repo script + tests: no `src/` change, no API, no fleet migration surface (spec-converge is **agent-private** — deliberately NOT in the builtin skill set, matching `/instar-dev`'s explicit not-user-facing status; the spec's "promote vs declare agent-private" decision is resolved as **agent-private**, recorded here). Future instar-dev specs converge under the stricter criteria — that is the intended effect.
|
|
57
|
+
|
|
58
|
+
## 7. Rollback cost
|
|
59
|
+
|
|
60
|
+
Trivial: revert the commit. No persistent state, no migration, no user-visible runtime surface. Specs stamped in the interim keep their earned fields (harmless disclosure).
|
|
61
|
+
|
|
62
|
+
## Conclusion
|
|
63
|
+
|
|
64
|
+
Piece 2 lands the spec's design with the criterion enforced structurally (Structure > Willpower) rather than as prose: the tag writer is now the chokepoint that makes "a spec cannot converge with open user-decisions" unskippable, and the `single-run-completable` tag carries its evidence counts. The one new blocking surface is hard-invariant validation, explicitly inside the principle's allowed class. Verified by 15 new unit tests + the 16 existing cross-model tag tests + live functional runs (refuses on a live question; stamps with counts once resolved). Clear to ship as PR 3-of-3's sibling (PR 2).
|
|
65
|
+
|
|
66
|
+
---
|
|
67
|
+
|
|
68
|
+
## Second-pass review
|
|
69
|
+
|
|
70
|
+
**Reviewer:** independent reviewer subagent (adversarial audit of artifact + code + tests, ran the suites).
|
|
71
|
+
**Independent read of the artifact: concern raised → all resolved this pass.**
|
|
72
|
+
|
|
73
|
+
- MUST-FIX (resolved): the IS_MAIN guard could silently exit 0 (doing nothing) when invoked via a symlink or a %-encodable path — safe direction (tag never written without the checks) but a fail-loud violation. Fixed: realpathSync + fileURLToPath comparison; the pre-existing `ROOT` URL-pathname decode bug rode the same fix.
|
|
74
|
+
- MUST-FIX (resolved): this artifact overstated the existing cross-model test count (claimed 16; vitest counts 5). Corrected above.
|
|
75
|
+
- NICE-TO-HAVE (applied): heading-variant false-pass (`## Open questions (round 2)` was invisible to the gate) — regex loosened to `\b[^\n]*$`.
|
|
76
|
+
- NICE-TO-HAVE (applied): SKILL.md now scopes the "earned" claim honestly (the structural guarantee is the open-questions invariant; the counts are caller-supplied disclosure on the same trust model as `--cross-model-review`) and names the blockquote-commentary convention's limit with its reviewer backstop.
|
|
77
|
+
- Confirmed clean: signal-vs-authority compliance (deterministic hard-invariant validation; all judgment in the LLM reviewer), taxonomy fidelity to the spec, and test honesty (refuse → exit 1 AND tag absent; idempotency).
|
|
78
|
+
|
|
79
|
+
---
|
|
80
|
+
|
|
81
|
+
## Evidence pointers
|
|
82
|
+
|
|
83
|
+
- `tests/unit/write-convergence-tag-decision-completeness.test.ts` — 15 tests: parser none-marker variants, section-scoping, refuse-on-live-question (and tag NOT written), stamp-on-resolved, earned counts, no-counts → no minted tag, idempotent re-runs.
|
|
84
|
+
- `tests/unit/write-convergence-tag-crossmodel.test.ts` — 16 existing tests green (no regression from the main-guard restructure).
|
|
85
|
+
- Live functional run: refused `- **Q1:** should we do A or B?` with exit 1 + remediation message; stamped `single-run-completable: true` + counts on the `*(none)*` variant.
|