instar 1.3.1018 → 1.3.1019

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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "instar",
3
- "version": "1.3.1018",
3
+ "version": "1.3.1019",
4
4
  "description": "Coherence infrastructure for self-evolving AI agents — on the Claude Code or Codex subscription you already have.",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -102,12 +102,44 @@ function parseArgs() {
102
102
  * Anything else with content (e.g. a `- **Q1:** …` bullet or a paragraph posing
103
103
  * a question) is an unresolved entry.
104
104
  */
105
+ /**
106
+ * Builds the H2 matcher for a named gate section.
107
+ *
108
+ * ONE builder, used by BOTH gate sections, deliberately: the numbered-heading
109
+ * hole below existed because the two matchers were written separately and only
110
+ * one of them ever got a heading-variance fix. A shared builder means the next
111
+ * variance fix cannot land on one gate and miss its sibling.
112
+ *
113
+ * Tolerated shapes:
114
+ * - `## Open questions` (canonical)
115
+ * - `## 9. Open questions` (numbered — the hole this closes)
116
+ * - `## 8b. Open questions` / `## 3) …` (lettered / paren'd)
117
+ * - `## 1.2 Open questions` (dotted)
118
+ * - `## Open questions (round 2)` (suffix variant — already worked)
119
+ *
120
+ * Why this was load-bearing: `findOpenQuestions` returns `[]` when the heading
121
+ * does not match, and `[]` means "nothing parked on the user". So a NUMBERED
122
+ * heading made a LIVE, unresolved user-decision invisible to the gate the skill
123
+ * calls structural ("cannot be skipped by prose"). Verified with a control
124
+ * before the fix: numbered heading + a live question → `[]`; the identical
125
+ * question under a plain heading → caught. Its sibling `findDecisionPointGaps`
126
+ * failed CLOSED on the very same input — two defaults for one quantity.
127
+ */
128
+ const SECTION_LABEL = String.raw`(?:\d+(?:\.\d+)*[a-z]?[.)]?\s+)?`;
129
+ function gateSectionHeadingRe(name) {
130
+ return new RegExp(String.raw`^##\s+${SECTION_LABEL}${name}\b[^\n]*$`, 'im');
131
+ }
132
+
105
133
  export function findOpenQuestions(specBody) {
106
134
  // \b…[^\n]*$ (not \s*$) so heading variants like "## Open questions (round 2)"
107
135
  // or "## Open Questions & Decisions" are still recognized — a variant heading
108
136
  // must not make the section invisible to the gate (reviewer finding, PR 2).
109
- const m = specBody.match(/^##\s+Open questions\b[^\n]*$/im);
110
- if (!m) return []; // no section → nothing parked on the user
137
+ // SECTION_LABEL additionally tolerates a numbered prefix (see the builder).
138
+ const m = specBody.match(gateSectionHeadingRe('Open questions'));
139
+ // A genuinely ABSENT section still means nothing is parked on the user; that
140
+ // semantic is unchanged and separately tested. What changed is that a present
141
+ // section can no longer hide behind its own section number.
142
+ if (!m) return [];
111
143
  const start = m.index + m[0].length;
112
144
  const restAfter = specBody.slice(start);
113
145
  const nextHeading = restAfter.search(/^##\s+/m);
@@ -144,7 +176,11 @@ export const GRANDFATHERED_SLUGS = [
144
176
 
145
177
  export function findDecisionPointGaps(specBody, slug) {
146
178
  if (slug && GRANDFATHERED_SLUGS.includes(slug)) return { ok: true };
147
- const m = specBody.match(/^##\s+Decision points touched\b[^\n]*$/im);
179
+ // Same shared builder as findOpenQuestions — this gate already failed CLOSED
180
+ // on a numbered heading (correct direction), but it was refusing specs whose
181
+ // section was PRESENT and merely numbered, which is a false refusal rather
182
+ // than a safety property. Both gates now recognise the same heading shapes.
183
+ const m = specBody.match(gateSectionHeadingRe('Decision points touched'));
148
184
  if (!m) return { ok: false, reason: 'missing-section' };
149
185
  const start = m.index + m[0].length;
150
186
  const restAfter = specBody.slice(start);
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "$schema": "./builtin-manifest.schema.json",
3
3
  "schemaVersion": 1,
4
- "generatedAt": "2026-07-27T21:55:15.925Z",
5
- "instarVersion": "1.3.1018",
4
+ "generatedAt": "2026-07-27T22:23:55.031Z",
5
+ "instarVersion": "1.3.1019",
6
6
  "entryCount": 202,
7
7
  "entries": {
8
8
  "hook:session-start": {
@@ -0,0 +1,49 @@
1
+ # Upgrade Guide — vNEXT
2
+
3
+ <!-- assembled-by: assemble-next-md -->
4
+ <!-- bump: patch -->
5
+
6
+ ## What Changed
7
+
8
+ The convergence tag writer refuses to mark a design document "converged" while it
9
+ still has an unanswered question parked on a person. That check only recognised a
10
+ section titled exactly `Open questions`. Many documents number their sections —
11
+ `9. Open questions` — and for those the check found nothing, concluded there was
12
+ nothing to find, and let the document through.
13
+
14
+ The two halves of the same gate also disagreed with each other on identical input:
15
+ the sibling check covering decision points refused a numbered heading outright,
16
+ while the open-questions check waved it past. Both now recognise the same set of
17
+ heading shapes (plain, numbered, lettered, dotted, parenthesised, and with a
18
+ trailing variant such as `(round 2)`), and they share one matcher so a future fix
19
+ to one cannot silently miss the other.
20
+
21
+ What deliberately did NOT change: a document with genuinely no such section is
22
+ still treated as having nothing outstanding. Whether that should instead refuse is
23
+ a separate decision worth arguing on its own rather than folding in here.
24
+
25
+ ## What to Tell Your User
26
+
27
+ Nothing you need to do. A safety check that decides whether a design document is
28
+ finished had a blind spot: if the document numbered its sections, an unanswered
29
+ question could slip past unnoticed. It now sees those documents too.
30
+
31
+ ## Summary of New Capabilities
32
+
33
+ No new capability. This repairs a check that already existed and was quietly not
34
+ looking at part of what it was meant to cover.
35
+
36
+ ## Evidence
37
+
38
+ The defect was reproduced with a control before any fix was written: a live,
39
+ unanswered question under a numbered heading returned "nothing outstanding", while
40
+ the identical question under a plain heading was caught.
41
+
42
+ Seven tests now cover the numbered, lettered, dotted, parenthesised and
43
+ variant-suffix headings plus the cross-check that both halves of the gate agree.
44
+ Reverting the fix makes exactly those seven fail; restoring it returns 50 passing
45
+ tests across all four suites that touch the changed file, with type-checking clean.
46
+
47
+ ## Title
48
+
49
+ A section number could hide an unanswered question from the convergence gate
@@ -0,0 +1,47 @@
1
+ # Side effects — open-questions gate: numbered-heading recognition
2
+
3
+ ## What this change can affect
4
+
5
+ `write-convergence-tag.mjs` is the structural gate for `/spec-converge`. Widening
6
+ heading recognition changes which specs the gate can SEE, so both directions were
7
+ checked rather than only the one being fixed.
8
+
9
+ ## Newly-visible sections (the intended effect)
10
+
11
+ A spec whose `Open questions` section is numbered was previously invisible to the
12
+ gate; it is now parsed. **Consequence to state plainly: a spec that would have been
13
+ stamped before may now be REFUSED — correctly — because it carries a live
14
+ unresolved question that the gate could not previously see.** That is the point of
15
+ the fix, and it is a behaviour change for any such spec mid-flight.
16
+
17
+ ## Not changed, deliberately
18
+
19
+ - A genuinely ABSENT `Open questions` section still yields "nothing parked on the
20
+ user". Whether an absent section should instead fail closed is a separate argued
21
+ decision; smuggling it in here would be a semantic change hiding inside a
22
+ matcher fix. Tracked, not silently taken.
23
+ - Resolution semantics are untouched: `*(none)*`, `(none)`, `None`, `N/A`,
24
+ blockquote commentary and horizontal rules still count as resolved, including
25
+ under a numbered heading (explicitly tested, so the fix cannot become
26
+ "refuse every numbered spec").
27
+ - `GRANDFATHERED_SLUGS` is untouched and remains empty.
28
+
29
+ ## The decision-points gate
30
+
31
+ That sibling previously refused a numbered heading with `missing-section`. That is
32
+ a FALSE refusal — the section was present, merely numbered — so it now recognises
33
+ the same shapes. This makes the gate less likely to block a conforming spec; it
34
+ does not weaken it, because an actually-missing section still refuses.
35
+
36
+ ## Blast radius and rollback
37
+
38
+ Two files: one script, one test file. No route, no config key, no persisted state,
39
+ no migration. Rollback is a revert.
40
+
41
+ ## Honest limit
42
+
43
+ The matcher tolerates a bounded set of section-label shapes. An exotic heading
44
+ (e.g. a roman numeral, or an emoji prefix) would still be invisible, and the
45
+ underlying design remains "match a heading by name". A structurally stronger
46
+ answer — a declared anchor rather than a heading regex — was NOT attempted here
47
+ and is a larger change than this repair.