@warnyin/sdlc 0.2.1 → 0.3.0

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/CHANGELOG.md CHANGED
@@ -1,5 +1,34 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.3.0 (2026-08-21)
4
+
5
+ - **`/sdlc:auto` resumes an open change** instead of always starting at `new`. It
6
+ resolves its entry stage from `sdlc status` first: an argument naming an active
7
+ change maps that change's status to the entry stage and the pipeline starts
8
+ there, keeping the tier, Delta and Assumptions it was already triaged with. Only
9
+ an argument matching no active change starts at `new`. The entry stage is stated
10
+ in the plan line, so a resume is never silent. The status → stage table stays in
11
+ `next.md` alone rather than being copied into a second place that can drift.
12
+ - **Fix (ownership)**: `installFile` dropped a file's manifest entry whenever it
13
+ kept the file. The next run then saw a path it had never installed, which
14
+ permanently disarmed `update`'s refresh branch — the file froze at its old
15
+ payload version and every later run relabelled it user-modified. That affects
16
+ anyone who re-runs `init` to upgrade before `update`. A kept file now carries
17
+ its recorded hash forward; prune is unaffected (its guard compares the file on
18
+ disk against that same hash) and in fact strictly safer, since a kept file is no
19
+ longer even a prune candidate.
20
+ - A file whose content still matches its recorded hash is reported as
21
+ `kept (ours, older version — run update to refresh)` instead of
22
+ `kept (user-modified)`, which sent people hunting for an edit they never made.
23
+
24
+ ## 0.2.2 (2026-08-20)
25
+
26
+ - `init` (and `update`) now drop a `.gitkeep` in `sdlc/changes/archive/`, so the
27
+ directory survives a commit and is still there after a clone. 0.2.1 made
28
+ `archive` recover from the missing directory; this stops it going missing.
29
+ The marker is not payload-owned — prune never reclaims it and the installer
30
+ never warns about it.
31
+
3
32
  ## 0.2.1 (2026-08-20)
4
33
 
5
34
  - **Fix**: `archive` failed with `ENOENT` on the first change a repo ever ships.
package/README.md CHANGED
@@ -30,6 +30,7 @@ Then in your coding agent:
30
30
  ```
31
31
  /sdlc:init # interview → constitution + harness (the one human gate)
32
32
  /sdlc:auto Add rate limiting # AI runs new → contract → build → verify → ship
33
+ /sdlc:auto add-rate-limiting # already opened it with /sdlc:new? auto resumes from there
33
34
  ```
34
35
 
35
36
  ## How it works
package/bin/cli.mjs CHANGED
@@ -101,7 +101,20 @@ export function installFile(projectRoot, destRel, content, ctx) {
101
101
  ctx.manifest.set(relPosix, nextHash);
102
102
  return tally(ctx, 'updated');
103
103
  }
104
- ctx.warnings.push(`kept (user-modified): ${relPosix}`);
104
+ // Keeping the content must not forget the ownership. Dropping the entry made
105
+ // the next run see a file we had never installed, which permanently disarmed
106
+ // update's refresh branch (disk hash === old manifest hash) — the file froze
107
+ // at its old payload version and every later run relabelled it user-modified.
108
+ // The recorded hash stays the one we last wrote, so prune's "disk must match
109
+ // the manifest" guard still refuses to touch a file the user really did edit.
110
+ const owned = ctx.oldManifest?.get(relPosix);
111
+ if (owned) ctx.manifest.set(relPosix, owned);
112
+ // Matching the recorded hash proves nobody touched it — `install` simply does
113
+ // not refresh. Calling that "user-modified" sent people hunting for an edit
114
+ // they never made.
115
+ ctx.warnings.push(owned === currentHash
116
+ ? `kept (ours, older version — run \`npx @warnyin/sdlc update\` to refresh): ${relPosix}`
117
+ : `kept (user-modified): ${relPosix}`);
105
118
  return tally(ctx, 'kept');
106
119
  }
107
120
 
@@ -189,6 +202,12 @@ function scaffoldSdlc(projectRoot, tools, ctx) {
189
202
  fs.mkdirSync(path.join(sdlcRoot, dir), { recursive: true });
190
203
  }
191
204
 
205
+ // git does not track empty directories, so changes/archive/ vanishes for
206
+ // anyone who clones before the first change ships. Not manifested: an empty
207
+ // marker is nothing for prune to reclaim or for the installer to warn about.
208
+ const gitkeep = path.join(sdlcRoot, 'changes', 'archive', '.gitkeep');
209
+ if (!fs.existsSync(gitkeep)) fs.writeFileSync(gitkeep, '');
210
+
192
211
  // Seeds are user-owned from birth: created once, never manifested/overwritten.
193
212
  const seed = (rel, templateName, transform = (s) => s) => {
194
213
  const dest = path.join(sdlcRoot, rel);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@warnyin/sdlc",
3
- "version": "0.2.1",
3
+ "version": "0.3.0",
4
4
  "description": "Spec-driven, AI-driven SDLC framework — token-lean specs, contract-first changes, autonomous pipeline with managed hooks. Operationalizes the Day-1 'New SDLC with Vibe Coding' work process.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -1,5 +1,5 @@
1
- ---
2
- description: Run the whole pipeline autonomously: new contract → build → verify → ship (pauses only on policy escalations)
3
- argument-hint: "<title>"
4
- ---
5
- Read `sdlc/.playbook/auto.md` and execute it now. Arguments: $ARGUMENTS
1
+ ---
2
+ description: Run the whole pipeline autonomously — resumes an open change or opens a new one, then contract → build → verify → ship (pauses only on policy escalations)
3
+ argument-hint: "<title|change-id>"
4
+ ---
5
+ Read `sdlc/.playbook/auto.md` and execute it now. Arguments: $ARGUMENTS
@@ -9,7 +9,7 @@ new → [design] → contract → build → verify → [review] → ship
9
9
  | Command | Day-1 phase | Reads | Writes | Gate (automatic unless noted) |
10
10
  |---|---|---|---|---|
11
11
  | /sdlc:init | Configure harness | interview | constitution, harness.md | human approves (once) |
12
- | /sdlc:auto | whole loop | | everything below | escalation only |
12
+ | /sdlc:auto | whole loop | status (resumes an open change) | everything below | escalation only |
13
13
  | /sdlc:new | Requirements | specs Purpose headers | change.md | validator: delta + assumptions |
14
14
  | /sdlc:design | Architecture | change + touched specs | change.md § Design | escalate irreversible only |
15
15
  | /sdlc:contract | Contract-first | change.md | contract/*, failing tests | adversarial panel + validator |
@@ -1,4 +1,4 @@
1
- # /sdlc:auto <title> — the whole pipeline, one command
1
+ # /sdlc:auto <title|change-id> — the whole pipeline, one command
2
2
 
3
3
  Runs new → [design] → contract → build → verify → [review] → ship, each stage by
4
4
  its own playbook, WITHOUT pausing for the human except on the Autonomy-policy
@@ -10,8 +10,18 @@ escalation conditions:
10
10
  - token budget exceeded (if the user set one),
11
11
  - ship policy requires human approval (deep tier).
12
12
 
13
+ Entry stage — resolve this first, never assume `new`:
14
+ - Run `npx @warnyin/sdlc status`. If the argument names an active change (or one
15
+ is active and the argument describes it), RESUME: map its status to the entry
16
+ stage with `next.md` §2 and start the pipeline there.
17
+ - Resume never rewrites an existing `change.md` — a change already triaged keeps
18
+ its tier, Delta and Assumptions. Re-run a stage only if its output is missing
19
+ or the validator rejects it.
20
+ - Start at `new` only when the argument matches no active change.
21
+
13
22
  Rules:
14
- - Announce the plan in ≤3 lines after triage (id, tier, task count), then work.
23
+ - Announce the plan in ≤3 lines after triage (id, tier, task count, entry stage),
24
+ so a resume is never silent. Then work.
15
25
  - Between stages run `npx @warnyin/sdlc validate <id>` — a red validator is a
16
26
  hard stop for that stage, not a suggestion.
17
27
  - On escalation: stop at the exact step, state what is needed in ≤5 lines, wait.