forge-orkes 0.80.1 → 0.80.3

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.
@@ -588,12 +588,21 @@ function extractDetectionBlock(guideContent) {
588
588
  * with the exact guide pointer. There are NO per-version code branches: a new
589
589
  * guide that ships with a conformant Detection block is auto-covered.
590
590
  *
591
- * Range: installed < v <= source. `installedVersion` is the project's
591
+ * Range: installed <= v <= source. `installedVersion` is the project's
592
592
  * settings.json forge.version captured BEFORE the version stamp; `source` is
593
593
  * pkgVersion (this package's package.json) — never the template settings.json
594
594
  * literal, which is a placeholder the installer stamps at write time. Non-
595
595
  * interactive: the bin only warns and points at the guide; it never applies.
596
596
  *
597
+ * Lower bound is INCLUSIVE (issue #43): the version stamp always advances
598
+ * unconditionally, even when a MIGRATE hit was surfaced but declined that
599
+ * run — so an exclusive `installed < v` permanently hides a same-numbered
600
+ * guide the moment its own version gets stamped without being applied. This
601
+ * is safe to widen because the guide's own `## Detection` block is the real
602
+ * gate (repo-state-based: tracked file, size, absence of the post-migration
603
+ * artifact) — the version range only decides which guides are worth asking;
604
+ * a genuinely-already-applied guide's Detection block reports a no-op.
605
+ *
597
606
  * Returns the applying guides as [{version, guide, reason}] — the human warning
598
607
  * and the --json `migrations` key render the same findings.
599
608
  */
@@ -606,7 +615,7 @@ function runPostUpgradeMigrationChecks(installedVersion) {
606
615
  installedVersion && installedVersion !== 'unknown' ? installedVersion : '0.0.0';
607
616
  const source = pkgVersion;
608
617
 
609
- // Select in-range guides: installed < v <= source. Parse {v} from the filename.
618
+ // Select in-range guides: installed <= v <= source. Parse {v} from the filename.
610
619
  let guides;
611
620
  try {
612
621
  guides = fs
@@ -618,7 +627,7 @@ function runPostUpgradeMigrationChecks(installedVersion) {
618
627
  .filter(Boolean)
619
628
  .filter(
620
629
  (g) =>
621
- compareVersions(installed, g.version) < 0 &&
630
+ compareVersions(installed, g.version) <= 0 &&
622
631
  compareVersions(g.version, source) <= 0
623
632
  )
624
633
  .sort((a, b) => compareVersions(a.version, b.version));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "forge-orkes",
3
- "version": "0.80.1",
3
+ "version": "0.80.3",
4
4
  "description": "Set up the Forge meta-prompting framework for Claude Code in your project",
5
5
  "bin": {
6
6
  "create-forge": "./bin/create-forge.js"
@@ -135,7 +135,12 @@ function extract_id(line, w, arr, n, tok, digits, suffix) {
135
135
  # normalization. Labelled ids (M-EVT01, m-SEQPRIM01) must NOT be digit-
136
136
  # stripped — that would collapse distinct labelled ids onto the same
137
137
  # numeric suffix (m-EVT01 and m-SEQPRIM01 would both become m-01) — so
138
- # their alphanumeric suffix is preserved verbatim (lowercased) instead.
138
+ # their alphanumeric suffix is preserved VERBATIM, case included (issue
139
+ # #44): only the "m-" prefix is normalized, exactly like the numeric path
140
+ # never invents a digit convention of its own. Forge doesn't own the
141
+ # label's spelling — a project's own casing convention (e.g. m-EVT01, all
142
+ # caps) must survive the shard, or citations elsewhere in the repo stop
143
+ # matching the shard filename.
139
144
  if (tok !~ /^[Mm]-?[A-Za-z0-9]+$/) return ""
140
145
  suffix = tok
141
146
  sub(/^[Mm]-?/, "", suffix)
@@ -145,7 +150,7 @@ function extract_id(line, w, arr, n, tok, digits, suffix) {
145
150
  gsub(/[^0-9]/, "", digits)
146
151
  return "m-" digits
147
152
  }
148
- return "m-" tolower(suffix)
153
+ return "m-" suffix
149
154
  }
150
155
 
151
156
  function add_id(id) {
@@ -163,6 +168,7 @@ BEGIN {
163
168
  cur_noid_key = ""
164
169
  n_ids = 0
165
170
  n_noid = 0
171
+ n_other_headings = 0
166
172
  }
167
173
 
168
174
  /^## / {
@@ -174,6 +180,10 @@ BEGIN {
174
180
  if ($0 ~ /^## Needs Resolution/) { section = "NEEDS_RESOLUTION"; next }
175
181
  if ($0 ~ /^## Amendment Log/) { section = "AMENDMENT"; next }
176
182
  section = "OTHER"
183
+ n_other_headings++
184
+ w = $0
185
+ sub(/^## /, "", w)
186
+ other_heading_order[n_other_headings] = w
177
187
  other_buf = other_buf $0 "\n"
178
188
  next
179
189
  }
@@ -308,6 +318,23 @@ END {
308
318
  exit 1
309
319
  }
310
320
 
321
+ # Unrecognized top-level `## ` sections (issue #45) are preserved verbatim
322
+ # in context-log.md's "Other Sections" by design — never guessed onto a
323
+ # milestone — but a large or multi-heading OTHER bucket usually means the
324
+ # source context.md uses a section vocabulary this tool doesn't know about
325
+ # (e.g. a project-specific "## Active Streams"), and that content silently
326
+ # missing its own per-milestone home is easy to overlook. Loud, not fatal:
327
+ # nothing is lost (it's right there in context-log.md), so this warns
328
+ # without changing the exit code.
329
+ if (n_other_headings > 0 && length(other_buf) > 500) {
330
+ headings_list = other_heading_order[1]
331
+ for (i = 2; i <= n_other_headings; i++) headings_list = headings_list ", " other_heading_order[i]
332
+ print "forge-context-migrate: WARNING — " length(other_buf) " byte(s) across " \
333
+ n_other_headings " unrecognized heading(s) (" headings_list ") landed in" \
334
+ " context-log.md's \"Other Sections\" instead of a per-milestone file — review" \
335
+ " it; if that content is milestone-scoped, re-file it into context/{id}.md by hand" > "/dev/stderr"
336
+ }
337
+
311
338
  print "forge-context-migrate: " n_ids " milestone id(s) processed"
312
339
  }
313
340
  AWKEOF
@@ -159,11 +159,14 @@ src_after="$(cat "$SOURCE" | wc -c)"
159
159
  [ -n "$src_after" ] && pass "source context.md still readable after migration" || fail "source context.md unreadable after migration"
160
160
  grep -q "### M7 — Widget Registry" "$SOURCE" && pass "source context.md left untouched" || fail "source context.md was modified"
161
161
 
162
- # --- labelled milestone ids (issue #42) -------------------------------------
162
+ # --- labelled milestone ids (issue #42, case preservation issue #44) --------
163
163
  # Non-numeric ids (M-EVT01, m-SEQPRIM01) must shard into distinct files —
164
164
  # digit-stripping them onto a shared "m-01" was the exact silent-collision
165
165
  # bug reported upstream. Purely-numeric ids must keep their existing
166
- # normalization (m-53 stays m-53, unaffected by the labelled-id path).
166
+ # normalization (m-53 stays m-53, unaffected by the labelled-id path). The
167
+ # label's own casing must survive verbatim (issue #44) — only the "m-"
168
+ # prefix is normalized — since a project's own convention (e.g. an
169
+ # all-caps label) is what the rest of its repo actually cites.
167
170
 
168
171
  LBLROOT="$(mktemp -d)"
169
172
  trap 'rm -rf "$ROOT" "$LBLROOT"' EXIT INT TERM
@@ -203,15 +206,22 @@ LBLOUT="$("$MIGRATE" --source "$LBLSRC" --dest "$LBLDEST" 2>&1)"
203
206
  LBLRC=$?
204
207
  [ "$LBLRC" -eq 0 ] && pass "labelled-id migration exits 0" || fail "labelled-id migration exits 0 (exit $LBLRC)"
205
208
 
206
- check_file "$LBLDEST/context/m-evt01.md" "context/m-evt01.md created (labelled id, lowercased)"
207
- check_file "$LBLDEST/context/m-seqprim01.md" "context/m-seqprim01.md created (distinct from m-evt01, not digit-collapsed)"
209
+ check_file "$LBLDEST/context/m-EVT01.md" "context/m-EVT01.md created (labelled id, prefix normalized)"
210
+ check_file "$LBLDEST/context/m-SEQPRIM01.md" "context/m-SEQPRIM01.md created (distinct from m-EVT01, not digit-collapsed)"
208
211
  check_file "$LBLDEST/context/m-53.md" "context/m-53.md still created (numeric id unaffected by labelled-id path)"
209
212
 
213
+ # check_file alone can't prove case survived — macOS's default case-insensitive
214
+ # FS makes m-evt01.md and m-EVT01.md the same lookup. `ls` returns the actual
215
+ # stored name; grep -x is case-sensitive regardless of the FS.
216
+ lbl_listing="$(ls "$LBLDEST/context" 2>/dev/null)"
217
+ echo "$lbl_listing" | grep -qx "m-EVT01.md" && pass "m-EVT01.md label case preserved verbatim on disk (issue #44)" || fail "label case NOT preserved — directory listing: $lbl_listing"
218
+ echo "$lbl_listing" | grep -qx "m-SEQPRIM01.md" && pass "m-SEQPRIM01.md label case preserved verbatim on disk (issue #44)" || fail "label case NOT preserved — directory listing: $lbl_listing"
219
+
210
220
  lbl_count="$(ls "$LBLDEST"/context/*.md 2>/dev/null | wc -l | tr -d ' ')"
211
221
  [ "$lbl_count" -eq 3 ] && pass "labelled + numeric ids produce 3 distinct files, none collapsed" || fail "labelled-id run produced $lbl_count file(s), want 3"
212
222
 
213
- evt="$(cat "$LBLDEST/context/m-evt01.md" 2>/dev/null)"
214
- check_has "m-evt01.md carries its own Deferred entry (labelled-prefix match)" "$evt" "a follow-up scoped to the labelled id"
223
+ evt="$(cat "$LBLDEST/context/m-EVT01.md" 2>/dev/null)"
224
+ check_has "m-EVT01.md carries its own Deferred entry (labelled-prefix match)" "$evt" "a follow-up scoped to the labelled id"
215
225
 
216
226
  # --- unresolvable id scheme guard (issue #42) --------------------------------
217
227
  # A context.md whose Locked Decisions headings don't match m-<number> or a
@@ -241,6 +251,56 @@ BADRC=$?
241
251
  [ "$BADRC" -ne 0 ] && pass "unresolvable id scheme exits non-zero instead of silently producing 0 shards" || fail "unresolvable id scheme should exit non-zero (exit $BADRC)"
242
252
  check_has "unresolvable id scheme prints an ERROR line" "$BADOUT" "ERROR"
243
253
 
254
+ # --- large unrecognized-section warning (issue #45) -------------------------
255
+ # A project-specific top-level heading (e.g. "## Active Streams") this tool
256
+ # doesn't know about is preserved verbatim in context-log.md's "Other
257
+ # Sections" by design — but silently doing that with a large amount of
258
+ # content is easy to overlook. Must warn (not fail — nothing is lost) when
259
+ # the OTHER bucket is substantial, and stay silent when it's small/absent.
260
+ # Deliberately alongside a resolving Locked Decisions id, so this warning
261
+ # fires independent of (and isn't masked by) the zero-shards guard above.
262
+
263
+ WARNROOT="$(mktemp -d)"
264
+ trap 'rm -rf "$ROOT" "$LBLROOT" "$BADROOT" "$WARNROOT"' EXIT INT TERM
265
+ WARNSRC="$WARNROOT/context.md"
266
+ WARNDEST="$WARNROOT/.forge"
267
+ mkdir -p "$WARNDEST"
268
+
269
+ {
270
+ printf '## Locked Decisions\n\n### m-53 — numeric decision\n\n- **Decision**: fine.\n\n'
271
+ printf '## Active Streams\n\n### m-HC01 — some stream\n\n'
272
+ # 600 bytes — over the 500-byte warn floor
273
+ printf 'x%.0s' $(seq 1 600)
274
+ printf '\n\n## Needs Resolution\n\n(none)\n\n## Amendment Log\n\n(none)\n'
275
+ } > "$WARNSRC"
276
+
277
+ WARNOUT="$("$MIGRATE" --source "$WARNSRC" --dest "$WARNDEST" --dry-run 2>&1)"
278
+ WARNRC=$?
279
+ [ "$WARNRC" -eq 0 ] && pass "large unrecognized-section run still exits 0 (warning, not a failure)" || fail "large unrecognized-section run should exit 0 (exit $WARNRC)"
280
+ check_has "large unrecognized-section run prints a WARNING line" "$WARNOUT" "WARNING"
281
+ check_has "warning names the unrecognized heading" "$WARNOUT" "Active Streams"
282
+
283
+ SMALLROOT="$(mktemp -d)"
284
+ trap 'rm -rf "$ROOT" "$LBLROOT" "$BADROOT" "$WARNROOT" "$SMALLROOT"' EXIT INT TERM
285
+ SMALLSRC="$SMALLROOT/context.md"
286
+ SMALLDEST="$SMALLROOT/.forge"
287
+ mkdir -p "$SMALLDEST"
288
+
289
+ cat > "$SMALLSRC" <<'FIXTURE'
290
+ ## Locked Decisions
291
+
292
+ ### m-53 — numeric decision
293
+
294
+ - **Decision**: fine.
295
+
296
+ ## Some Small Aside
297
+
298
+ A short unrecognized note, well under the warn floor.
299
+ FIXTURE
300
+
301
+ SMALLOUT="$("$MIGRATE" --source "$SMALLSRC" --dest "$SMALLDEST" --dry-run 2>&1)"
302
+ check_absent "a small unrecognized section stays silent (no WARNING)" "$SMALLOUT" "WARNING"
303
+
244
304
  # --- summary -----------------------------------------------------------------
245
305
 
246
306
  printf '\n%s passed, %s failed\n' "$PASSED" "$FAILED"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "forge": {
3
- "version": "0.80.1",
3
+ "version": "0.80.3",
4
4
  "default_tier": "standard",
5
5
  "beads_integration": false,
6
6
  "context_gates": {