@warnyin/sdlc 0.3.0 → 0.5.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,95 +1,147 @@
1
- # Changelog
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
-
32
- ## 0.2.1 (2026-08-20)
33
-
34
- - **Fix**: `archive` failed with `ENOENT` on the first change a repo ever ships.
35
- `init` scaffolds `sdlc/changes/archive/`, but git does not track empty
36
- directories, so the folder is absent for everyone who clones before that first
37
- ship. The rename is now preceded by a `mkdir -p` of the archive root.
38
-
39
- The failure landed mid-phase-2, after the delta had been merged into the living
40
- specs, evals promoted, `status: shipped` stamped and the ship event journalled
41
- a repo left half-shipped while the CLI reported total failure. The directory is
42
- now prepared next to the other destination checks, before phase 1 computes a
43
- single merge, so an unusable archive path aborts with the specs untouched.
44
-
45
- ## 0.2.0 (2026-08-20)
46
-
47
- `init` is now an installer you can actually see working still zero dependencies.
48
-
49
- - **Interactive tool picker**: a searchable checkbox list replaces the
50
- comma-separated typing prompt. Arrows move, `space` toggles, typing filters,
51
- `ctrl+a` selects everything on screen, `enter` confirms, `ctrl+c` cancels
52
- without installing anything.
53
- - **Tool detection**: tools the project already uses (`.claude/`, `.cursor/`,
54
- `AGENTS.md`, …) come pre-selected; an empty project still defaults to claude.
55
- - **Post-install summary**: artifact counts, the adapter path per tool, a
56
- written/unchanged/refreshed/kept tally, and Getting-started hints that differ
57
- per tool (slash commands for Claude Code, prose for the rest).
58
- - **Colour** gated on `NO_COLOR` > `FORCE_COLOR` > TTY, with an ASCII glyph
59
- fallback for legacy Windows consoles.
60
- - `--tool` accepts `all` and `none`; `--tools` is an alias. An empty `--tool`,
61
- an unknown tool, or `all` mixed with a named tool now fails loudly.
62
- - **Fix**: `update` read an explicit `tools: []` (what `init --tool none` writes)
63
- as "unset" and reinstalled claude. A missing key and a declared-empty one are
64
- now distinguished.
65
-
66
- ## 0.1.2 (2026-08-20)
67
-
68
- - Fix: `readStdinJson()` grew a 1s grace timeout journal and hook utilities no
69
- longer hang when stdin is open but idle.
70
- - Fix: hooks release stdin (pause + unref) so an open-idle stdin cannot keep a
71
- hook process alive.
72
-
73
- ## 0.1.1 (2026-08-20)
74
-
75
- - Fix: the entrypoint guard must realpath `process.argv[1]` — npx invokes through
76
- a `node_modules/.bin` symlink, which made the CLI a silent no-op.
77
- - Fix (CI): `node --test` bare discovery; a quoted glob is not expanded on Node 20.
78
-
79
- ## 0.1.0 (2026-08-20)
80
-
81
- Initial releasethe full Day-1 SDLC loop:
82
-
83
- - CLI: `init` (multi-tool: claude/cursor/windsurf/copilot/cline/gemini/agents-md),
84
- `update` (ownership-aware refresh + guarded prune), `validate`, `status`,
85
- `observe`, `archive`.
86
- - Artifact model: constitution (≤30) + steering with inclusion modes + harness
87
- (routing/triage/autonomy policy) + living specs + delta-based changes
88
- (vibe/standard/deep caps 40/100/150) + contracts (tests ≤60, evals ≤40).
89
- - Managed Claude Code hooks: static-context injector, spec write-lock with TTL
90
- gates, artifact validator + steering pointers, session token/cost journaling,
91
- compact-event tracking.
92
- - 13 stage playbooks + 13 `/sdlc:*` commands + 3 background skills + 8 agents
93
- with model routing (cheap/balanced/deepest).
94
- - Observability: per-change tokens/cost, first-pass rate, lead time, dead
95
- steering and residency flags; post-ship learner loop (distill, never bloat).
1
+ # Changelog
2
+
3
+ ## 0.5.0 (2026-08-25)
4
+
5
+ - **`--auto` on every pipeline stage.** `/sdlc:auto` already ran the whole pipeline,
6
+ but it stopped at every escalation so you were pulled back in three or four times
7
+ per change and typed each stage anyway. Now all seven stage commands take `--auto`:
8
+ the run gathers what it needs, confirms once, and goes to ship. The confirmation is
9
+ decidable item by item scope as understood, the tier and why, every ambiguity with
10
+ the assumption to be acted on, and each escalation as its own refusable line, the
11
+ ship row naming the hard-floor surface it covers instead of hiding behind a general
12
+ "run without me". Nothing is written before you confirm, down to the active-change
13
+ pointer, so declining leaves the repository untouched. The approval covers that run
14
+ only not config, not the next change, not a resume. Anything outside what you
15
+ confirmed still stops and asks.
16
+ - Escalations passed unattended are journalled, counted by `/sdlc:observe` as
17
+ `unattended×N`, and listed in the digest: the record shows where a human would
18
+ normally have stood and, that run, did not.
19
+ - **Verify and review outcomes now record how they were produced** (`mode=panel|solo`).
20
+ A journal that says "verify passed" hides the thing a reader most needs later —
21
+ whether that verdict came from independent reviewers or from the same loop that
22
+ wrote the code. `observe` marks such changes `self-judged`, and the digest must name
23
+ self-produced outcomes. Absent provenance reads as unknown, never as `panel`, so
24
+ older journals are not retroactively dressed up as independently reviewed; where
25
+ provenance is mixed, the weakest link decides.
26
+ - Where a panel cannot run, the playbooks now say to judge in the main loop and record
27
+ that not to skip the stage. A review that never happened is worse than one
28
+ labelled honestly.
29
+ - The constitution gains a hard rule: human-written text SHALL NOT reach a shell as an
30
+ argument. It is the defect that got past two separate gates in 0.4.0.
31
+
32
+ ## 0.4.0 (2026-08-25)
33
+
34
+ - **New stage command `/sdlc:feedback`** reports a bug, a rough edge, or a missing
35
+ feature in the framework itself to `warnyin/warnyin-sdlc`, from inside the session
36
+ where you hit it. It collects the context a maintainer triages by (framework and
37
+ Node version, OS, tool adapter, active change id and status), redacts it, shows you
38
+ the complete draft, and files it only after you approve. Submission goes through
39
+ `gh`; when `gh` is missing, logged out, or authenticated only against an enterprise
40
+ host, you get a prefilled issue URL instead a normal path, not an error.
41
+ Nothing is attached automatically: no logs, no journal, no diff. Redaction is a
42
+ rule list rather than a guarantee, and the playbook says so your eyes on the
43
+ draft are the control.
44
+ - Human-written text never reaches a shell as an argument: the body travels over
45
+ stdin, while the title and the duplicate-search keywords are written by the agent
46
+ under a length and character allow-list instead of being pasted raw.
47
+ - **New: `warnyin-sdlc version`** (also `--version` / `-v`). Nothing in an installed
48
+ project was readable as a version — an npx install leaves no package behind — so
49
+ every bug report would have carried `unknown` in the field that decides whether a
50
+ report can be acted on at all.
51
+ - The repository now ships `.github/ISSUE_TEMPLATE/` bug and feature forms asking for
52
+ the same fields the command collects, so web-filed and command-filed reports read
53
+ alike.
54
+
55
+ ## 0.3.0 (2026-08-21)
56
+
57
+ - **`/sdlc:auto` resumes an open change** instead of always starting at `new`. It
58
+ resolves its entry stage from `sdlc status` first: an argument naming an active
59
+ change maps that change's status to the entry stage and the pipeline starts
60
+ there, keeping the tier, Delta and Assumptions it was already triaged with. Only
61
+ an argument matching no active change starts at `new`. The entry stage is stated
62
+ in the plan line, so a resume is never silent. The status → stage table stays in
63
+ `next.md` alone rather than being copied into a second place that can drift.
64
+ - **Fix (ownership)**: `installFile` dropped a file's manifest entry whenever it
65
+ kept the file. The next run then saw a path it had never installed, which
66
+ permanently disarmed `update`'s refresh branch — the file froze at its old
67
+ payload version and every later run relabelled it user-modified. That affects
68
+ anyone who re-runs `init` to upgrade before `update`. A kept file now carries
69
+ its recorded hash forward; prune is unaffected (its guard compares the file on
70
+ disk against that same hash) and in fact strictly safer, since a kept file is no
71
+ longer even a prune candidate.
72
+ - A file whose content still matches its recorded hash is reported as
73
+ `kept (ours, older version — run update to refresh)` instead of
74
+ `kept (user-modified)`, which sent people hunting for an edit they never made.
75
+
76
+ ## 0.2.2 (2026-08-20)
77
+
78
+ - `init` (and `update`) now drop a `.gitkeep` in `sdlc/changes/archive/`, so the
79
+ directory survives a commit and is still there after a clone. 0.2.1 made
80
+ `archive` recover from the missing directory; this stops it going missing.
81
+ The marker is not payload-owned prune never reclaims it and the installer
82
+ never warns about it.
83
+
84
+ ## 0.2.1 (2026-08-20)
85
+
86
+ - **Fix**: `archive` failed with `ENOENT` on the first change a repo ever ships.
87
+ `init` scaffolds `sdlc/changes/archive/`, but git does not track empty
88
+ directories, so the folder is absent for everyone who clones before that first
89
+ ship. The rename is now preceded by a `mkdir -p` of the archive root.
90
+
91
+ The failure landed mid-phase-2, after the delta had been merged into the living
92
+ specs, evals promoted, `status: shipped` stamped and the ship event journalled
93
+ a repo left half-shipped while the CLI reported total failure. The directory is
94
+ now prepared next to the other destination checks, before phase 1 computes a
95
+ single merge, so an unusable archive path aborts with the specs untouched.
96
+
97
+ ## 0.2.0 (2026-08-20)
98
+
99
+ `init` is now an installer you can actually see working — still zero dependencies.
100
+
101
+ - **Interactive tool picker**: a searchable checkbox list replaces the
102
+ comma-separated typing prompt. Arrows move, `space` toggles, typing filters,
103
+ `ctrl+a` selects everything on screen, `enter` confirms, `ctrl+c` cancels
104
+ without installing anything.
105
+ - **Tool detection**: tools the project already uses (`.claude/`, `.cursor/`,
106
+ `AGENTS.md`, …) come pre-selected; an empty project still defaults to claude.
107
+ - **Post-install summary**: artifact counts, the adapter path per tool, a
108
+ written/unchanged/refreshed/kept tally, and Getting-started hints that differ
109
+ per tool (slash commands for Claude Code, prose for the rest).
110
+ - **Colour** gated on `NO_COLOR` > `FORCE_COLOR` > TTY, with an ASCII glyph
111
+ fallback for legacy Windows consoles.
112
+ - `--tool` accepts `all` and `none`; `--tools` is an alias. An empty `--tool`,
113
+ an unknown tool, or `all` mixed with a named tool now fails loudly.
114
+ - **Fix**: `update` read an explicit `tools: []` (what `init --tool none` writes)
115
+ as "unset" and reinstalled claude. A missing key and a declared-empty one are
116
+ now distinguished.
117
+
118
+ ## 0.1.2 (2026-08-20)
119
+
120
+ - Fix: `readStdinJson()` grew a 1s grace timeout — journal and hook utilities no
121
+ longer hang when stdin is open but idle.
122
+ - Fix: hooks release stdin (pause + unref) so an open-idle stdin cannot keep a
123
+ hook process alive.
124
+
125
+ ## 0.1.1 (2026-08-20)
126
+
127
+ - Fix: the entrypoint guard must realpath `process.argv[1]` — npx invokes through
128
+ a `node_modules/.bin` symlink, which made the CLI a silent no-op.
129
+ - Fix (CI): `node --test` bare discovery; a quoted glob is not expanded on Node 20.
130
+
131
+ ## 0.1.0 (2026-08-20)
132
+
133
+ Initial release — the full Day-1 SDLC loop:
134
+
135
+ - CLI: `init` (multi-tool: claude/cursor/windsurf/copilot/cline/gemini/agents-md),
136
+ `update` (ownership-aware refresh + guarded prune), `validate`, `status`,
137
+ `observe`, `archive`.
138
+ - Artifact model: constitution (≤30) + steering with inclusion modes + harness
139
+ (routing/triage/autonomy policy) + living specs + delta-based changes
140
+ (vibe/standard/deep caps 40/100/150) + contracts (tests ≤60, evals ≤40).
141
+ - Managed Claude Code hooks: static-context injector, spec write-lock with TTL
142
+ gates, artifact validator + steering pointers, session token/cost journaling,
143
+ compact-event tracking.
144
+ - 13 stage playbooks + 13 `/sdlc:*` commands + 3 background skills + 8 agents
145
+ with model routing (cheap/balanced/deepest).
146
+ - Observability: per-change tokens/cost, first-pass rate, lead time, dead
147
+ steering and residency flags; post-ship learner loop (distill, never bloat).
package/README.md CHANGED
@@ -1,84 +1,91 @@
1
- # @warnyin/sdlc
2
-
3
- **Spec-driven, AI-driven SDLC for coding agents — token-lean by construction.**
4
-
5
- Operationalizes the *"New SDLC with Vibe Coding"* (Day-1) work process: the human configures
6
- the harness once; the AI drives changes end-to-end through contract-first gates; deterministic
7
- hooks and a validator enforce the rules; a journal prices every change in real tokens.
8
-
9
- Inspired by OpenSpec (delta specs, archive lifecycle), spec-kit (artifact grammar), and
10
- Kiro (steering + enforced hooks) — tuned for minimum context residency.
11
-
12
- ## Install
13
-
14
- ```bash
15
- cd your-project
16
- npx @warnyin/sdlc init # interactive picker; tools already in the project are pre-selected
17
- ```
18
-
19
- The picker is a checkbox list — arrows move, `space` toggles, typing filters, `ctrl+a` selects
20
- everything on screen, `enter` confirms. Skip it in CI or scripts:
21
-
22
- ```bash
23
- npx @warnyin/sdlc init --tool claude,cursor # explicit list
24
- npx @warnyin/sdlc init --tool all # every supported tool
25
- npx @warnyin/sdlc init --tool none # sdlc/ framework only, no agent adapters
26
- ```
27
-
28
- Then in your coding agent:
29
-
30
- ```
31
- /sdlc:init # interview → constitution + harness (the one human gate)
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
34
- ```
35
-
36
- ## How it works
37
-
38
- ```
39
- sdlc/
40
- ├── context/constitution.md ≤30 lines the ONLY always-loaded prose (hook-injected)
41
- ├── context/steering/*.md scoped knowledge · inclusion: always|paths|manual|agent
42
- ├── harness.md tools, model routing, tier triage, autonomy policy
43
- ├── specs/<capability>/ living specs — WHEN/THEN SHALL, merged mechanically at ship
44
- ├── changes/<id>/ one change: change.md (Why+Delta+Tasks) + contract/ + journal
45
- └── changes/archive/ shipped changes + digests (the async human touchpoint)
46
- ```
47
-
48
- - **Contract-first**: tests + evals are written *before* code and gate everything after.
49
- - **AI-driven, exception-only humans**: the autonomy policy in `harness.md` decides what
50
- auto-ships and what escalates (hard-floor: security/payments/data-loss/irreversible).
51
- - **Managed hooks** (Claude Code): SessionStart injects ≤60 lines of static context;
52
- PreToolUse write-locks living specs; PostToolUse validates caps and points at steering;
53
- Stop journals real token usage. Other tools get the same rules as prose + the validator.
54
- - **Self-improving, leaner over time**: a post-ship learner proposes rules with evidence;
55
- the always-loaded budget is fixed, so learning must distill, not accumulate.
56
- - **Measured**: `npx @warnyin/sdlc observe` — tokens/cost per change, first-pass rate,
57
- lead time, dead steering, context-overflow flags.
58
-
59
- ## CLI
60
-
61
- ```
62
- warnyin-sdlc init [--tool all|none|a,b] scaffold + adapters + hooks (picker when omitted)
63
- warnyin-sdlc update [--force] refresh payload, guarded prune of stale files
64
- warnyin-sdlc validate [id] [--strict]
65
- warnyin-sdlc status | observe [--json]
66
- warnyin-sdlc archive <id> merge deltas into living specs + archive
67
- ```
68
-
69
- ## Commands (in your agent)
70
-
71
- `/sdlc:init` · `/sdlc:auto` · `/sdlc:new` · `/sdlc:design` · `/sdlc:contract` · `/sdlc:build`
72
- · `/sdlc:verify` · `/sdlc:review` · `/sdlc:ship` · `/sdlc:observe` · `/sdlc:converge`
73
- · `/sdlc:steer` · `/sdlc:next`
74
-
75
- Playbooks live in `sdlc/.playbook/` — behavior is defined once there; commands are thin pointers.
76
-
77
- ## Development
78
-
79
- Zero dependencies, Node 20. `npm test` runs the black-box suite (temp dirs, real CLI spawns).
80
- This repo self-hosts: its own development flows through `sdlc/changes/`. After cloning, run
81
- `npm run setup:dogfood` to regenerate the installer-owned mirrors (`sdlc/.playbook/`,
82
- `sdlc/.hooks/`, `.claude/`).
83
-
84
- MIT
1
+ # @warnyin/sdlc
2
+
3
+ **Spec-driven, AI-driven SDLC for coding agents — token-lean by construction.**
4
+
5
+ Operationalizes the *"New SDLC with Vibe Coding"* (Day-1) work process: the human configures
6
+ the harness once; the AI drives changes end-to-end through contract-first gates; deterministic
7
+ hooks and a validator enforce the rules; a journal prices every change in real tokens.
8
+
9
+ Inspired by OpenSpec (delta specs, archive lifecycle), spec-kit (artifact grammar), and
10
+ Kiro (steering + enforced hooks) — tuned for minimum context residency.
11
+
12
+ ## Install
13
+
14
+ ```bash
15
+ cd your-project
16
+ npx @warnyin/sdlc init # interactive picker; tools already in the project are pre-selected
17
+ ```
18
+
19
+ The picker is a checkbox list — arrows move, `space` toggles, typing filters, `ctrl+a` selects
20
+ everything on screen, `enter` confirms. Skip it in CI or scripts:
21
+
22
+ ```bash
23
+ npx @warnyin/sdlc init --tool claude,cursor # explicit list
24
+ npx @warnyin/sdlc init --tool all # every supported tool
25
+ npx @warnyin/sdlc init --tool none # sdlc/ framework only, no agent adapters
26
+ ```
27
+
28
+ Then in your coding agent:
29
+
30
+ ```
31
+ /sdlc:init # interview → constitution + harness (the one human gate)
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
34
+ /sdlc:new Add rate limiting --auto # any stage takes --auto: confirm once, then run to ship
35
+ ```
36
+
37
+ `--auto` asks everything up front — scope, tier, each ambiguity, and every escalation
38
+ it wants pre-approved as its own line you can refuse — then runs unattended. Nothing
39
+ is written until you confirm, the approval covers that run only, and anything you did
40
+ not pre-approve still stops and asks.
41
+
42
+ ## How it works
43
+
44
+ ```
45
+ sdlc/
46
+ ├── context/constitution.md ≤30 lines — the ONLY always-loaded prose (hook-injected)
47
+ ├── context/steering/*.md scoped knowledge · inclusion: always|paths|manual|agent
48
+ ├── harness.md tools, model routing, tier triage, autonomy policy
49
+ ├── specs/<capability>/ living specs WHEN/THEN SHALL, merged mechanically at ship
50
+ ├── changes/<id>/ one change: change.md (Why+Delta+Tasks) + contract/ + journal
51
+ └── changes/archive/ shipped changes + digests (the async human touchpoint)
52
+ ```
53
+
54
+ - **Contract-first**: tests + evals are written *before* code and gate everything after.
55
+ - **AI-driven, exception-only humans**: the autonomy policy in `harness.md` decides what
56
+ auto-ships and what escalates (hard-floor: security/payments/data-loss/irreversible).
57
+ - **Managed hooks** (Claude Code): SessionStart injects ≤60 lines of static context;
58
+ PreToolUse write-locks living specs; PostToolUse validates caps and points at steering;
59
+ Stop journals real token usage. Other tools get the same rules as prose + the validator.
60
+ - **Self-improving, leaner over time**: a post-ship learner proposes rules with evidence;
61
+ the always-loaded budget is fixed, so learning must distill, not accumulate.
62
+ - **Measured**: `npx @warnyin/sdlc observe` tokens/cost per change, first-pass rate,
63
+ lead time, dead steering, context-overflow flags.
64
+
65
+ ## CLI
66
+
67
+ ```
68
+ warnyin-sdlc init [--tool all|none|a,b] scaffold + adapters + hooks (picker when omitted)
69
+ warnyin-sdlc update [--force] refresh payload, guarded prune of stale files
70
+ warnyin-sdlc validate [id] [--strict]
71
+ warnyin-sdlc status | observe [--json]
72
+ warnyin-sdlc archive <id> merge deltas into living specs + archive
73
+ warnyin-sdlc version | --version print the installed framework version
74
+ ```
75
+
76
+ ## Commands (in your agent)
77
+
78
+ `/sdlc:init` · `/sdlc:auto` · `/sdlc:new` · `/sdlc:design` · `/sdlc:contract` · `/sdlc:build`
79
+ · `/sdlc:verify` · `/sdlc:review` · `/sdlc:ship` · `/sdlc:observe` · `/sdlc:converge`
80
+ · `/sdlc:steer` · `/sdlc:next` · `/sdlc:feedback`
81
+
82
+ Playbooks live in `sdlc/.playbook/` — behavior is defined once there; commands are thin pointers.
83
+
84
+ ## Development
85
+
86
+ Zero dependencies, Node ≥ 20. `npm test` runs the black-box suite (temp dirs, real CLI spawns).
87
+ This repo self-hosts: its own development flows through `sdlc/changes/`. After cloning, run
88
+ `npm run setup:dogfood` to regenerate the installer-owned mirrors (`sdlc/.playbook/`,
89
+ `sdlc/.hooks/`, `.claude/`).
90
+
91
+ MIT
package/bin/cli.mjs CHANGED
@@ -45,7 +45,7 @@ export function sha256(content) {
45
45
  }
46
46
 
47
47
  export function parseArgs(argv) {
48
- const args = { _: [], tool: null, toolProvided: false, strict: false, force: false, json: false, help: false };
48
+ const args = { _: [], tool: null, toolProvided: false, strict: false, force: false, json: false, help: false, version: false };
49
49
  for (let i = 0; i < argv.length; i++) {
50
50
  const a = argv[i];
51
51
  if (a === '--tool' || a === '--tools') {
@@ -56,6 +56,7 @@ export function parseArgs(argv) {
56
56
  else if (a === '--force') args.force = true;
57
57
  else if (a === '--json') args.json = true;
58
58
  else if (a === '--help' || a === '-h') args.help = true;
59
+ else if (a === '--version' || a === '-v') args.version = true;
59
60
  else if (a.startsWith('--')) console.warn(`unknown flag ${a} (ignored)`);
60
61
  else args._.push(a);
61
62
  }
@@ -526,6 +527,12 @@ function runValidate(projectRoot, args) {
526
527
  process.exitCode = res.status ?? 0;
527
528
  }
528
529
 
530
+ // Read from our own package.json: an npx install leaves nothing readable in the
531
+ // target project, and a report whose version is `unknown` cannot be triaged.
532
+ function pkgVersion() {
533
+ return JSON.parse(fs.readFileSync(path.join(PKG_ROOT, 'package.json'), 'utf8')).version;
534
+ }
535
+
529
536
  const HELP = `@warnyin/sdlc — spec-driven AI-SDLC framework
530
537
 
531
538
  usage: warnyin-sdlc <command> [options]
@@ -536,14 +543,18 @@ usage: warnyin-sdlc <command> [options]
536
543
  status [--json] list active changes and their stage
537
544
  observe [--json] tokens/cost per change, residency, steering hits, drift flags
538
545
  archive <id> merge delta specs into living specs and archive the change
546
+ version | --version | -v print the installed framework version
539
547
  help this text
540
548
  `;
541
549
 
542
550
  export async function main(argv = process.argv.slice(2), projectRoot = process.cwd()) {
543
551
  const args = parseArgs(argv);
544
552
  const cmd = args._[0];
545
- if (args.help || !cmd || cmd === 'help') { console.log(HELP); return; }
546
553
  try {
554
+ // before the help branch: `--version` carries no command, and `!cmd` would
555
+ // otherwise print help instead of the version.
556
+ if (args.version || cmd === 'version') { console.log(pkgVersion()); return; }
557
+ if (args.help || !cmd || cmd === 'help') { console.log(HELP); return; }
547
558
  if (cmd === 'init') await cmdInit(projectRoot, args);
548
559
  else if (cmd === 'update') cmdUpdate(projectRoot, args);
549
560
  else if (cmd === 'validate') runValidate(projectRoot, args);