agileflow 2.94.1 → 2.95.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.
Files changed (73) hide show
  1. package/CHANGELOG.md +15 -0
  2. package/lib/colors.generated.js +117 -0
  3. package/lib/colors.js +59 -109
  4. package/lib/generator-factory.js +333 -0
  5. package/lib/path-utils.js +49 -0
  6. package/lib/session-registry.js +25 -15
  7. package/lib/smart-json-file.js +40 -32
  8. package/lib/state-machine.js +286 -0
  9. package/package.json +1 -1
  10. package/scripts/agileflow-configure.js +7 -6
  11. package/scripts/archive-completed-stories.sh +86 -11
  12. package/scripts/babysit-context-restore.js +89 -0
  13. package/scripts/claude-tmux.sh +111 -5
  14. package/scripts/damage-control/bash-tool-damage-control.js +11 -247
  15. package/scripts/damage-control/edit-tool-damage-control.js +9 -249
  16. package/scripts/damage-control/write-tool-damage-control.js +9 -244
  17. package/scripts/generate-colors.js +314 -0
  18. package/scripts/lib/colors.generated.sh +82 -0
  19. package/scripts/lib/colors.sh +10 -70
  20. package/scripts/lib/configure-features.js +401 -0
  21. package/scripts/lib/context-loader.js +181 -52
  22. package/scripts/precompact-context.sh +54 -17
  23. package/scripts/session-coordinator.sh +2 -2
  24. package/scripts/session-manager.js +653 -10
  25. package/src/core/commands/audit.md +93 -0
  26. package/src/core/commands/auto.md +73 -0
  27. package/src/core/commands/babysit.md +169 -13
  28. package/src/core/commands/baseline.md +73 -0
  29. package/src/core/commands/batch.md +64 -0
  30. package/src/core/commands/blockers.md +60 -0
  31. package/src/core/commands/board.md +66 -0
  32. package/src/core/commands/choose.md +77 -0
  33. package/src/core/commands/ci.md +77 -0
  34. package/src/core/commands/compress.md +27 -1
  35. package/src/core/commands/configure.md +126 -10
  36. package/src/core/commands/council.md +74 -0
  37. package/src/core/commands/debt.md +72 -0
  38. package/src/core/commands/deploy.md +73 -0
  39. package/src/core/commands/deps.md +68 -0
  40. package/src/core/commands/docs.md +60 -0
  41. package/src/core/commands/feedback.md +68 -0
  42. package/src/core/commands/ideate.md +74 -0
  43. package/src/core/commands/impact.md +74 -0
  44. package/src/core/commands/install.md +529 -0
  45. package/src/core/commands/maintain.md +558 -0
  46. package/src/core/commands/metrics.md +75 -0
  47. package/src/core/commands/multi-expert.md +74 -0
  48. package/src/core/commands/packages.md +69 -0
  49. package/src/core/commands/readme-sync.md +64 -0
  50. package/src/core/commands/research/analyze.md +285 -121
  51. package/src/core/commands/research/import.md +281 -109
  52. package/src/core/commands/retro.md +76 -0
  53. package/src/core/commands/review.md +72 -0
  54. package/src/core/commands/rlm.md +83 -0
  55. package/src/core/commands/rpi.md +90 -0
  56. package/src/core/commands/session/cleanup.md +214 -12
  57. package/src/core/commands/session/end.md +155 -17
  58. package/src/core/commands/sprint.md +72 -0
  59. package/src/core/commands/story-validate.md +68 -0
  60. package/src/core/commands/template.md +69 -0
  61. package/src/core/commands/tests.md +83 -0
  62. package/src/core/commands/update.md +59 -0
  63. package/src/core/commands/validate-expertise.md +76 -0
  64. package/src/core/commands/velocity.md +74 -0
  65. package/src/core/commands/verify.md +91 -0
  66. package/src/core/commands/whats-new.md +69 -0
  67. package/src/core/commands/workflow.md +88 -0
  68. package/src/core/templates/command-documentation.md +187 -0
  69. package/tools/cli/commands/session.js +1171 -0
  70. package/tools/cli/commands/setup.js +2 -81
  71. package/tools/cli/installers/core/installer.js +0 -5
  72. package/tools/cli/installers/ide/claude-code.js +6 -0
  73. package/tools/cli/lib/config-manager.js +42 -5
@@ -0,0 +1,286 @@
1
+ /**
2
+ * state-machine.js - Generic State Machine Base Class
3
+ *
4
+ * Provides a reusable state machine pattern for:
5
+ * - Story status transitions (ready → in_progress → completed)
6
+ * - Session thread type transitions (base → parallel → fusion)
7
+ *
8
+ * Features:
9
+ * - Configurable states and transitions
10
+ * - Transition validation with clear error messages
11
+ * - Audit trail support
12
+ * - Force mode for admin overrides
13
+ *
14
+ * Usage:
15
+ * const { StateMachine } = require('./state-machine');
16
+ *
17
+ * const storyMachine = new StateMachine({
18
+ * states: ['ready', 'in_progress', 'completed'],
19
+ * transitions: {
20
+ * ready: ['in_progress'],
21
+ * in_progress: ['completed', 'ready'],
22
+ * completed: [],
23
+ * },
24
+ * initial: 'ready',
25
+ * });
26
+ *
27
+ * const result = storyMachine.transition('ready', 'in_progress');
28
+ * // { success: true, from: 'ready', to: 'in_progress' }
29
+ */
30
+
31
+ /**
32
+ * Generic State Machine
33
+ */
34
+ class StateMachine {
35
+ /**
36
+ * @param {Object} config - State machine configuration
37
+ * @param {string[]} config.states - Valid state values
38
+ * @param {Object<string, string[]>} config.transitions - Map of state -> allowed next states
39
+ * @param {string} [config.initial] - Initial state (first in states array if not specified)
40
+ * @param {string} [config.name='state'] - Name for error messages (e.g., 'status', 'thread_type')
41
+ */
42
+ constructor(config) {
43
+ if (!config.states || !Array.isArray(config.states) || config.states.length === 0) {
44
+ throw new Error('StateMachine requires non-empty states array');
45
+ }
46
+ if (!config.transitions || typeof config.transitions !== 'object') {
47
+ throw new Error('StateMachine requires transitions object');
48
+ }
49
+
50
+ this.states = config.states;
51
+ this.transitions = config.transitions;
52
+ this.initial = config.initial || config.states[0];
53
+ this.name = config.name || 'state';
54
+
55
+ // Validate that all transition targets are valid states
56
+ for (const [from, targets] of Object.entries(this.transitions)) {
57
+ if (!this.states.includes(from)) {
58
+ throw new Error(`Invalid transition source state: ${from}`);
59
+ }
60
+ for (const to of targets) {
61
+ if (!this.states.includes(to)) {
62
+ throw new Error(`Invalid transition target state: ${to} (from ${from})`);
63
+ }
64
+ }
65
+ }
66
+ }
67
+
68
+ /**
69
+ * Check if a state is valid
70
+ * @param {string} state - State to check
71
+ * @returns {boolean}
72
+ */
73
+ isValidState(state) {
74
+ return this.states.includes(state);
75
+ }
76
+
77
+ /**
78
+ * Check if a transition is valid
79
+ * @param {string} from - Current state
80
+ * @param {string} to - Target state
81
+ * @returns {boolean}
82
+ */
83
+ isValidTransition(from, to) {
84
+ // Same state is always valid (no-op)
85
+ if (from === to) {
86
+ return true;
87
+ }
88
+
89
+ // Check if from state has defined transitions
90
+ const allowed = this.transitions[from];
91
+ if (!allowed) {
92
+ return false;
93
+ }
94
+
95
+ return allowed.includes(to);
96
+ }
97
+
98
+ /**
99
+ * Get valid transitions from a state
100
+ * @param {string} from - Current state
101
+ * @returns {string[]}
102
+ */
103
+ getValidTransitions(from) {
104
+ return this.transitions[from] || [];
105
+ }
106
+
107
+ /**
108
+ * Validate and perform a transition
109
+ * @param {string} from - Current state
110
+ * @param {string} to - Target state
111
+ * @param {Object} [options={}] - Transition options
112
+ * @param {boolean} [options.force=false] - Force transition even if invalid
113
+ * @returns {{success: boolean, from: string, to: string, error?: string, forced?: boolean}}
114
+ */
115
+ transition(from, to, options = {}) {
116
+ const { force = false } = options;
117
+
118
+ // Validate target state
119
+ if (!this.isValidState(to)) {
120
+ return {
121
+ success: false,
122
+ from,
123
+ to,
124
+ error: `Invalid ${this.name}: "${to}". Valid values: ${this.states.join(', ')}`,
125
+ };
126
+ }
127
+
128
+ // Validate source state
129
+ if (!this.isValidState(from)) {
130
+ return {
131
+ success: false,
132
+ from,
133
+ to,
134
+ error: `Invalid source ${this.name}: "${from}". Valid values: ${this.states.join(', ')}`,
135
+ };
136
+ }
137
+
138
+ // Same state is a no-op
139
+ if (from === to) {
140
+ return {
141
+ success: true,
142
+ from,
143
+ to,
144
+ noop: true,
145
+ };
146
+ }
147
+
148
+ // Check transition validity
149
+ if (!force && !this.isValidTransition(from, to)) {
150
+ const validTargets = this.getValidTransitions(from);
151
+ return {
152
+ success: false,
153
+ from,
154
+ to,
155
+ error: `Invalid transition: ${from} → ${to}. Valid transitions from "${from}": ${validTargets.join(', ') || 'none'}`,
156
+ };
157
+ }
158
+
159
+ return {
160
+ success: true,
161
+ from,
162
+ to,
163
+ forced: force && !this.isValidTransition(from, to),
164
+ };
165
+ }
166
+
167
+ /**
168
+ * Get the initial state
169
+ * @returns {string}
170
+ */
171
+ getInitialState() {
172
+ return this.initial;
173
+ }
174
+
175
+ /**
176
+ * Get all valid states
177
+ * @returns {string[]}
178
+ */
179
+ getStates() {
180
+ return [...this.states];
181
+ }
182
+
183
+ /**
184
+ * Get all transitions as a map
185
+ * @returns {Object<string, string[]>}
186
+ */
187
+ getTransitionsMap() {
188
+ return { ...this.transitions };
189
+ }
190
+
191
+ /**
192
+ * Generate a Mermaid state diagram
193
+ * @returns {string}
194
+ */
195
+ toMermaidDiagram() {
196
+ const lines = ['stateDiagram-v2'];
197
+
198
+ // Add initial state arrow
199
+ lines.push(` [*] --> ${this.initial}`);
200
+
201
+ // Add transitions
202
+ for (const [from, targets] of Object.entries(this.transitions)) {
203
+ for (const to of targets) {
204
+ lines.push(` ${from} --> ${to}`);
205
+ }
206
+ // Mark terminal states
207
+ if (targets.length === 0) {
208
+ lines.push(` ${from} --> [*]`);
209
+ }
210
+ }
211
+
212
+ return lines.join('\n');
213
+ }
214
+ }
215
+
216
+ // ============================================================================
217
+ // Pre-configured State Machines
218
+ // ============================================================================
219
+
220
+ /**
221
+ * Story status state machine
222
+ *
223
+ * States: ready, in_progress, in_review, blocked, completed, archived
224
+ *
225
+ * Transitions:
226
+ * - ready → in_progress, blocked
227
+ * - in_progress → in_review, blocked, ready
228
+ * - in_review → completed, in_progress, blocked
229
+ * - blocked → ready, in_progress, in_review
230
+ * - completed → archived, in_progress (reopened)
231
+ * - archived → (terminal)
232
+ */
233
+ const storyStatusMachine = new StateMachine({
234
+ name: 'status',
235
+ states: ['ready', 'in_progress', 'in_review', 'blocked', 'completed', 'archived'],
236
+ transitions: {
237
+ ready: ['in_progress', 'blocked'],
238
+ in_progress: ['in_review', 'blocked', 'ready'],
239
+ in_review: ['completed', 'in_progress', 'blocked'],
240
+ blocked: ['ready', 'in_progress', 'in_review'],
241
+ completed: ['archived', 'in_progress'],
242
+ archived: [], // Terminal state
243
+ },
244
+ initial: 'ready',
245
+ });
246
+
247
+ /**
248
+ * Session thread type state machine
249
+ *
250
+ * States: base, parallel, chained, fusion, big, long
251
+ *
252
+ * Thread Type Semantics:
253
+ * - base: Main session in project root (default)
254
+ * - parallel: Independent worktree session
255
+ * - chained: Sequential dependency on another session
256
+ * - fusion: Merged work from multiple sessions
257
+ * - big: Large task spanning multiple sessions
258
+ * - long: Extended session with context preservation
259
+ *
260
+ * Transitions:
261
+ * - base → parallel (spawn worktree)
262
+ * - parallel → base (merge to main), fusion (merge multiple), chained (add dependency)
263
+ * - chained → parallel (remove dependency), fusion (complete chain)
264
+ * - fusion → base (merge to main)
265
+ * - big → parallel (split), fusion (consolidate)
266
+ * - long → base (complete), parallel (split)
267
+ */
268
+ const sessionThreadMachine = new StateMachine({
269
+ name: 'thread_type',
270
+ states: ['base', 'parallel', 'chained', 'fusion', 'big', 'long'],
271
+ transitions: {
272
+ base: ['parallel', 'big', 'long'],
273
+ parallel: ['base', 'fusion', 'chained'],
274
+ chained: ['parallel', 'fusion'],
275
+ fusion: ['base'],
276
+ big: ['parallel', 'fusion'],
277
+ long: ['base', 'parallel'],
278
+ },
279
+ initial: 'base',
280
+ });
281
+
282
+ module.exports = {
283
+ StateMachine,
284
+ storyStatusMachine,
285
+ sessionThreadMachine,
286
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agileflow",
3
- "version": "2.94.1",
3
+ "version": "2.95.0",
4
4
  "description": "AI-driven agile development system for Claude Code, Cursor, Windsurf, and more",
5
5
  "keywords": [
6
6
  "agile",
@@ -23,7 +23,7 @@
23
23
  * --detect Show current status
24
24
  * --help Show help
25
25
  *
26
- * Features: sessionstart, precompact, ralphloop, selfimprove, archival, statusline, autoupdate, damagecontrol, askuserquestion, tmuxautospawn
26
+ * Features: sessionstart, precompact, ralphloop, selfimprove, archival, statusline, autoupdate, damagecontrol, askuserquestion, tmuxautospawn, shellaliases, claudemdreinforcement
27
27
  */
28
28
 
29
29
  const fs = require('fs');
@@ -121,16 +121,17 @@ ${c.cyan}Usage:${c.reset}
121
121
  node .agileflow/scripts/agileflow-configure.js [options]
122
122
 
123
123
  ${c.cyan}Profiles:${c.reset}
124
- --profile=full All features (hooks, Stop hooks, archival, statusline)
125
- --profile=basic SessionStart + PreCompact + archival (no Stop hooks)
126
- --profile=minimal SessionStart + archival only
127
- --profile=none Disable all AgileFlow features
124
+ --profile=full All features (hooks, Stop hooks, archival, statusline)
125
+ --profile=basic SessionStart + PreCompact + archival (no Stop hooks)
126
+ --profile=minimal SessionStart + archival only
127
+ --profile=experimental ⚠️ All features + FULL FILE injection during compact (CONTEXT HEAVY)
128
+ --profile=none Disable all AgileFlow features
128
129
 
129
130
  ${c.cyan}Feature Control:${c.reset}
130
131
  --enable=<list> Enable features (comma-separated)
131
132
  --disable=<list> Disable features (comma-separated)
132
133
 
133
- Features: sessionstart, precompact, ralphloop, selfimprove, archival, statusline, damagecontrol, askuserquestion, tmuxautospawn
134
+ Features: sessionstart, precompact, ralphloop, selfimprove, archival, statusline, damagecontrol, askuserquestion, tmuxautospawn, shellaliases, claudemdreinforcement
134
135
 
135
136
  ${c.cyan}Statusline Components:${c.reset}
136
137
  --show=<list> Show statusline components (comma-separated)
@@ -105,13 +105,87 @@ echo -e "${BLUE}Cutoff date: $CUTOFF_DATE${NC}"
105
105
 
106
106
  # Archive using Node.js (more reliable for JSON manipulation)
107
107
  if command -v node &> /dev/null; then
108
- STATUS_FILE="$STATUS_FILE" ARCHIVE_DIR="$ARCHIVE_DIR" CUTOFF_DATE="$CUTOFF_DATE" node <<'EOF'
108
+ STATUS_FILE="$STATUS_FILE" ARCHIVE_DIR="$ARCHIVE_DIR" CUTOFF_DATE="$CUTOFF_DATE" PROJECT_ROOT="$PROJECT_ROOT" node <<'EOF'
109
109
  const fs = require('fs');
110
110
  const path = require('path');
111
111
 
112
112
  const statusFile = process.env.STATUS_FILE;
113
113
  const archiveDir = process.env.ARCHIVE_DIR;
114
114
  const cutoffDate = process.env.CUTOFF_DATE;
115
+ const projectRoot = process.env.PROJECT_ROOT;
116
+
117
+ // =============================================================================
118
+ // Security: Inline validatePath equivalent (US-0188)
119
+ // =============================================================================
120
+
121
+ /**
122
+ * Validate a path is safe and within the base directory.
123
+ * Rejects direct symlinks within the path but allows symlinked parent directories
124
+ * (needed for git worktrees where docs/ is often symlinked).
125
+ * @param {string} inputPath - Path to validate
126
+ * @param {string} baseDir - Allowed base directory
127
+ * @returns {{ ok: boolean, resolvedPath?: string, realPath?: string, error?: string }}
128
+ */
129
+ function validatePath(inputPath, baseDir) {
130
+ if (!inputPath || typeof inputPath !== 'string') {
131
+ return { ok: false, error: 'Path is required and must be a string' };
132
+ }
133
+ if (!baseDir || typeof baseDir !== 'string') {
134
+ return { ok: false, error: 'Base directory is required' };
135
+ }
136
+
137
+ // Resolve to absolute path
138
+ const resolvedPath = path.resolve(baseDir, inputPath);
139
+ const resolvedBase = path.resolve(baseDir);
140
+
141
+ // Check path stays within base directory (path traversal prevention)
142
+ if (!resolvedPath.startsWith(resolvedBase + path.sep) && resolvedPath !== resolvedBase) {
143
+ return { ok: false, error: `Path traversal detected: ${inputPath} escapes ${baseDir}` };
144
+ }
145
+
146
+ // Check if the final target path itself is a symlink (allowSymlinks: false for target)
147
+ // Note: We allow parent directories to be symlinks (needed for git worktrees)
148
+ try {
149
+ const stats = fs.lstatSync(resolvedPath);
150
+ if (stats.isSymbolicLink()) {
151
+ // The actual file/directory we're writing to is a symlink - reject
152
+ return { ok: false, error: `Target path is a symlink: ${resolvedPath}` };
153
+ }
154
+ } catch (e) {
155
+ // Path doesn't exist yet, that's OK for new files
156
+ if (e.code !== 'ENOENT') {
157
+ return { ok: false, error: `Cannot stat path: ${e.message}` };
158
+ }
159
+ }
160
+
161
+ // Use fs.realpathSync() to get the actual path after symlink resolution
162
+ let realPath = resolvedPath;
163
+ try {
164
+ realPath = fs.realpathSync(resolvedPath);
165
+ // We don't restrict realPath to baseDir because parent directories may be
166
+ // symlinked (e.g., git worktrees). The key protection is:
167
+ // 1. path.resolve() prevents ../../ traversal in the input
168
+ // 2. lstatSync() above prevents the target itself from being a symlink
169
+ } catch (e) {
170
+ // Path doesn't exist yet, use resolved path
171
+ if (e.code !== 'ENOENT') {
172
+ return { ok: false, error: `Cannot resolve real path: ${e.message}` };
173
+ }
174
+ realPath = resolvedPath;
175
+ }
176
+
177
+ return { ok: true, resolvedPath, realPath };
178
+ }
179
+
180
+ // =============================================================================
181
+ // Validate archive directory (US-0188)
182
+ // =============================================================================
183
+
184
+ const archiveDirValidation = validatePath(archiveDir, projectRoot);
185
+ if (!archiveDirValidation.ok) {
186
+ console.error(`\x1b[31mSecurity: ${archiveDirValidation.error}. Aborting.\x1b[0m`);
187
+ process.exit(1);
188
+ }
115
189
 
116
190
  // Read status.json
117
191
  const status = JSON.parse(fs.readFileSync(statusFile, 'utf8'));
@@ -146,7 +220,7 @@ for (const [storyId, story] of Object.entries(toArchive)) {
146
220
  const date = new Date(story.completed_at);
147
221
  const monthKey = `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, '0')}`;
148
222
 
149
- // Security: Validate monthKey matches expected format (YYYY-MM) to prevent path traversal
223
+ // Security: Validate monthKey matches expected format (YYYY-MM) to prevent path traversal (US-0188 AC)
150
224
  if (!/^\d{4}-\d{2}$/.test(monthKey)) {
151
225
  console.error(`\x1b[31mSkipping story ${storyId}: invalid date format\x1b[0m`);
152
226
  continue;
@@ -165,27 +239,28 @@ for (const [storyId, story] of Object.entries(toArchive)) {
165
239
 
166
240
  // Write archive files
167
241
  for (const [monthKey, archiveData] of Object.entries(byMonth)) {
168
- const archiveFile = path.join(archiveDir, `${monthKey}.json`);
242
+ const archiveFile = `${monthKey}.json`;
169
243
 
170
- // Security: Verify resolved path stays within archive directory
171
- const resolvedPath = path.resolve(archiveFile);
172
- const resolvedArchiveDir = path.resolve(archiveDir);
173
- if (!resolvedPath.startsWith(resolvedArchiveDir + path.sep) && resolvedPath !== resolvedArchiveDir) {
174
- console.error(`\x1b[31mSecurity: Archive path ${archiveFile} escapes archive directory. Skipping.\x1b[0m`);
244
+ // Security: Use validatePath() with allowSymlinks: false (US-0188 AC)
245
+ const validation = validatePath(archiveFile, archiveDir);
246
+ if (!validation.ok) {
247
+ console.error(`\x1b[31mSecurity: ${validation.error}. Skipping ${monthKey}.\x1b[0m`);
175
248
  continue;
176
249
  }
177
250
 
251
+ const finalPath = validation.resolvedPath;
252
+
178
253
  // Merge with existing archive if it exists
179
- if (fs.existsSync(archiveFile)) {
254
+ if (fs.existsSync(finalPath)) {
180
255
  try {
181
- const existing = JSON.parse(fs.readFileSync(archiveFile, 'utf8'));
256
+ const existing = JSON.parse(fs.readFileSync(finalPath, 'utf8'));
182
257
  archiveData.stories = { ...existing.stories, ...archiveData.stories };
183
258
  } catch (e) {
184
259
  console.error(`\x1b[31mWarning: Could not parse existing ${monthKey}.json, will overwrite\x1b[0m`);
185
260
  }
186
261
  }
187
262
 
188
- fs.writeFileSync(archiveFile, JSON.stringify(archiveData, null, 2));
263
+ fs.writeFileSync(finalPath, JSON.stringify(archiveData, null, 2));
189
264
  const count = Object.keys(archiveData.stories).length;
190
265
  console.log(`\x1b[32m✓ Archived ${count} stories to ${monthKey}.json\x1b[0m`);
191
266
  }
@@ -0,0 +1,89 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * babysit-context-restore.js - UserPromptSubmit hook
4
+ *
5
+ * Backup mechanism to restore babysit context after plan mode clears context.
6
+ * When user selects "Clear context and bypass permissions" after ExitPlanMode,
7
+ * this hook fires on the next user prompt and reminds Claude of babysit rules.
8
+ *
9
+ * The primary mechanism is embedding rules in the plan file (Rule #6).
10
+ * This hook is a backup for edge cases where plan file approach might miss.
11
+ *
12
+ * Usage: Called automatically as UserPromptSubmit hook
13
+ */
14
+
15
+ const fs = require('fs');
16
+ const path = require('path');
17
+
18
+ // Find session-state.json - try multiple locations
19
+ function findSessionState() {
20
+ const locations = [
21
+ 'docs/09-agents/session-state.json',
22
+ path.join(process.env.CLAUDE_PROJECT_DIR || '.', 'docs/09-agents/session-state.json'),
23
+ ];
24
+
25
+ for (const loc of locations) {
26
+ if (fs.existsSync(loc)) {
27
+ return loc;
28
+ }
29
+ }
30
+ return null;
31
+ }
32
+
33
+ function main() {
34
+ const sessionStatePath = findSessionState();
35
+ if (!sessionStatePath) return;
36
+
37
+ try {
38
+ const state = JSON.parse(fs.readFileSync(sessionStatePath, 'utf8'));
39
+
40
+ // Check if restoration is pending
41
+ if (!state.babysit_pending_restore) return;
42
+
43
+ // Output restoration context
44
+ console.log('');
45
+ console.log('\x1b[36m╔══════════════════════════════════════════════════════════════╗\x1b[0m');
46
+ console.log(
47
+ '\x1b[36m║\x1b[0m \x1b[1m\x1b[33m/babysit CONTEXT RESTORED\x1b[0m \x1b[36m║\x1b[0m'
48
+ );
49
+ console.log('\x1b[36m╠══════════════════════════════════════════════════════════════╣\x1b[0m');
50
+ console.log(
51
+ '\x1b[36m║\x1b[0m /agileflow:babysit was active before context clear. \x1b[36m║\x1b[0m'
52
+ );
53
+ console.log(
54
+ '\x1b[36m║\x1b[0m These rules are MANDATORY: \x1b[36m║\x1b[0m'
55
+ );
56
+ console.log(
57
+ '\x1b[36m║\x1b[0m \x1b[36m║\x1b[0m'
58
+ );
59
+ console.log(
60
+ '\x1b[36m║\x1b[0m 1. ALWAYS end responses with AskUserQuestion tool \x1b[36m║\x1b[0m'
61
+ );
62
+ console.log(
63
+ '\x1b[36m║\x1b[0m 2. Use EnterPlanMode before non-trivial tasks \x1b[36m║\x1b[0m'
64
+ );
65
+ console.log(
66
+ '\x1b[36m║\x1b[0m 3. Delegate complex work to domain experts \x1b[36m║\x1b[0m'
67
+ );
68
+ console.log(
69
+ '\x1b[36m║\x1b[0m 4. Track progress with TodoWrite for 3+ step tasks \x1b[36m║\x1b[0m'
70
+ );
71
+ console.log(
72
+ '\x1b[36m║\x1b[0m \x1b[36m║\x1b[0m'
73
+ );
74
+ console.log(
75
+ '\x1b[36m║\x1b[0m For full context: /agileflow:babysit \x1b[36m║\x1b[0m'
76
+ );
77
+ console.log('\x1b[36m╚══════════════════════════════════════════════════════════════╝\x1b[0m');
78
+ console.log('');
79
+
80
+ // Clear the flag (one-time restoration)
81
+ state.babysit_pending_restore = false;
82
+ state.babysit_restored_at = new Date().toISOString();
83
+ fs.writeFileSync(sessionStatePath, JSON.stringify(state, null, 2) + '\n');
84
+ } catch (e) {
85
+ // Silently fail - don't break user's workflow
86
+ }
87
+ }
88
+
89
+ main();