agileflow 3.1.0 → 3.2.1

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 (106) hide show
  1. package/CHANGELOG.md +10 -0
  2. package/README.md +57 -85
  3. package/lib/dashboard-automations.js +130 -0
  4. package/lib/dashboard-git.js +254 -0
  5. package/lib/dashboard-inbox.js +64 -0
  6. package/lib/dashboard-protocol.js +1 -0
  7. package/lib/dashboard-server.js +114 -924
  8. package/lib/dashboard-session.js +136 -0
  9. package/lib/dashboard-status.js +72 -0
  10. package/lib/dashboard-terminal.js +354 -0
  11. package/lib/dashboard-websocket.js +88 -0
  12. package/lib/drivers/codex-driver.ts +4 -4
  13. package/lib/logger.js +106 -0
  14. package/package.json +4 -2
  15. package/scripts/agileflow-configure.js +2 -2
  16. package/scripts/agileflow-welcome.js +409 -434
  17. package/scripts/claude-tmux.sh +80 -2
  18. package/scripts/context-loader.js +4 -9
  19. package/scripts/lib/browser-qa-evidence.js +409 -0
  20. package/scripts/lib/browser-qa-status.js +192 -0
  21. package/scripts/lib/command-prereqs.js +280 -0
  22. package/scripts/lib/configure-detect.js +92 -2
  23. package/scripts/lib/configure-features.js +295 -1
  24. package/scripts/lib/context-formatter.js +468 -233
  25. package/scripts/lib/context-loader.js +27 -15
  26. package/scripts/lib/damage-control-utils.js +8 -1
  27. package/scripts/lib/feature-catalog.js +321 -0
  28. package/scripts/lib/portable-tasks-cli.js +274 -0
  29. package/scripts/lib/portable-tasks.js +479 -0
  30. package/scripts/lib/signal-detectors.js +1 -1
  31. package/scripts/lib/team-events.js +86 -1
  32. package/scripts/obtain-context.js +28 -4
  33. package/scripts/smart-detect.js +17 -0
  34. package/scripts/strip-ai-attribution.js +63 -0
  35. package/scripts/team-manager.js +7 -2
  36. package/scripts/welcome-deferred.js +437 -0
  37. package/src/core/agents/browser-qa.md +328 -0
  38. package/src/core/agents/perf-analyzer-assets.md +174 -0
  39. package/src/core/agents/perf-analyzer-bundle.md +165 -0
  40. package/src/core/agents/perf-analyzer-caching.md +160 -0
  41. package/src/core/agents/perf-analyzer-compute.md +165 -0
  42. package/src/core/agents/perf-analyzer-memory.md +182 -0
  43. package/src/core/agents/perf-analyzer-network.md +157 -0
  44. package/src/core/agents/perf-analyzer-queries.md +155 -0
  45. package/src/core/agents/perf-analyzer-rendering.md +156 -0
  46. package/src/core/agents/perf-consensus.md +280 -0
  47. package/src/core/agents/security-analyzer-api.md +199 -0
  48. package/src/core/agents/security-analyzer-auth.md +160 -0
  49. package/src/core/agents/security-analyzer-authz.md +168 -0
  50. package/src/core/agents/security-analyzer-deps.md +147 -0
  51. package/src/core/agents/security-analyzer-infra.md +176 -0
  52. package/src/core/agents/security-analyzer-injection.md +148 -0
  53. package/src/core/agents/security-analyzer-input.md +191 -0
  54. package/src/core/agents/security-analyzer-secrets.md +175 -0
  55. package/src/core/agents/security-consensus.md +276 -0
  56. package/src/core/agents/test-analyzer-assertions.md +181 -0
  57. package/src/core/agents/test-analyzer-coverage.md +183 -0
  58. package/src/core/agents/test-analyzer-fragility.md +185 -0
  59. package/src/core/agents/test-analyzer-integration.md +155 -0
  60. package/src/core/agents/test-analyzer-maintenance.md +173 -0
  61. package/src/core/agents/test-analyzer-mocking.md +178 -0
  62. package/src/core/agents/test-analyzer-patterns.md +189 -0
  63. package/src/core/agents/test-analyzer-structure.md +177 -0
  64. package/src/core/agents/test-consensus.md +294 -0
  65. package/src/core/commands/{legal/audit.md → audit/legal.md} +13 -13
  66. package/src/core/commands/{logic/audit.md → audit/logic.md} +12 -12
  67. package/src/core/commands/audit/performance.md +443 -0
  68. package/src/core/commands/audit/security.md +443 -0
  69. package/src/core/commands/audit/test.md +442 -0
  70. package/src/core/commands/babysit.md +505 -463
  71. package/src/core/commands/browser-qa.md +240 -0
  72. package/src/core/commands/configure.md +8 -8
  73. package/src/core/commands/research/ask.md +42 -9
  74. package/src/core/commands/research/import.md +14 -8
  75. package/src/core/commands/research/list.md +17 -16
  76. package/src/core/commands/research/synthesize.md +8 -8
  77. package/src/core/commands/research/view.md +28 -4
  78. package/src/core/commands/whats-new.md +2 -2
  79. package/src/core/experts/devops/expertise.yaml +13 -2
  80. package/src/core/experts/documentation/expertise.yaml +26 -4
  81. package/src/core/profiles/COMPARISON.md +170 -0
  82. package/src/core/profiles/README.md +178 -0
  83. package/src/core/profiles/claude-code.yaml +111 -0
  84. package/src/core/profiles/codex.yaml +103 -0
  85. package/src/core/profiles/cursor.yaml +134 -0
  86. package/src/core/profiles/examples.js +250 -0
  87. package/src/core/profiles/loader.js +235 -0
  88. package/src/core/profiles/windsurf.yaml +159 -0
  89. package/src/core/teams/logic-audit.json +6 -0
  90. package/src/core/teams/perf-audit.json +71 -0
  91. package/src/core/teams/security-audit.json +71 -0
  92. package/src/core/teams/test-audit.json +71 -0
  93. package/src/core/templates/browser-qa-spec.yaml +94 -0
  94. package/src/core/templates/command-prerequisites.yaml +169 -0
  95. package/src/core/templates/damage-control-patterns.yaml +9 -0
  96. package/tools/cli/installers/ide/_base-ide.js +33 -3
  97. package/tools/cli/installers/ide/claude-code.js +2 -69
  98. package/tools/cli/installers/ide/codex.js +9 -9
  99. package/tools/cli/installers/ide/cursor.js +165 -4
  100. package/tools/cli/installers/ide/windsurf.js +237 -6
  101. package/tools/cli/lib/content-transformer.js +234 -9
  102. package/tools/cli/lib/docs-setup.js +1 -1
  103. package/tools/cli/lib/ide-generator.js +357 -0
  104. package/tools/cli/lib/ide-registry.js +2 -2
  105. package/scripts/tmux-task-name.sh +0 -105
  106. package/scripts/tmux-task-watcher.sh +0 -344
@@ -0,0 +1,192 @@
1
+ /**
2
+ * browser-qa-status.js - Agentic test status integration for status.json
3
+ *
4
+ * Adds and manages `agentic_test_status` field on stories in status.json.
5
+ * This field is separate from `test_status` (which tracks deterministic Jest tests).
6
+ *
7
+ * Status values:
8
+ * - "validated" : >=80% pass rate in agentic browser tests
9
+ * - "warning" : 70-79% pass rate (needs investigation)
10
+ * - "failed" : <70% pass rate (potential bug)
11
+ * - "not_run" : No agentic tests executed yet
12
+ *
13
+ * Usage:
14
+ * const { updateAgenticTestStatus, getAgenticTestStatus } = require('./lib/browser-qa-status');
15
+ *
16
+ * // Update a story's agentic test status
17
+ * updateAgenticTestStatus(projectRoot, 'US-0050', {
18
+ * status: 'validated',
19
+ * pass_rate: 0.87,
20
+ * scenarios_run: 3,
21
+ * last_run: '2026-02-16T14:30:00Z',
22
+ * evidence_path: '.agileflow/ui-review/runs/2026-02-16_14-30-00/'
23
+ * });
24
+ *
25
+ * // Read a story's agentic test status
26
+ * const result = getAgenticTestStatus(projectRoot, 'US-0050');
27
+ */
28
+
29
+ const fs = require('fs');
30
+ const path = require('path');
31
+
32
+ const STATUS_FILE = 'docs/09-agents/status.json';
33
+
34
+ /**
35
+ * Read status.json safely
36
+ * @param {string} projectRoot - Project root directory
37
+ * @returns {object|null} Parsed status.json or null on error
38
+ */
39
+ function readStatusJson(projectRoot) {
40
+ const statusPath = path.join(projectRoot, STATUS_FILE);
41
+ if (!fs.existsSync(statusPath)) return null;
42
+ try {
43
+ return JSON.parse(fs.readFileSync(statusPath, 'utf-8'));
44
+ } catch {
45
+ return null;
46
+ }
47
+ }
48
+
49
+ /**
50
+ * Write status.json safely
51
+ * @param {string} projectRoot - Project root directory
52
+ * @param {object} data - Status data to write
53
+ * @returns {boolean} Success status
54
+ */
55
+ function writeStatusJson(projectRoot, data) {
56
+ const statusPath = path.join(projectRoot, STATUS_FILE);
57
+ try {
58
+ data.updated = new Date().toISOString();
59
+ fs.writeFileSync(statusPath, JSON.stringify(data, null, 2) + '\n');
60
+ return true;
61
+ } catch {
62
+ return false;
63
+ }
64
+ }
65
+
66
+ /**
67
+ * Update a story's agentic test status
68
+ * @param {string} projectRoot - Project root directory
69
+ * @param {string} storyId - Story ID (e.g., 'US-0050')
70
+ * @param {object} result - Agentic test result
71
+ * @param {'validated'|'warning'|'failed'|'not_run'} result.status - Overall status
72
+ * @param {number} result.pass_rate - Pass rate (0-1)
73
+ * @param {number} result.scenarios_run - Number of scenarios executed
74
+ * @param {string} result.last_run - ISO timestamp of last run
75
+ * @param {string} result.evidence_path - Path to evidence directory
76
+ * @returns {boolean} Success status
77
+ */
78
+ function updateAgenticTestStatus(projectRoot, storyId, result) {
79
+ const status = readStatusJson(projectRoot);
80
+ if (!status) return false;
81
+
82
+ if (!status.stories || !status.stories[storyId]) {
83
+ return false;
84
+ }
85
+
86
+ status.stories[storyId].agentic_test_status = result.status;
87
+ status.stories[storyId].agentic_test_details = {
88
+ pass_rate: result.pass_rate,
89
+ scenarios_run: result.scenarios_run,
90
+ last_run: result.last_run,
91
+ evidence_path: result.evidence_path,
92
+ };
93
+
94
+ return writeStatusJson(projectRoot, status);
95
+ }
96
+
97
+ /**
98
+ * Get a story's agentic test status
99
+ * @param {string} projectRoot - Project root directory
100
+ * @param {string} storyId - Story ID (e.g., 'US-0050')
101
+ * @returns {object|null} Agentic test status or null
102
+ */
103
+ function getAgenticTestStatus(projectRoot, storyId) {
104
+ const status = readStatusJson(projectRoot);
105
+ if (!status || !status.stories || !status.stories[storyId]) return null;
106
+
107
+ const story = status.stories[storyId];
108
+ return {
109
+ status: story.agentic_test_status || 'not_run',
110
+ details: story.agentic_test_details || null,
111
+ };
112
+ }
113
+
114
+ /**
115
+ * Update multiple stories' agentic test statuses from a run summary
116
+ * @param {string} projectRoot - Project root directory
117
+ * @param {Array} scenarioResults - Array of scenario results
118
+ * @param {string} scenarioResults[].story_id - Story ID
119
+ * @param {'validated'|'warning'|'failed'} scenarioResults[].status - Status
120
+ * @param {number} scenarioResults[].pass_rate - Pass rate
121
+ * @param {string} evidencePath - Path to run evidence directory
122
+ * @returns {{ updated: number, skipped: number }}
123
+ */
124
+ function updateBatchAgenticStatus(projectRoot, scenarioResults, evidencePath) {
125
+ const status = readStatusJson(projectRoot);
126
+ if (!status) return { updated: 0, skipped: 0 };
127
+
128
+ let updated = 0;
129
+ let skipped = 0;
130
+ const timestamp = new Date().toISOString();
131
+
132
+ for (const result of scenarioResults) {
133
+ if (!result.story_id) {
134
+ skipped++;
135
+ continue;
136
+ }
137
+
138
+ if (!status.stories || !status.stories[result.story_id]) {
139
+ skipped++;
140
+ continue;
141
+ }
142
+
143
+ status.stories[result.story_id].agentic_test_status = result.status;
144
+ status.stories[result.story_id].agentic_test_details = {
145
+ pass_rate: result.pass_rate,
146
+ scenarios_run: 1,
147
+ last_run: timestamp,
148
+ evidence_path: evidencePath,
149
+ };
150
+ updated++;
151
+ }
152
+
153
+ if (updated > 0) {
154
+ writeStatusJson(projectRoot, status);
155
+ }
156
+
157
+ return { updated, skipped };
158
+ }
159
+
160
+ /**
161
+ * Get summary of all agentic test statuses across stories
162
+ * @param {string} projectRoot - Project root directory
163
+ * @returns {{ validated: number, warning: number, failed: number, not_run: number, total: number }}
164
+ */
165
+ function getAgenticTestSummary(projectRoot) {
166
+ const status = readStatusJson(projectRoot);
167
+ const summary = { validated: 0, warning: 0, failed: 0, not_run: 0, total: 0 };
168
+
169
+ if (!status || !status.stories) return summary;
170
+
171
+ for (const story of Object.values(status.stories)) {
172
+ summary.total++;
173
+ const agenticStatus = story.agentic_test_status || 'not_run';
174
+ if (summary[agenticStatus] !== undefined) {
175
+ summary[agenticStatus]++;
176
+ } else {
177
+ summary.not_run++;
178
+ }
179
+ }
180
+
181
+ return summary;
182
+ }
183
+
184
+ module.exports = {
185
+ updateAgenticTestStatus,
186
+ getAgenticTestStatus,
187
+ updateBatchAgenticStatus,
188
+ getAgenticTestSummary,
189
+ readStatusJson,
190
+ writeStatusJson,
191
+ STATUS_FILE,
192
+ };
@@ -0,0 +1,280 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * command-prereqs.js
4
+ *
5
+ * Declarative prerequisite checker for AgileFlow commands.
6
+ * Validates that environment signals are met before command execution
7
+ * and provides actionable warnings with fix instructions.
8
+ *
9
+ * Uses:
10
+ * - resolveSignalPath() from feature-catalog.js for signal checking
11
+ * - mtime-based caching pattern from damage-control-utils.js
12
+ * - safeLoad() from yaml-utils.js for YAML parsing
13
+ *
14
+ * All functions fail-open: errors return empty/safe defaults.
15
+ */
16
+
17
+ 'use strict';
18
+
19
+ const fs = require('fs');
20
+ const path = require('path');
21
+
22
+ // Import resolveSignalPath from feature-catalog
23
+ const { resolveSignalPath } = require('./feature-catalog');
24
+
25
+ // Lazy-load yaml-utils (may not be available in all environments)
26
+ let safeLoad;
27
+ try {
28
+ safeLoad = require('../../lib/yaml-utils').safeLoad;
29
+ } catch {
30
+ // Fallback: no YAML parsing available
31
+ safeLoad = null;
32
+ }
33
+
34
+ // Inline colors (standalone - no dependency on colors.js for hook compat)
35
+ const c = {
36
+ coral: '\x1b[38;5;203m',
37
+ amber: '\x1b[38;5;215m',
38
+ mintGreen: '\x1b[38;5;158m',
39
+ skyBlue: '\x1b[38;5;117m',
40
+ dim: '\x1b[2m',
41
+ bold: '\x1b[1m',
42
+ reset: '\x1b[0m',
43
+ };
44
+
45
+ // Severity display config
46
+ const SEVERITY = {
47
+ critical: { icon: '\u2718', color: c.coral, label: 'CRITICAL' },
48
+ high: { icon: '!', color: c.amber, label: 'HIGH' },
49
+ medium: { icon: '\u25CB', color: c.dim, label: 'MEDIUM' },
50
+ };
51
+
52
+ // =============================================================================
53
+ // Config Cache (mtime-based invalidation, same pattern as damage-control-utils)
54
+ // =============================================================================
55
+
56
+ const _prereqCache = {
57
+ /** @type {string|null} */ filePath: null,
58
+ /** @type {number} */ mtime: 0,
59
+ /** @type {object|null} */ config: null,
60
+ };
61
+
62
+ /**
63
+ * Clear the prereq config cache (for testing or forced reload).
64
+ */
65
+ function clearPrereqCache() {
66
+ _prereqCache.filePath = null;
67
+ _prereqCache.mtime = 0;
68
+ _prereqCache.config = null;
69
+ }
70
+
71
+ // Config search paths (installed location, then source template)
72
+ const CONFIG_PATHS = [
73
+ '.agileflow/templates/command-prerequisites.yaml',
74
+ '.agileflow/templates/command-prerequisites.yml',
75
+ '.agileflow/config/command-prerequisites.yaml',
76
+ ];
77
+
78
+ /**
79
+ * Load prerequisite configuration from YAML with mtime-based caching.
80
+ * Returns safe defaults on any error (fail-open).
81
+ *
82
+ * @param {string} [projectRoot] - Project root directory (defaults to cwd)
83
+ * @returns {{ commands: Object, settings: Object }} Parsed config
84
+ */
85
+ function loadPrereqConfig(projectRoot) {
86
+ const root = projectRoot || process.cwd();
87
+ const defaultConfig = { commands: {}, settings: { fail_open: true, max_warnings: 5 } };
88
+
89
+ if (!safeLoad) {
90
+ return defaultConfig;
91
+ }
92
+
93
+ for (const configPath of CONFIG_PATHS) {
94
+ const fullPath = path.join(root, configPath);
95
+ if (!fs.existsSync(fullPath)) continue;
96
+
97
+ try {
98
+ const stat = fs.statSync(fullPath);
99
+ const mtime = stat.mtimeMs;
100
+
101
+ // Return cached if file hasn't changed
102
+ if (
103
+ _prereqCache.filePath === fullPath &&
104
+ _prereqCache.mtime === mtime &&
105
+ _prereqCache.config
106
+ ) {
107
+ return _prereqCache.config;
108
+ }
109
+
110
+ const content = fs.readFileSync(fullPath, 'utf8');
111
+ const parsed = safeLoad(content);
112
+
113
+ if (!parsed || typeof parsed !== 'object') {
114
+ continue;
115
+ }
116
+
117
+ const config = {
118
+ commands: parsed.commands || {},
119
+ settings: { ...defaultConfig.settings, ...(parsed.settings || {}) },
120
+ };
121
+
122
+ // Store in cache
123
+ _prereqCache.filePath = fullPath;
124
+ _prereqCache.mtime = mtime;
125
+ _prereqCache.config = config;
126
+
127
+ return config;
128
+ } catch {
129
+ // Continue to next path
130
+ }
131
+ }
132
+
133
+ return defaultConfig;
134
+ }
135
+
136
+ // =============================================================================
137
+ // Prerequisite Checking (pure function)
138
+ // =============================================================================
139
+
140
+ /**
141
+ * Check prerequisites for a specific command against current signals.
142
+ * Pure function - no I/O, no side effects.
143
+ *
144
+ * @param {string} commandName - Command to check (e.g. 'deploy', 'babysit')
145
+ * @param {Object} signals - Extracted signals from smart-detect
146
+ * @param {{ commands: Object, settings: Object }} config - Loaded prereq config
147
+ * @returns {{
148
+ * command: string,
149
+ * hasPrereqs: boolean,
150
+ * allMet: boolean,
151
+ * results: Array<{ signal: string, description: string, fix: string, severity: string, met: boolean }>,
152
+ * unmet: Array<{ signal: string, description: string, fix: string, severity: string }>,
153
+ * criticalUnmet: number,
154
+ * highUnmet: number
155
+ * }}
156
+ */
157
+ function checkCommandPrereqs(commandName, signals, config) {
158
+ const result = {
159
+ command: commandName,
160
+ hasPrereqs: false,
161
+ allMet: true,
162
+ results: [],
163
+ unmet: [],
164
+ criticalUnmet: 0,
165
+ highUnmet: 0,
166
+ };
167
+
168
+ if (!commandName || !config || !config.commands) {
169
+ return result;
170
+ }
171
+
172
+ const commandConfig = config.commands[commandName];
173
+ if (!commandConfig || !Array.isArray(commandConfig.prerequisites)) {
174
+ return result;
175
+ }
176
+
177
+ result.hasPrereqs = true;
178
+ const prereqs = commandConfig.prerequisites;
179
+
180
+ for (const prereq of prereqs) {
181
+ if (!prereq.signal) continue;
182
+
183
+ const value = resolveSignalPath(signals || {}, prereq.signal);
184
+ // Evaluate signal: empty arrays and empty strings are "unmet"
185
+ // (e.g. git.filesChanged=[] means no changes, which is unmet)
186
+ const met = Array.isArray(value) ? value.length > 0 : !!value;
187
+
188
+ const entry = {
189
+ signal: prereq.signal,
190
+ description: prereq.description || prereq.signal,
191
+ fix: prereq.fix || '',
192
+ severity: prereq.severity || 'medium',
193
+ met,
194
+ };
195
+
196
+ result.results.push(entry);
197
+
198
+ if (!met) {
199
+ result.allMet = false;
200
+ result.unmet.push(entry);
201
+
202
+ if (entry.severity === 'critical') {
203
+ result.criticalUnmet++;
204
+ } else if (entry.severity === 'high') {
205
+ result.highUnmet++;
206
+ }
207
+ }
208
+ }
209
+
210
+ return result;
211
+ }
212
+
213
+ // =============================================================================
214
+ // Warning Formatting
215
+ // =============================================================================
216
+
217
+ /**
218
+ * Format unmet prerequisite warnings with ANSI colors.
219
+ * Returns empty string if all prerequisites are met.
220
+ *
221
+ * @param {{ allMet: boolean, command: string, unmet: Array, criticalUnmet: number }} checkResult
222
+ * @param {{ max_warnings?: number }} [settings] - Display settings
223
+ * @returns {string} Formatted warning string (with trailing newline) or empty string
224
+ */
225
+ function formatPrereqWarnings(checkResult, settings) {
226
+ if (!checkResult || checkResult.allMet || checkResult.unmet.length === 0) {
227
+ return '';
228
+ }
229
+
230
+ const maxWarnings = (settings && settings.max_warnings) || 5;
231
+ const { command, unmet, criticalUnmet } = checkResult;
232
+
233
+ const lines = [];
234
+ const headerColor = criticalUnmet > 0 ? c.coral : c.amber;
235
+ const headerIcon = criticalUnmet > 0 ? '\u26A0\uFE0F' : '\u2139\uFE0F';
236
+
237
+ lines.push('');
238
+ lines.push(
239
+ `${headerColor}${c.bold}\u2501\u2501\u2501 ${headerIcon} Command Prerequisites: /${command} \u2501\u2501\u2501${c.reset}`
240
+ );
241
+
242
+ if (criticalUnmet > 0) {
243
+ lines.push(
244
+ `${c.coral}${criticalUnmet} critical prerequisite(s) not met - command may fail${c.reset}`
245
+ );
246
+ } else {
247
+ lines.push(
248
+ `${c.amber}${unmet.length} prerequisite(s) not met - results may be suboptimal${c.reset}`
249
+ );
250
+ }
251
+
252
+ lines.push('');
253
+
254
+ const displayed = unmet.slice(0, maxWarnings);
255
+ for (const prereq of displayed) {
256
+ const sev = SEVERITY[prereq.severity] || SEVERITY.medium;
257
+ lines.push(` ${sev.color}${sev.icon} [${sev.label}]${c.reset} ${prereq.description}`);
258
+ if (prereq.fix) {
259
+ lines.push(` ${c.dim}\u2192 Fix: ${prereq.fix}${c.reset}`);
260
+ }
261
+ }
262
+
263
+ if (unmet.length > maxWarnings) {
264
+ lines.push(` ${c.dim}... and ${unmet.length - maxWarnings} more${c.reset}`);
265
+ }
266
+
267
+ lines.push('');
268
+
269
+ return lines.join('\n');
270
+ }
271
+
272
+ module.exports = {
273
+ loadPrereqConfig,
274
+ checkCommandPrereqs,
275
+ formatPrereqWarnings,
276
+ clearPrereqCache,
277
+ // Exported for testing
278
+ CONFIG_PATHS,
279
+ SEVERITY,
280
+ };
@@ -83,6 +83,13 @@ function detectConfig(version) {
83
83
  level: null,
84
84
  patternCount: 0,
85
85
  },
86
+ noaiattribution: {
87
+ enabled: false,
88
+ valid: true,
89
+ issues: [],
90
+ version: null,
91
+ outdated: false,
92
+ },
86
93
  askuserquestion: {
87
94
  enabled: false,
88
95
  valid: true,
@@ -98,6 +105,22 @@ function detectConfig(version) {
98
105
  version: null,
99
106
  outdated: false,
100
107
  },
108
+ browserqa: {
109
+ enabled: false,
110
+ valid: true,
111
+ issues: [],
112
+ version: null,
113
+ outdated: false,
114
+ playwright_detected: false,
115
+ },
116
+ contextverbosity: {
117
+ enabled: false,
118
+ valid: true,
119
+ issues: [],
120
+ version: null,
121
+ outdated: false,
122
+ mode: 'full',
123
+ },
101
124
  },
102
125
  metadata: { exists: false, version: null },
103
126
  currentVersion: version,
@@ -225,7 +248,7 @@ function detectStopHooks(hook, status) {
225
248
  }
226
249
 
227
250
  /**
228
- * Detect PreToolUse hooks (damage control)
251
+ * Detect PreToolUse hooks (damage control, no AI attribution)
229
252
  */
230
253
  function detectPreToolUseHooks(hooks, status) {
231
254
  if (!Array.isArray(hooks) || hooks.length === 0) return;
@@ -248,6 +271,17 @@ function detectPreToolUseHooks(hooks, status) {
248
271
  status.features.damagecontrol.issues.push(`Only ${hookCount}/3 hooks configured`);
249
272
  }
250
273
  }
274
+
275
+ // Detect no AI attribution hook
276
+ const hasNoAiHook = hooks.some(
277
+ h =>
278
+ h.matcher === 'Bash' &&
279
+ Array.isArray(h.hooks) &&
280
+ h.hooks.some(hk => hk.command?.includes('strip-ai-attribution'))
281
+ );
282
+ if (hasNoAiHook) {
283
+ status.features.noaiattribution.enabled = true;
284
+ }
251
285
  }
252
286
 
253
287
  /**
@@ -303,9 +337,31 @@ function detectMetadata(status, version) {
303
337
  status.features.tmuxautospawn.enabled = true; // Default enabled
304
338
  }
305
339
 
340
+ // No AI attribution metadata
341
+ if (meta.features?.noaiattribution?.enabled) {
342
+ status.features.noaiattribution.enabled = true;
343
+ }
344
+
345
+ // Browser QA metadata
346
+ if (meta.features?.browserqa?.enabled) {
347
+ status.features.browserqa.enabled = true;
348
+ status.features.browserqa.playwright_detected =
349
+ meta.features.browserqa.playwright_detected || false;
350
+ }
351
+
352
+ // Context verbosity metadata
353
+ if (meta.features?.contextVerbosity?.enabled) {
354
+ status.features.contextverbosity.enabled = true;
355
+ status.features.contextverbosity.mode = meta.features.contextVerbosity.mode || 'full';
356
+ }
357
+
306
358
  // Read feature versions and check if outdated (content-based)
307
359
  if (meta.features) {
308
- const featureKeyMap = { askUserQuestion: 'askuserquestion', tmuxAutoSpawn: 'tmuxautospawn' };
360
+ const featureKeyMap = {
361
+ askUserQuestion: 'askuserquestion',
362
+ tmuxAutoSpawn: 'tmuxautospawn',
363
+ contextVerbosity: 'contextverbosity',
364
+ };
309
365
  const packageScriptDir = findPackageScriptDir();
310
366
 
311
367
  Object.entries(meta.features).forEach(([feature, data]) => {
@@ -434,6 +490,14 @@ function printStatus(status) {
434
490
  log(` Damage Control: disabled`, c.dim);
435
491
  }
436
492
 
493
+ // No AI Attribution
494
+ const naa = status.features.noaiattribution;
495
+ if (naa.enabled) {
496
+ log(` No AI Attribution: enabled`, c.green);
497
+ } else {
498
+ log(` No AI Attribution: disabled`, c.dim);
499
+ }
500
+
437
501
  // AskUserQuestion
438
502
  const auq = status.features.askuserquestion;
439
503
  if (auq.enabled) {
@@ -452,6 +516,32 @@ function printStatus(status) {
452
516
  log(` Tmux Auto-Spawn: disabled`, c.dim);
453
517
  }
454
518
 
519
+ // Browser QA
520
+ const bqa = status.features.browserqa;
521
+ if (bqa) {
522
+ if (bqa.enabled) {
523
+ let bqaText = 'enabled';
524
+ if (bqa.playwright_detected) bqaText += ' (Playwright detected)';
525
+ else bqaText += ' (Playwright not found)';
526
+ log(
527
+ ` ${bqa.playwright_detected ? '' : ''} Browser QA: ${bqaText}`,
528
+ bqa.playwright_detected ? c.green : c.yellow
529
+ );
530
+ } else {
531
+ log(` Browser QA: disabled`, c.dim);
532
+ }
533
+ }
534
+
535
+ // Context Verbosity
536
+ const cv = status.features.contextverbosity;
537
+ if (cv) {
538
+ if (cv.enabled) {
539
+ log(` Context Verbosity: ${cv.mode}`, c.green);
540
+ } else {
541
+ log(` Context Verbosity: full (default)`, c.dim);
542
+ }
543
+ }
544
+
455
545
  // Metadata version
456
546
  if (status.metadata.exists) {
457
547
  log(`\nMetadata: v${status.metadata.version}`, c.dim);