create-sdd-project 0.18.3 → 0.18.4

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/lib/meta.js CHANGED
@@ -12,13 +12,27 @@
12
12
  * warnings on cross-version upgrades — the Codex P1 finding from the
13
13
  * v0.16.10 cross-model review).
14
14
  *
15
- * Core invariant (Codex M1 from plan v1.0 review): a hash in this file
16
- * represents "the last time the tool wrote this file, the content hashed
17
- * to X". Hashes are ONLY written/updated when the tool actually wrote
18
- * canonical output to a file in the current run (replaced, new, or
19
- * --force-template paths). Preserved files leave their hash entry
20
- * untouched otherwise the user's customized content would be hashed and
21
- * silently overwritten on the next upgrade.
15
+ * Core invariant (Codex M1 from plan v1.0 review, refined v0.18.4 G1):
16
+ * a hash in this file represents the canonical TEMPLATE output the tool
17
+ * last attempted to install for this path. Hashes are written/updated in
18
+ * three situations:
19
+ *
20
+ * 1. The tool wrote canonical output (replaced, new file, or
21
+ * --force-template paths) record the new template hash.
22
+ * 2. v0.18.4 G1 — Case 3c fallback preserve writes the template hash to
23
+ * bootstrap a previously-untracked path into the hash-based decision
24
+ * tree for future upgrades. The file on disk is NOT modified (user's
25
+ * customization is preserved + .new is written); only the meta entry
26
+ * is recorded so the next upgrade can enter Case 2 instead of falling
27
+ * through Case 3 again. Records what we "attempted to install", which
28
+ * is the correct anchor for both: (a) the user later accepting .new
29
+ * → user_current == stored → Case 2a clean replace, and (b) the user
30
+ * keeping their customization → user_current != stored → Case 2b
31
+ * preserve (situation (3) below then applies).
32
+ * 3. NEVER on Case 2b preserve — when a stored hash already exists and
33
+ * the current file diverges from it, the prior baseline is kept
34
+ * untouched. Otherwise the user's customized content would be hashed
35
+ * and silently overwritten on the next upgrade.
22
36
  *
23
37
  * File format (schemaVersion: 1):
24
38
  * {
@@ -540,8 +540,21 @@ function generateUpgrade(config) {
540
540
  continue;
541
541
  }
542
542
 
543
- // Content mismatch → preserve. Same rule: no hash update.
543
+ // Content mismatch → preserve.
544
+ //
545
+ // v0.18.4 G1: bootstrap hash AFTER preserve so next upgrade enters
546
+ // the hash-based path (Case 2) instead of falling through Case 3c
547
+ // again. Recording the TEMPLATE hash (not the user's hash) is the
548
+ // correct semantic anchor:
549
+ // - If user later accepts .new → user_current == stored → Case 2a
550
+ // clean replace.
551
+ // - If user keeps customization → user_current != stored → Case 2b
552
+ // preserve (Codex M1 invariant then applies — no further hash
553
+ // updates).
554
+ // This is opt-in at the CALL SITE, not inside preserveFile, so
555
+ // Case 2b callers above remain governed by the M1 invariant.
544
556
  preserveFile(adaptedFullTargetFallback);
557
+ newHashes[posixPath] = computeHash(adaptedFullTargetFallback);
545
558
  }
546
559
  continue;
547
560
  }
@@ -651,7 +664,9 @@ function generateUpgrade(config) {
651
664
  replaced++;
652
665
  continue;
653
666
  }
667
+ // v0.18.4 G1: Case 3c bootstrap (see agents site for full rationale).
654
668
  preserveCmd();
669
+ newHashes[posixPath] = computeHash(rawTemplate);
655
670
  }
656
671
  continue;
657
672
  }
@@ -836,6 +851,8 @@ function generateUpgrade(config) {
836
851
  }
837
852
  modifiedAgentsResults.push({ name: relativePath, modified: true });
838
853
  preserved++;
854
+ // v0.18.4 G1: Case 3c bootstrap (see agents site for full rationale).
855
+ newHashes[posix] = computeHash(adaptedTarget);
839
856
  }
840
857
 
841
858
  // --- d) Handle standards (smart diff) ---
@@ -979,7 +996,9 @@ function generateUpgrade(config) {
979
996
  replaced++;
980
997
  continue;
981
998
  }
999
+ // v0.18.4 G1: Case 3c bootstrap (see agents site for full rationale).
982
1000
  preserveStandard();
1001
+ newHashes[spec.posix] = computeHash(freshAdapted);
983
1002
  }
984
1003
 
985
1004
  step('Updated standards files');
@@ -1055,7 +1074,9 @@ function generateUpgrade(config) {
1055
1074
  newHashes[AGENTS_MD_POSIX] = computeHash(adaptedAgentsMd);
1056
1075
  replaced++;
1057
1076
  } else {
1077
+ // v0.18.4 G1: Case 3c bootstrap (see agents site for full rationale).
1058
1078
  preserveAgentsMd();
1079
+ newHashes[AGENTS_MD_POSIX] = computeHash(adaptedAgentsMd);
1059
1080
  }
1060
1081
  }
1061
1082
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-sdd-project",
3
- "version": "0.18.3",
3
+ "version": "0.18.4",
4
4
  "description": "Create a new SDD DevFlow project with AI-assisted development workflow",
5
5
  "bin": {
6
6
  "create-sdd-project": "bin/cli.js"
@@ -186,15 +186,33 @@ TICKET_STATUS=$(grep -E "^\*\*Status:\*\*" "$TICKET" | head -1 \
186
186
  | sed -E 's/[[:space:]]+(\(.*\)|—.*|–.*|-.*)$//' \
187
187
  | sed -E 's/\*\*[[:space:]]*$//' \
188
188
  | sed -E 's/[[:space:]]+$//')
189
- FEATURE_ID=$(basename "$TICKET" .md | sed -E 's/-[a-z].*//')
190
- TRACKER_STATUS=$(grep -F "$FEATURE_ID" docs/project_notes/product-tracker.md | grep -oE "\| (in-progress|done|pending|blocked) \|" | head -1 | sed -E 's/\| ([a-z-]+) \|/\1/')
191
- case "$TICKET_STATUS" in
192
- "Ready for Merge"|"Review"|"In Progress"|"Planning"|"Spec") EXPECTED="in-progress" ;;
193
- "Done") EXPECTED="done" ;;
194
- *) EXPECTED="" ;;
189
+ TICKET_BASENAME=$(basename "$TICKET" .md)
190
+ # v0.18.4 P11-B: sub-scope tickets (-lite / -FU / -FU[0-9]*) close a partial
191
+ # scope of a parent feature; the parent tracker row stays at its parent status
192
+ # (typically `pending` or `in-progress`) while the sub-scope ticket reaches
193
+ # `Done`. P11 must NOT enforce status mapping across this boundary. Pattern
194
+ # scope: empirically derived from fx convention (uppercase -FU + -lite). If
195
+ # future projects introduce -spike / -mini / -aux variants, expand here.
196
+ case "$TICKET_BASENAME" in
197
+ *-lite|*-lite-*|*-FU|*-FU-*|*-FU[0-9]*)
198
+ echo "P11 N/A: $TICKET_BASENAME is a sub-scope ticket — parent tracker row status independent" >&2
199
+ ;;
200
+ *)
201
+ FEATURE_ID=$(echo "$TICKET_BASENAME" | sed -E 's/-[a-z].*//')
202
+ # v0.18.4 P11-B drive-by hardening: anchor tracker lookup to pipe-table row
203
+ # (FEATURE_ID as first cell). Mirrors v0.18.3 P16 idiom — prevents narrative
204
+ # mentions earlier in the tracker from silencing or false-firing the check.
205
+ TRACKER_STATUS=$(grep -E "^\|[[:space:]]*$FEATURE_ID[[:space:]]*\|" docs/project_notes/product-tracker.md \
206
+ | grep -oE "\| (in-progress|done|pending|blocked) \|" | head -1 | sed -E 's/\| ([a-z-]+) \|/\1/')
207
+ case "$TICKET_STATUS" in
208
+ "Ready for Merge"|"Review"|"In Progress"|"Planning"|"Spec") EXPECTED="in-progress" ;;
209
+ "Done") EXPECTED="done" ;;
210
+ *) EXPECTED="" ;;
211
+ esac
212
+ [ -n "$EXPECTED" ] && [ -n "$TRACKER_STATUS" ] && [ "$TRACKER_STATUS" != "$EXPECTED" ] \
213
+ && flag "P11 drift: ticket Status='$TICKET_STATUS' expects tracker='$EXPECTED' but tracker='$TRACKER_STATUS'"
214
+ ;;
195
215
  esac
196
- [ -n "$EXPECTED" ] && [ -n "$TRACKER_STATUS" ] && [ "$TRACKER_STATUS" != "$EXPECTED" ] \
197
- && flag "P11 drift: ticket Status='$TICKET_STATUS' expects tracker='$EXPECTED' but tracker='$TRACKER_STATUS'"
198
216
  ```
199
217
 
200
218
  **23. P12 — Tracker HEAD references stale (added v0.18.2).** The `**Last Updated:**` and `**Active Feature:**` lines may embed `HEAD <sha>` or `HEAD: <sha>` references that were correct when written but went stale as further commits landed (empirically observed in fx F-WEB-MENU-VISION-001 audit cycle 2026-05-06: tracker said `HEAD: fd752e4` while `git rev-parse HEAD` was `6fa801e` after the agent's own self-edit commit). Compare each extracted SHA against the active branch HEAD. Bidirectional prefix tolerance: a 7-char tracker SHA matches the full 40-char actual HEAD if it's a prefix; a full 40-char tracker SHA matches if its first 7 chars equal the actual short form. Scoped strictly to the two header lines so narrative SHAs in "Last Completed" prose never false-positive-fire.
@@ -74,6 +74,14 @@ In the ticket, fill the `## Merge Checklist Evidence` table. For each action (0
74
74
 
75
75
  **Canonical form for the AC count claim:** write `AC: <marked>/<total>` — `marked` is the count of `[x]` Acceptance Criteria, `total` is the count of all AC items including any intentionally deferred `[ ]`. When all are checked use the matching form `AC: N/N` (or the shorthand `all N marked`). The `/audit-merge` P6 drift check parses both forms.
76
76
 
77
+ **Sub-scope ticket naming convention (recognized by `/audit-merge` P11 since v0.18.4):** when a feature is too large to close in one ticket, split it into sub-scope tickets using one of these suffixes on the basename:
78
+
79
+ - `<FEATURE_ID>-lite-<descriptor>.md` — minimal viable closure of a partial scope (e.g. `F116-lite-ci-hardening.md`)
80
+ - `<FEATURE_ID>-FU.md` — single follow-up closing a deferred piece
81
+ - `<FEATURE_ID>-FU<N>.md` — numbered follow-ups (e.g. `F-H7-FU1.md`, `F-H10-FU2.md`)
82
+
83
+ Sub-scope tickets reach `Status: Done` independently while the parent feature's tracker row stays at its parent status (typically `pending` or `in-progress`) until ALL sub-scopes close. `/audit-merge` P11 detects the suffix and emits `P11 N/A` instead of flagging the parent/sub-scope status divergence as drift.
84
+
77
85
  ## Action 9: Run compliance audit
78
86
 
79
87
  Run `/audit-merge` to verify all compliance checks pass automatically. If any check fails, fix it and re-run until all pass.
@@ -186,15 +186,33 @@ TICKET_STATUS=$(grep -E "^\*\*Status:\*\*" "$TICKET" | head -1 \
186
186
  | sed -E 's/[[:space:]]+(\(.*\)|—.*|–.*|-.*)$//' \
187
187
  | sed -E 's/\*\*[[:space:]]*$//' \
188
188
  | sed -E 's/[[:space:]]+$//')
189
- FEATURE_ID=$(basename "$TICKET" .md | sed -E 's/-[a-z].*//')
190
- TRACKER_STATUS=$(grep -F "$FEATURE_ID" docs/project_notes/product-tracker.md | grep -oE "\| (in-progress|done|pending|blocked) \|" | head -1 | sed -E 's/\| ([a-z-]+) \|/\1/')
191
- case "$TICKET_STATUS" in
192
- "Ready for Merge"|"Review"|"In Progress"|"Planning"|"Spec") EXPECTED="in-progress" ;;
193
- "Done") EXPECTED="done" ;;
194
- *) EXPECTED="" ;;
189
+ TICKET_BASENAME=$(basename "$TICKET" .md)
190
+ # v0.18.4 P11-B: sub-scope tickets (-lite / -FU / -FU[0-9]*) close a partial
191
+ # scope of a parent feature; the parent tracker row stays at its parent status
192
+ # (typically `pending` or `in-progress`) while the sub-scope ticket reaches
193
+ # `Done`. P11 must NOT enforce status mapping across this boundary. Pattern
194
+ # scope: empirically derived from fx convention (uppercase -FU + -lite). If
195
+ # future projects introduce -spike / -mini / -aux variants, expand here.
196
+ case "$TICKET_BASENAME" in
197
+ *-lite|*-lite-*|*-FU|*-FU-*|*-FU[0-9]*)
198
+ echo "P11 N/A: $TICKET_BASENAME is a sub-scope ticket — parent tracker row status independent" >&2
199
+ ;;
200
+ *)
201
+ FEATURE_ID=$(echo "$TICKET_BASENAME" | sed -E 's/-[a-z].*//')
202
+ # v0.18.4 P11-B drive-by hardening: anchor tracker lookup to pipe-table row
203
+ # (FEATURE_ID as first cell). Mirrors v0.18.3 P16 idiom — prevents narrative
204
+ # mentions earlier in the tracker from silencing or false-firing the check.
205
+ TRACKER_STATUS=$(grep -E "^\|[[:space:]]*$FEATURE_ID[[:space:]]*\|" docs/project_notes/product-tracker.md \
206
+ | grep -oE "\| (in-progress|done|pending|blocked) \|" | head -1 | sed -E 's/\| ([a-z-]+) \|/\1/')
207
+ case "$TICKET_STATUS" in
208
+ "Ready for Merge"|"Review"|"In Progress"|"Planning"|"Spec") EXPECTED="in-progress" ;;
209
+ "Done") EXPECTED="done" ;;
210
+ *) EXPECTED="" ;;
211
+ esac
212
+ [ -n "$EXPECTED" ] && [ -n "$TRACKER_STATUS" ] && [ "$TRACKER_STATUS" != "$EXPECTED" ] \
213
+ && flag "P11 drift: ticket Status='$TICKET_STATUS' expects tracker='$EXPECTED' but tracker='$TRACKER_STATUS'"
214
+ ;;
195
215
  esac
196
- [ -n "$EXPECTED" ] && [ -n "$TRACKER_STATUS" ] && [ "$TRACKER_STATUS" != "$EXPECTED" ] \
197
- && flag "P11 drift: ticket Status='$TICKET_STATUS' expects tracker='$EXPECTED' but tracker='$TRACKER_STATUS'"
198
216
  ```
199
217
 
200
218
  **23. P12 — Tracker HEAD references stale (added v0.18.2).** The `**Last Updated:**` and `**Active Feature:**` lines may embed `HEAD <sha>` or `HEAD: <sha>` references that were correct when written but went stale as further commits landed (empirically observed in fx F-WEB-MENU-VISION-001 audit cycle 2026-05-06: tracker said `HEAD: fd752e4` while `git rev-parse HEAD` was `6fa801e` after the agent's own self-edit commit). Compare each extracted SHA against the active branch HEAD. Bidirectional prefix tolerance: a 7-char tracker SHA matches the full 40-char actual HEAD if it's a prefix; a full 40-char tracker SHA matches if its first 7 chars equal the actual short form. Scoped strictly to the two header lines so narrative SHAs in "Last Completed" prose never false-positive-fire.
@@ -74,6 +74,14 @@ In the ticket, fill the `## Merge Checklist Evidence` table. For each action (0
74
74
 
75
75
  **Canonical form for the AC count claim:** write `AC: <marked>/<total>` — `marked` is the count of `[x]` Acceptance Criteria, `total` is the count of all AC items including any intentionally deferred `[ ]`. When all are checked use the matching form `AC: N/N` (or the shorthand `all N marked`). The `/audit-merge` P6 drift check parses both forms.
76
76
 
77
+ **Sub-scope ticket naming convention (recognized by `/audit-merge` P11 since v0.18.4):** when a feature is too large to close in one ticket, split it into sub-scope tickets using one of these suffixes on the basename:
78
+
79
+ - `<FEATURE_ID>-lite-<descriptor>.md` — minimal viable closure of a partial scope (e.g. `F116-lite-ci-hardening.md`)
80
+ - `<FEATURE_ID>-FU.md` — single follow-up closing a deferred piece
81
+ - `<FEATURE_ID>-FU<N>.md` — numbered follow-ups (e.g. `F-H7-FU1.md`, `F-H10-FU2.md`)
82
+
83
+ Sub-scope tickets reach `Status: Done` independently while the parent feature's tracker row stays at its parent status (typically `pending` or `in-progress`) until ALL sub-scopes close. `/audit-merge` P11 detects the suffix and emits `P11 N/A` instead of flagging the parent/sub-scope status divergence as drift.
84
+
77
85
  ## Action 9: Run compliance audit
78
86
 
79
87
  Run `/audit-merge` to verify all compliance checks pass automatically. If any check fails, fix it and re-run until all pass.