instar 1.3.956 → 1.3.957
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
CHANGED
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Generate a spec's IMPLEMENTATION CONTRACT — the normative sections only,
|
|
4
|
+
* with the review history stripped out.
|
|
5
|
+
*
|
|
6
|
+
* Why this exists (outbound-gate-advisory-override rounds 22/23/24): a spec that
|
|
7
|
+
* records its own review honestly accumulates change logs describing designs that
|
|
8
|
+
* were REVERSED. Both external reviewers independently named the same risk — an
|
|
9
|
+
* implementer cargo-culting a retired term out of a change log — and the same
|
|
10
|
+
* fix: publish the contract separately from the history.
|
|
11
|
+
*
|
|
12
|
+
* The rule is deliberately dumb and mechanical: everything up to the first
|
|
13
|
+
* history heading is the contract; everything from there on is history. A
|
|
14
|
+
* generator that had to understand the document would drift like the document.
|
|
15
|
+
*
|
|
16
|
+
* Usage:
|
|
17
|
+
* node scripts/generate-spec-contract.mjs --spec docs/specs/<slug>.md
|
|
18
|
+
* node scripts/generate-spec-contract.mjs --spec <path> --check # CI: fail if stale
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
import fs from 'node:fs';
|
|
22
|
+
import path from 'node:path';
|
|
23
|
+
|
|
24
|
+
/** A heading that begins the review-history half. Matched case-insensitively. */
|
|
25
|
+
const HISTORY_HEADING_RE = /^##\s+(?:\d+\.\s+)?(?:Round-\d+\b[^\n]*?(?:change log|hand-check|consistency sweep)|Appendix\b)/i;
|
|
26
|
+
|
|
27
|
+
/** Headings that are contract even though they sort after a history heading. */
|
|
28
|
+
const ALWAYS_CONTRACT_RE = /^##\s+(?:\d+\.\s+)?(?:Decision points touched|Open questions|Frontloaded Decisions|Dependencies|Honest limits|Multi-machine posture|What this does not do)/i;
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Inline review annotations inside NORMATIVE prose — "(round-12, codex — ...)",
|
|
32
|
+
* "*(Round-25, codex: ...)*". They are provenance, not contract. Both external
|
|
33
|
+
* reviewers named them as the reason §§0-11 still read as history (rounds 24/25).
|
|
34
|
+
* Stripped from the generated contract; untouched in the source spec, where they
|
|
35
|
+
* are the record of why a decision is what it is.
|
|
36
|
+
*
|
|
37
|
+
* Deliberately conservative: only fully-delimited groups are removed, so a
|
|
38
|
+
* malformed annotation is left visible rather than silently eating prose.
|
|
39
|
+
*/
|
|
40
|
+
const INLINE_ANNOTATION_RES = [
|
|
41
|
+
// *( Round-N ... )* — a complete italic aside
|
|
42
|
+
/\*\((?:round|rounds)[- ]\d+[^()*]*\)\*/gi,
|
|
43
|
+
// ( round-N, reviewer — ... ) — a complete parenthetical with no nesting
|
|
44
|
+
/\s*\((?:round|rounds)[- ]\d+[^()]*\)/gi,
|
|
45
|
+
// — round-N, codex … (an em-dash aside running to the end of a sentence)
|
|
46
|
+
/\s+—\s+(?:round|rounds)[- ]\d+,[^.;]*(?=[.;])/gi,
|
|
47
|
+
];
|
|
48
|
+
|
|
49
|
+
export function stripInlineAnnotations(text) {
|
|
50
|
+
let out = text;
|
|
51
|
+
for (const re of INLINE_ANNOTATION_RES) out = out.replace(re, '');
|
|
52
|
+
// Collapse the double spaces a removal can leave mid-sentence.
|
|
53
|
+
return out.replace(/[ \t]{2,}/g, ' ').replace(/ +([.,;:])/g, '$1');
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export function splitContract(markdown) {
|
|
57
|
+
const lines = markdown.split('\n');
|
|
58
|
+
const kept = [];
|
|
59
|
+
let inHistory = false;
|
|
60
|
+
let droppedSections = 0;
|
|
61
|
+
for (const line of lines) {
|
|
62
|
+
if (/^##\s/.test(line)) {
|
|
63
|
+
if (HISTORY_HEADING_RE.test(line) && !ALWAYS_CONTRACT_RE.test(line)) {
|
|
64
|
+
if (!inHistory) inHistory = true;
|
|
65
|
+
droppedSections++;
|
|
66
|
+
continue;
|
|
67
|
+
}
|
|
68
|
+
// A non-history H2 ends a history run (sections are not strictly ordered).
|
|
69
|
+
inHistory = false;
|
|
70
|
+
}
|
|
71
|
+
if (!inHistory) kept.push(line);
|
|
72
|
+
}
|
|
73
|
+
const body = stripInlineAnnotations(kept.join('\n')).replace(/\n{4,}/g, '\n\n\n');
|
|
74
|
+
return { contract: body, droppedSections };
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/** The banner that makes the generated file unmistakable and un-editable-by-hand. */
|
|
78
|
+
function banner(specRel) {
|
|
79
|
+
return [
|
|
80
|
+
'<!-- GENERATED FILE — DO NOT EDIT.',
|
|
81
|
+
` Source: ${specRel}`,
|
|
82
|
+
' Regenerate: node scripts/generate-spec-contract.mjs --spec ' + specRel,
|
|
83
|
+
' This is the IMPLEMENTATION CONTRACT: the normative design only.',
|
|
84
|
+
' Review history (change logs, retired designs, reversed decisions) is',
|
|
85
|
+
' deliberately absent — read the source spec for how the design got here.',
|
|
86
|
+
'-->',
|
|
87
|
+
'',
|
|
88
|
+
].join('\n');
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function main() {
|
|
92
|
+
const args = process.argv.slice(2);
|
|
93
|
+
const specIdx = args.indexOf('--spec');
|
|
94
|
+
if (specIdx === -1 || !args[specIdx + 1]) {
|
|
95
|
+
console.error('usage: generate-spec-contract.mjs --spec <path> [--check]');
|
|
96
|
+
process.exit(2);
|
|
97
|
+
}
|
|
98
|
+
const specPath = path.resolve(args[specIdx + 1]);
|
|
99
|
+
const check = args.includes('--check');
|
|
100
|
+
const specRel = path.relative(process.cwd(), specPath);
|
|
101
|
+
|
|
102
|
+
const markdown = fs.readFileSync(specPath, 'utf8');
|
|
103
|
+
const { contract, droppedSections } = splitContract(markdown);
|
|
104
|
+
const output = banner(specRel) + contract;
|
|
105
|
+
|
|
106
|
+
const slug = path.basename(specPath, '.md');
|
|
107
|
+
const outPath = path.join(path.dirname(specPath), 'generated', `${slug}.contract.md`);
|
|
108
|
+
|
|
109
|
+
if (check) {
|
|
110
|
+
if (!fs.existsSync(outPath)) {
|
|
111
|
+
console.error(`STALE: ${path.relative(process.cwd(), outPath)} does not exist. Run without --check.`);
|
|
112
|
+
process.exit(1);
|
|
113
|
+
}
|
|
114
|
+
const existing = fs.readFileSync(outPath, 'utf8');
|
|
115
|
+
if (existing !== output) {
|
|
116
|
+
console.error(
|
|
117
|
+
`STALE: ${path.relative(process.cwd(), outPath)} does not match ${specRel}.\n` +
|
|
118
|
+
'The contract is generated — regenerate it rather than editing it.',
|
|
119
|
+
);
|
|
120
|
+
process.exit(1);
|
|
121
|
+
}
|
|
122
|
+
console.log(`OK: contract is current (${droppedSections} history sections excluded).`);
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
fs.mkdirSync(path.dirname(outPath), { recursive: true });
|
|
127
|
+
fs.writeFileSync(outPath, output, 'utf8');
|
|
128
|
+
const pct = Math.round((1 - output.length / markdown.length) * 100);
|
|
129
|
+
console.log(
|
|
130
|
+
`wrote ${path.relative(process.cwd(), outPath)} — ${droppedSections} history sections excluded, ${pct}% smaller.`,
|
|
131
|
+
);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
if (import.meta.url === `file://${process.argv[1]}` || process.argv[1]?.endsWith('generate-spec-contract.mjs')) {
|
|
135
|
+
main();
|
|
136
|
+
}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$schema": "./builtin-manifest.schema.json",
|
|
3
3
|
"schemaVersion": 1,
|
|
4
|
-
"generatedAt": "2026-07-
|
|
5
|
-
"instarVersion": "1.3.
|
|
4
|
+
"generatedAt": "2026-07-25T09:37:20.372Z",
|
|
5
|
+
"instarVersion": "1.3.957",
|
|
6
6
|
"entryCount": 202,
|
|
7
7
|
"entries": {
|
|
8
8
|
"hook:session-start": {
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
# Upgrade Guide — vNEXT
|
|
2
|
+
|
|
3
|
+
<!-- assembled-by: assemble-next-md -->
|
|
4
|
+
<!-- bump: patch -->
|
|
5
|
+
|
|
6
|
+
## What Changed
|
|
7
|
+
|
|
8
|
+
Added `scripts/generate-spec-contract.mjs` — a build-time generator that emits
|
|
9
|
+
`docs/specs/generated/<slug>.contract.md` from a spec: the normative sections
|
|
10
|
+
only, with review-history sections and inline round-annotations stripped.
|
|
11
|
+
|
|
12
|
+
**Why.** A spec that honestly records its review accumulates change logs
|
|
13
|
+
describing designs that were later *reversed*. On
|
|
14
|
+
`outbound-gate-advisory-override` (33 review rounds), both external reviewers —
|
|
15
|
+
`codex-cli:gpt-5.5` and `gemini-cli:gemini-3.1-pro-preview` — independently
|
|
16
|
+
identified the same risk and proposed the same fix: an implementer reading the
|
|
17
|
+
document top-to-bottom can follow a retired design, so publish the contract
|
|
18
|
+
separately from the history.
|
|
19
|
+
|
|
20
|
+
`--check` fails the build when the committed contract does not match a fresh
|
|
21
|
+
generation, so the history cannot drift back into the contract over time.
|
|
22
|
+
|
|
23
|
+
The transform is deliberately mechanical — heading shapes mark history,
|
|
24
|
+
everything else is contract. A generator that had to *understand* the document
|
|
25
|
+
would drift out of step with it exactly as the document drifted out of step with
|
|
26
|
+
itself.
|
|
27
|
+
|
|
28
|
+
No runtime surface: no `src/` change, nothing installed to agent homes, no hook,
|
|
29
|
+
job or template references it. Nothing at runtime reads a spec.
|
|
30
|
+
|
|
31
|
+
## What to Tell Your User
|
|
32
|
+
|
|
33
|
+
None — internal change (no user-facing surface).
|
|
34
|
+
|
|
35
|
+
## Summary of New Capabilities
|
|
36
|
+
|
|
37
|
+
None — internal change (no user-facing surface).
|
|
38
|
+
|
|
39
|
+
## Evidence
|
|
40
|
+
|
|
41
|
+
- Generated against the real 2,700-line spec: **36 history sections excluded,
|
|
42
|
+
37% smaller output**.
|
|
43
|
+
- `--check` verified in both directions: exits 0 on a current contract, exits 1
|
|
44
|
+
with a regenerate message on a stale one.
|
|
45
|
+
- A regex leak was found and fixed during verification — `## 24. Round-11
|
|
46
|
+
external change log` was initially retained, because the heading text between
|
|
47
|
+
`Round-11` and `change log` was not matched. Caught by inspecting the generated
|
|
48
|
+
headings rather than trusting the run.
|
|
49
|
+
- Determinism confirmed by construction: no timestamps, no randomness, no
|
|
50
|
+
environment reads beyond the input path — which is what makes `--check` usable
|
|
51
|
+
in CI at all.
|
|
52
|
+
- Side-effects review: `upgrades/side-effects/spec-contract-generator.md`
|
|
53
|
+
(Tier 1; no decision point, no blocking authority, rollback is deleting two
|
|
54
|
+
files).
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
# Side-effects review — spec contract generator
|
|
2
|
+
|
|
3
|
+
**Change:** adds `scripts/generate-spec-contract.mjs`, a build-time generator that
|
|
4
|
+
produces `docs/specs/generated/<slug>.contract.md` from a spec: the normative
|
|
5
|
+
sections only, with review history and inline round-annotations stripped.
|
|
6
|
+
|
|
7
|
+
**Tier:** 1 (declared). New standalone dev-tooling script; no runtime surface, no
|
|
8
|
+
`src/` change, no decision point. Signal-only in the strongest sense — it emits a
|
|
9
|
+
derived document and can refuse a stale check; it gates no agent behavior.
|
|
10
|
+
|
|
11
|
+
**Why it exists:** on the `outbound-gate-advisory-override` spec, both external
|
|
12
|
+
reviewers (codex-cli, gemini-cli) independently identified the same risk across
|
|
13
|
+
several rounds — a spec that honestly records 33 rounds of review accumulates
|
|
14
|
+
change logs describing designs that were later **reversed**, and an implementer
|
|
15
|
+
reading top-to-bottom can follow a retired design. Both proposed the same fix:
|
|
16
|
+
publish the contract separately from the history.
|
|
17
|
+
|
|
18
|
+
---
|
|
19
|
+
|
|
20
|
+
## 1. Over-block — what legitimate inputs does this reject that it shouldn't?
|
|
21
|
+
|
|
22
|
+
The generator's only refusal is `--check` failing when the committed contract does
|
|
23
|
+
not byte-match a fresh generation. That is the intended behavior (CI staleness
|
|
24
|
+
gate), and it is refusable-by-regeneration, not by argument.
|
|
25
|
+
|
|
26
|
+
**Real over-block risk identified:** the history-heading regex could classify a
|
|
27
|
+
*normative* section as history if someone titles one `## Round-trip validation` or
|
|
28
|
+
similar. Mitigated by requiring both a `Round-N` prefix **and** one of
|
|
29
|
+
`change log` / `hand-check` / `consistency sweep`, plus an `ALWAYS_CONTRACT_RE`
|
|
30
|
+
allowlist for the known normative section names. A false positive drops a section
|
|
31
|
+
from the generated contract — visible immediately as a missing section, not
|
|
32
|
+
silent.
|
|
33
|
+
|
|
34
|
+
## 2. Under-block — what does it still miss?
|
|
35
|
+
|
|
36
|
+
- **Narrative history inside normative prose.** The generator strips history
|
|
37
|
+
*sections* and *inline annotations*; it cannot strip a paragraph of normative
|
|
38
|
+
prose that happens to narrate how a decision evolved. Those remain in the
|
|
39
|
+
contract. Partially addressed by the spec's own §0.2 "current design overview";
|
|
40
|
+
fully addressing it would require judgment the generator deliberately does not
|
|
41
|
+
have.
|
|
42
|
+
- **It does not verify the contract is CORRECT** — only that it is current. A
|
|
43
|
+
spec that is internally contradictory generates a contract that is equally
|
|
44
|
+
contradictory. The separate spec lint (required by the outbound spec) is what
|
|
45
|
+
addresses that; this is not it.
|
|
46
|
+
|
|
47
|
+
## 3. Level-of-abstraction fit
|
|
48
|
+
|
|
49
|
+
Correct layer: it is a build-time document transform, alongside the other
|
|
50
|
+
`scripts/*.mjs` spec tooling (`write-convergence-tag.mjs`,
|
|
51
|
+
`eli16-overview-check.mjs`, `publish-spec-review.mjs`). It does not belong in
|
|
52
|
+
`src/` — nothing at runtime reads a spec.
|
|
53
|
+
|
|
54
|
+
**Should a higher layer own it?** `/spec-converge` could invoke it automatically
|
|
55
|
+
on convergence. Deliberately not wired that way in this change: the generator
|
|
56
|
+
should prove itself as a standalone tool first, and auto-invocation is a change to
|
|
57
|
+
the skill's contract, not to this script. Not deferred-and-forgotten — it is
|
|
58
|
+
simply not part of this change's scope, and the script is useful without it.
|
|
59
|
+
|
|
60
|
+
## 4. Signal vs authority compliance
|
|
61
|
+
|
|
62
|
+
**Compliant, trivially.** The script holds no authority over agent behavior. Its
|
|
63
|
+
one enforcement surface (`--check` exiting non-zero) is a build-time staleness
|
|
64
|
+
assertion on a *derived artifact*, with a deterministic remedy (regenerate). It
|
|
65
|
+
makes no judgment, consults no model, and gates no message, session, or action.
|
|
66
|
+
|
|
67
|
+
## 5. Interactions
|
|
68
|
+
|
|
69
|
+
- **Shadows nothing.** No existing script generates or validates a spec contract.
|
|
70
|
+
- **Shadowed by nothing.** It reads a spec and writes a derived file under
|
|
71
|
+
`docs/specs/generated/`, a path nothing else writes.
|
|
72
|
+
- **No double-fire, no race.** Single-shot CLI; no daemon, no watcher, no
|
|
73
|
+
scheduling.
|
|
74
|
+
- **Adjacent tooling unaffected:** `write-convergence-tag.mjs` and
|
|
75
|
+
`eli16-overview-check.mjs` read the *source* spec; this writes only the
|
|
76
|
+
generated copy. `hashSpecReviewableBody` (used for cross-model delta-gating)
|
|
77
|
+
also reads the source, so generated files never affect review gating.
|
|
78
|
+
|
|
79
|
+
## 6. External surfaces
|
|
80
|
+
|
|
81
|
+
- **Other agents / users:** none. Dev tooling in the instar repo; not installed to
|
|
82
|
+
agent homes, not referenced by any hook, job, or template.
|
|
83
|
+
- **Timing / runtime conditions:** none — pure file in, file out.
|
|
84
|
+
- **New file paths introduced:** `docs/specs/generated/`. Additive; no existing
|
|
85
|
+
path changes meaning.
|
|
86
|
+
|
|
87
|
+
## 7. Multi-machine posture (Cross-Machine Coherence)
|
|
88
|
+
|
|
89
|
+
**Machine-local BY DESIGN, and trivially so:** the script runs in a developer's
|
|
90
|
+
checkout and writes a file into that checkout, which is then committed and
|
|
91
|
+
distributed by git like any other source file. There is no runtime state, no
|
|
92
|
+
per-machine store, no notice, and no generated URL. Running it on two machines
|
|
93
|
+
against the same spec produces byte-identical output (the transform is pure and
|
|
94
|
+
deterministic — no timestamps, no randomness, no environment reads beyond the
|
|
95
|
+
input path).
|
|
96
|
+
|
|
97
|
+
That determinism is what makes the `--check` mode usable in CI at all, and it is
|
|
98
|
+
asserted by the mode's existence: if the transform were machine-sensitive, CI
|
|
99
|
+
would fail against a contract generated on a developer's machine.
|
|
100
|
+
|
|
101
|
+
## 8. Rollback cost
|
|
102
|
+
|
|
103
|
+
**Near zero.** Delete the script and the generated directory; nothing depends on
|
|
104
|
+
either. No release, no migration, no agent-state repair. If the `--check` mode
|
|
105
|
+
turns out to be a nuisance in CI, it can be dropped independently of the
|
|
106
|
+
generator (the generator is useful without the check; the check is not useful
|
|
107
|
+
without the generator).
|
|
108
|
+
|
|
109
|
+
---
|
|
110
|
+
|
|
111
|
+
## Verification performed
|
|
112
|
+
|
|
113
|
+
- Generated against the real 2,700-line spec: 36 history sections excluded, 37%
|
|
114
|
+
smaller output.
|
|
115
|
+
- `--check` verified in both directions: passes on a current contract, fails with
|
|
116
|
+
a clear message on a stale one.
|
|
117
|
+
- Regex leak found and fixed during verification (`## 24. Round-11 external
|
|
118
|
+
change log` was initially retained because the heading text between `Round-11`
|
|
119
|
+
and `change log` was not matched) — caught by inspecting the generated
|
|
120
|
+
headings rather than trusting the run.
|
|
121
|
+
|
|
122
|
+
## Second pass
|
|
123
|
+
|
|
124
|
+
**Not required.** The change touches no block/allow decision, no session
|
|
125
|
+
lifecycle, no coherence gate, and nothing named sentinel/guard/gate/watchdog. It
|
|
126
|
+
is a standalone document transform.
|