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 +51 -0
- package/content/plugins/core/hooks/context-loader.js +13 -1
- package/content/plugins/core/hooks/damage-control-bash.js +10 -2
- package/content/plugins/core/hooks/damage-control-edit.js +4 -1
- package/content/plugins/core/hooks/damage-control-write.js +4 -1
- package/content/plugins/core/hooks/pre-compact-state.js +10 -1
- package/package.json +1 -1
- package/src/cli/commands/setup.js +27 -3
- package/src/cli/commands/update.js +13 -0
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
|
|
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
|
-
|
|
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
|
|
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
|
@@ -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(
|
|
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}`,
|