agileflow 4.0.0-alpha.2 → 4.0.0-alpha.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.
package/CHANGELOG.md CHANGED
@@ -3,6 +3,57 @@
3
3
  All notable changes to `agileflow` v4 are documented here.
4
4
  Format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
5
5
 
6
+ ## [4.0.0-alpha.3] — 2026-04-20
7
+
8
+ Flow audit fixes for the alpha.2 wizard + install path. Wiring/persistence
9
+ came back PASS; this patch closes the test gap and fixes the safety +
10
+ feedback gaps the audit surfaced.
11
+
12
+ ### Fixed
13
+
14
+ - **P0 test gap** (`tests/unit/config/writer.test.js`): the per-field
15
+ round-trip block tests every other config field but skipped
16
+ `behaviors`. A future PR that drops the `behaviors:` line from
17
+ `writer.js`'s payload would have shipped silently. Added explicit
18
+ mixed-shape round-trip test (not all-true, not all-false) so the
19
+ serializer-loader pair is contractually pinned.
20
+ - **P1 damage-control silent fail-open**
21
+ (`damage-control-bash.js`/`-edit.js`/`-write.js`): when
22
+ `damage-control-patterns.yaml` is missing or unreadable, all three
23
+ hooks used to `process.exit(0)` silently — guards disabled, no
24
+ signal to the user. Now emit a stderr WARNING with the error code
25
+ and the path. Repeated warnings on every Bash/Edit/Write are
26
+ intentional: they signal "fix this or disable the preset". Hooks
27
+ still fail-open (the contract is "block dangerous things, don't
28
+ block legit work just because we can't read our own config").
29
+ - **P1 missing behaviors visibility**:
30
+ - `setup --yes` console output now prints `behaviors enabled: ...`
31
+ after the plugin list, gated on `caps.hooks` so non-Claude-Code
32
+ IDEs don't see a noisy line. Listed-as-CSV in the order: any
33
+ `loadContext, babysitDefault, damageControl, preCompactState`
34
+ that are `true`.
35
+ - Interactive `prompts.outro` now includes `behaviors active: ...`
36
+ or `behaviors active: (none — no hooks will run; re-run setup to
37
+ enable)`. A user who deselected all four behaviors no longer
38
+ finishes the wizard celebrating "X plugins enabled" while
39
+ actually getting zero hooks.
40
+ - `agileflow update` console output mirrors the same pattern.
41
+ - Install spinner message changed from `Installing N plugin(s)` to
42
+ `Installing N plugin(s) — writing hooks, skills, mirrors` so
43
+ first-time users have a clearer mental model of what `.agileflow/`
44
+ will contain.
45
+ - **P2 fresh-project context-loader / pre-compact-state**: when
46
+ `docs/09-agents/status.json` is absent (brand new project), both
47
+ hooks used to silently omit the stories section. Now emit
48
+ `(no story tracker yet — docs/09-agents/status.json not found)` so
49
+ Claude knows the section was reached, not skipped due to error. Also
50
+ surfaces `(none in progress, none ready)` when status.json exists
51
+ but is empty.
52
+
53
+ ### Tests
54
+
55
+ - 305 passing (+1 from alpha.2's 304).
56
+
6
57
  ## [4.0.0-alpha.2] — 2026-04-20
7
58
 
8
59
  Curated behavior presets — first hooks ship, but never as a free-for-all.
@@ -87,7 +87,13 @@ out("");
87
87
 
88
88
  // 2. Active story / epic
89
89
  const status = readJSON(path.join(projectDir, "docs/09-agents/status.json"));
90
- if (status && status.stories) {
90
+ if (!status) {
91
+ // Fresh project — no story tracker yet. Tell Claude explicitly so it
92
+ // knows the section was reached, not silently skipped due to error.
93
+ out("## Stories");
94
+ out(" (no story tracker yet — docs/09-agents/status.json not found)");
95
+ out("");
96
+ } else if (status.stories) {
91
97
  const inProgress = Object.entries(status.stories)
92
98
  .filter(([, s]) => s && s.status === "in_progress")
93
99
  .map(([id, s]) => ({ id, ...s }));
@@ -121,6 +127,12 @@ if (status && status.stories) {
121
127
  }
122
128
  out("");
123
129
  }
130
+
131
+ if (!inProgress.length && !ready.length) {
132
+ out("## Stories");
133
+ out(" (none in progress, none ready)");
134
+ out("");
135
+ }
124
136
  }
125
137
 
126
138
  // 4. Git state
@@ -49,8 +49,16 @@ async function main() {
49
49
  try {
50
50
  const parsed = yaml.load(fs.readFileSync(patternsPath, "utf8"));
51
51
  patterns = Array.isArray(parsed && parsed.patterns) ? parsed.patterns : [];
52
- } catch {
53
- process.exit(0); // No patterns file, fail open.
52
+ } catch (err) {
53
+ // Fail open, but warn loudly: a missing/unreadable patterns file
54
+ // means every dangerous command will go through unblocked, and
55
+ // the user MUST notice. Repeated warnings on every Bash call are
56
+ // intentional — they signal "damageControl is broken, fix it or
57
+ // disable the preset in agileflow.config.json".
58
+ process.stderr.write(
59
+ `agileflow damage-control: WARNING — patterns file unreadable (${err.code || err.name}: ${patternsPath}). Bash safety guards are DISABLED until this is fixed.\n`,
60
+ );
61
+ process.exit(0);
54
62
  }
55
63
 
56
64
  for (const p of patterns) {
@@ -46,7 +46,10 @@ async function main() {
46
46
  try {
47
47
  const parsed = yaml.load(fs.readFileSync(patternsPath, "utf8"));
48
48
  patterns = Array.isArray(parsed && parsed.patterns) ? parsed.patterns : [];
49
- } catch {
49
+ } catch (err) {
50
+ process.stderr.write(
51
+ `agileflow damage-control: WARNING — patterns file unreadable (${err.code || err.name}: ${patternsPath}). Edit safety guards are DISABLED until this is fixed.\n`,
52
+ );
50
53
  process.exit(0);
51
54
  }
52
55
 
@@ -42,7 +42,10 @@ async function main() {
42
42
  try {
43
43
  const parsed = yaml.load(fs.readFileSync(patternsPath, "utf8"));
44
44
  patterns = Array.isArray(parsed && parsed.patterns) ? parsed.patterns : [];
45
- } catch {
45
+ } catch (err) {
46
+ process.stderr.write(
47
+ `agileflow damage-control: WARNING — patterns file unreadable (${err.code || err.name}: ${patternsPath}). Write safety guards are DISABLED until this is fixed.\n`,
48
+ );
46
49
  process.exit(0);
47
50
  }
48
51
 
@@ -43,7 +43,13 @@ out.push("## Pre-compaction state preservation");
43
43
  out.push("");
44
44
 
45
45
  const status = readJSON(path.join(projectDir, "docs/09-agents/status.json"));
46
- if (status && status.stories) {
46
+ if (!status) {
47
+ // Fresh project — no story tracker yet. Say so explicitly so the
48
+ // post-compaction prompt knows the section was reached, not silently
49
+ // skipped due to an error.
50
+ out.push("Active stories: (none yet — docs/09-agents/status.json not found)");
51
+ out.push("");
52
+ } else if (status.stories) {
47
53
  const inProgress = Object.entries(status.stories)
48
54
  .filter(([, s]) => s && s.status === "in_progress")
49
55
  .map(([id, s]) => `${id} ${s.title || ""}`);
@@ -51,6 +57,9 @@ if (status && status.stories) {
51
57
  out.push("Active stories:");
52
58
  for (const s of inProgress) out.push(` - ${s}`);
53
59
  out.push("");
60
+ } else {
61
+ out.push("Active stories: (none in progress)");
62
+ out.push("");
54
63
  }
55
64
  }
56
65
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agileflow",
3
- "version": "4.0.0-alpha.2",
3
+ "version": "4.0.0-alpha.3",
4
4
  "description": "AI-driven agile development toolkit for Claude Code — skills-first architecture with opt-in plugins (v4)",
5
5
  "keywords": [
6
6
  "agile",
@@ -203,6 +203,15 @@ async function setup(options = {}) {
203
203
  );
204
204
  // eslint-disable-next-line no-console
205
205
  console.log(` plugins enabled: ${enabled.join(", ")}`);
206
+ if (caps.hooks) {
207
+ const activeBehaviors = Object.entries(next.behaviors || {})
208
+ .filter(([, v]) => v)
209
+ .map(([k]) => k);
210
+ // eslint-disable-next-line no-console
211
+ console.log(
212
+ ` behaviors enabled: ${activeBehaviors.length ? activeBehaviors.join(", ") : "(none — no hooks will run)"}`,
213
+ );
214
+ }
206
215
  // eslint-disable-next-line no-console
207
216
  console.log(
208
217
  ` installed: created=${installResult.ops.created} updated=${installResult.ops.updated} unchanged=${installResult.ops.unchanged} preserved=${installResult.ops.preserved} removed=${installResult.ops.removed}`,
@@ -262,7 +271,9 @@ async function setup(options = {}) {
262
271
  .map(([id]) => id);
263
272
 
264
273
  const installSpinner = prompts.spinner();
265
- installSpinner.start(`Installing ${enabledList.length} plugin(s)`);
274
+ installSpinner.start(
275
+ `Installing ${enabledList.length} plugin(s) — writing hooks, skills, mirrors`,
276
+ );
266
277
  const installResult = await runInstallWithFeedback(
267
278
  enabledList,
268
279
  cwd,
@@ -274,14 +285,27 @@ async function setup(options = {}) {
274
285
  `Installed: created=${installResult.ops.created} updated=${installResult.ops.updated} unchanged=${installResult.ops.unchanged} preserved=${installResult.ops.preserved} removed=${installResult.ops.removed}`,
275
286
  );
276
287
 
288
+ // Surface behaviors state in the outro. With behaviors gated, a user
289
+ // who deselected all four ends up with zero hooks running — they
290
+ // need to know that explicitly, not infer it from "X plugins enabled".
291
+ const activeBehaviors = ideCaps.hooks
292
+ ? Object.entries(behaviors || {})
293
+ .filter(([, v]) => v)
294
+ .map(([k]) => k)
295
+ : [];
296
+ const behaviorsLine = ideCaps.hooks
297
+ ? activeBehaviors.length
298
+ ? `behaviors active: ${activeBehaviors.join(", ")}`
299
+ : "behaviors active: (none — no hooks will run; re-run setup to enable)"
300
+ : `hooks not supported by ${ide} — behaviors skipped`;
301
+
277
302
  prompts.outro(
278
303
  [
279
304
  `${enabledList.length} plugin(s) enabled: ${enabledList.join(", ")}`,
305
+ behaviorsLine,
280
306
  installResult.ops.preserved
281
307
  ? `${installResult.ops.preserved} file(s) preserved (your edits) — review .agileflow/_cfg/updates/`
282
308
  : "",
283
- "",
284
- "Phase 3+ will land hooks, Core content, and the publish pipeline.",
285
309
  ]
286
310
  .filter(Boolean)
287
311
  .join("\n"),
@@ -18,6 +18,7 @@ const pkg = require("../../../package.json");
18
18
  const { loadConfig } = require("../../runtime/config/loader.js");
19
19
  const { discoverPlugins } = require("../../runtime/plugins/registry.js");
20
20
  const { installPlugins } = require("../../runtime/installer/install.js");
21
+ const { capabilitiesFor } = require("../../runtime/ide/capabilities.js");
21
22
 
22
23
  /**
23
24
  * @param {{ force?: boolean }} options
@@ -68,6 +69,18 @@ async function update(options = {}) {
68
69
 
69
70
  // eslint-disable-next-line no-console
70
71
  console.log(`✓ Updated ${enabled.length} plugin(s): ${enabled.join(", ")}`);
72
+
73
+ const caps = capabilitiesFor(existing.config.ide.primary);
74
+ if (caps.hooks) {
75
+ const activeBehaviors = Object.entries(existing.config.behaviors || {})
76
+ .filter(([, v]) => v)
77
+ .map(([k]) => k);
78
+ // eslint-disable-next-line no-console
79
+ console.log(
80
+ ` behaviors enabled: ${activeBehaviors.length ? activeBehaviors.join(", ") : "(none — no hooks will run)"}`,
81
+ );
82
+ }
83
+
71
84
  // eslint-disable-next-line no-console
72
85
  console.log(
73
86
  ` created=${result.ops.created} updated=${result.ops.updated} unchanged=${result.ops.unchanged} preserved=${result.ops.preserved} removed=${result.ops.removed}`,