@ryuenn3123/agentic-senior-core 6.16.3 → 6.16.6

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 (84) hide show
  1. package/.agents/plugins/agentic-senior-core/.codex-plugin/plugin.json +1 -1
  2. package/.agents/plugins/agentic-senior-core/commands/asc-pentest.md +2 -0
  3. package/.agents/plugins/agentic-senior-core/commands/asc-pentest.toml +3 -0
  4. package/.agents/plugins/agentic-senior-core/commands/asc-test.md +1 -1
  5. package/.agents/plugins/agentic-senior-core/commands/asc-ui.md +1 -1
  6. package/.agents/plugins/agentic-senior-core/hooks/dedup-gate.js +137 -128
  7. package/.agents/plugins/agentic-senior-core/hooks/hooks.json +97 -0
  8. package/.agents/plugins/agentic-senior-core/hooks/ladder-pulse.js +53 -36
  9. package/.agents/plugins/agentic-senior-core/hooks/lib/known-ui-slop-patterns.json +5 -0
  10. package/.agents/plugins/agentic-senior-core/hooks/path-util.cjs +118 -4
  11. package/.agents/plugins/agentic-senior-core/hooks/post-edit-enforce.js +98 -81
  12. package/.agents/plugins/agentic-senior-core/hooks/pre-tool-dependency-gate.js +168 -191
  13. package/.agents/plugins/agentic-senior-core/hooks/session-pulse.js +48 -35
  14. package/.agents/plugins/agentic-senior-core/hooks/session-start.js +62 -11
  15. package/.agents/plugins/agentic-senior-core/hooks.json +4 -94
  16. package/.agents/plugins/agentic-senior-core/plugin.json +5 -13
  17. package/.agents/plugins/agentic-senior-core/rules/agentic-senior-core.md +3 -1
  18. package/.agents/plugins/agentic-senior-core/skills/asc/SKILL.md +5 -2
  19. package/.agents/plugins/agentic-senior-core/skills/asc/references/writing-skills.md +1 -1
  20. package/.agents/plugins/agentic-senior-core/skills/asc-develop/SKILL.md +2 -2
  21. package/.agents/plugins/agentic-senior-core/skills/asc-pentest/SKILL.md +97 -0
  22. package/.agents/plugins/agentic-senior-core/skills/asc-pentest/references/api-security-idor.md +105 -0
  23. package/.agents/plugins/agentic-senior-core/skills/asc-pentest/references/dast-baseline.md +78 -0
  24. package/.agents/plugins/agentic-senior-core/skills/asc-pentest/references/fuzzing-dsl.md +66 -0
  25. package/.agents/plugins/agentic-senior-core/skills/asc-pentest/references/pentest-task-tree.md +70 -0
  26. package/.agents/plugins/agentic-senior-core/skills/asc-review/SKILL.md +1 -1
  27. package/.agents/plugins/agentic-senior-core/skills/asc-test/SKILL.md +10 -3
  28. package/.agents/plugins/agentic-senior-core/skills/asc-test/references/mutation-testing.md +55 -0
  29. package/.agents/plugins/agentic-senior-core/skills/asc-test/references/performance-budgets.md +67 -0
  30. package/.agents/plugins/agentic-senior-core/skills/asc-test/references/property-testing.md +72 -0
  31. package/.agents/plugins/agentic-senior-core/skills/asc-test/references/regression-proof.md +2 -2
  32. package/.agents/plugins/agentic-senior-core/skills/asc-test/references/tdd-cycle.md +2 -2
  33. package/.agents/plugins/agentic-senior-core/skills/asc-test/references/uat.md +1 -1
  34. package/.agents/plugins/agentic-senior-core/skills/asc-ui/SKILL.md +31 -34
  35. package/.agents/plugins/agentic-senior-core/skills/asc-ui/references/accessibility.md +2 -2
  36. package/.agents/plugins/agentic-senior-core/skills/asc-ui/references/copy-i18n.md +24 -8
  37. package/.agents/plugins/agentic-senior-core/skills/asc-ui/references/craft/color-surfaces.md +32 -0
  38. package/.agents/plugins/agentic-senior-core/skills/asc-ui/references/craft/composition-patterns.md +299 -0
  39. package/.agents/plugins/agentic-senior-core/skills/asc-ui/references/craft/themes-catalog.md +199 -0
  40. package/.agents/plugins/agentic-senior-core/skills/asc-ui/references/divergence.md +65 -24
  41. package/.agents/plugins/agentic-senior-core/skills/asc-ui/references/modes/experience.md +4 -0
  42. package/.agents/plugins/agentic-senior-core/skills/asc-ui/references/modes/genre-calibration.md +93 -0
  43. package/.agents/plugins/agentic-senior-core/skills/asc-ui/references/modes/operate.md +31 -1
  44. package/.agents/plugins/agentic-senior-core/skills/asc-ui/references/modes/persuade.md +33 -23
  45. package/.agents/plugins/agentic-senior-core/skills/asc-ui/references/modes/read.md +4 -0
  46. package/.agents/plugins/agentic-senior-core/skills/asc-ui/references/redesign-audit.md +5 -10
  47. package/.agents/plugins/agentic-senior-core/skills/asc-ui/references/states-feedback.md +2 -2
  48. package/.agents/plugins/agentic-senior-core/skills/asc-ui/references/visuals/real-attio-dom.json +5 -0
  49. package/.agents/plugins/agentic-senior-core/skills/asc-ui/references/visuals/real-attio.png +0 -0
  50. package/.agents/plugins/agentic-senior-core/skills/asc-ui/references/visuals/real-cal-dom.json +5 -0
  51. package/.agents/plugins/agentic-senior-core/skills/asc-ui/references/visuals/real-cal.png +0 -0
  52. package/.agents/plugins/agentic-senior-core/skills/asc-ui/references/visuals/real-clerk-dom.json +5 -0
  53. package/.agents/plugins/agentic-senior-core/skills/asc-ui/references/visuals/real-clerk.png +0 -0
  54. package/.agents/plugins/agentic-senior-core/skills/asc-ui/references/visuals/real-dub-dom.json +5 -0
  55. package/.agents/plugins/agentic-senior-core/skills/asc-ui/references/visuals/real-dub.png +0 -0
  56. package/.agents/plugins/agentic-senior-core/skills/asc-ui/references/visuals/real-linear-dom.json +5 -0
  57. package/.agents/plugins/agentic-senior-core/skills/asc-ui/references/visuals/real-linear.png +0 -0
  58. package/.agents/plugins/agentic-senior-core/skills/asc-ui/references/visuals/real-marketplace-dom.json +5 -0
  59. package/.agents/plugins/agentic-senior-core/skills/asc-ui/references/visuals/real-marketplace.png +0 -0
  60. package/.agents/plugins/agentic-senior-core/skills/asc-ui/references/visuals/real-mintlify-dom.json +5 -0
  61. package/.agents/plugins/agentic-senior-core/skills/asc-ui/references/visuals/real-mintlify.png +0 -0
  62. package/.agents/plugins/agentic-senior-core/skills/asc-ui/references/visuals/real-raycast-dom.json +5 -0
  63. package/.agents/plugins/agentic-senior-core/skills/asc-ui/references/visuals/real-raycast.png +0 -0
  64. package/.agents/plugins/agentic-senior-core/skills/asc-ui/references/visuals/real-resend-dom.json +5 -0
  65. package/.agents/plugins/agentic-senior-core/skills/asc-ui/references/visuals/real-resend.png +0 -0
  66. package/.agents/plugins/agentic-senior-core/skills/asc-ui/references/visuals/real-stripe-dom.json +5 -0
  67. package/.agents/plugins/agentic-senior-core/skills/asc-ui/references/visuals/real-stripe.png +0 -0
  68. package/.agents/plugins/agentic-senior-core/skills/asc-ui/references/visuals/real-supabase-dom.json +5 -0
  69. package/.agents/plugins/agentic-senior-core/skills/asc-ui/references/visuals/real-supabase.png +0 -0
  70. package/.agents/plugins/agentic-senior-core/skills/asc-ui/references/visuals/real-tldraw-dom.json +5 -0
  71. package/.agents/plugins/agentic-senior-core/skills/asc-ui/references/visuals/real-tldraw.png +0 -0
  72. package/.agents/plugins/agentic-senior-core/skills/asc-ui/references/visuals/real-vercel-dom.json +5 -0
  73. package/.agents/plugins/agentic-senior-core/skills/asc-ui/references/visuals/real-vercel.png +0 -0
  74. package/bin/agentic-senior-core.js +19 -4
  75. package/gemini-extension.json +2 -2
  76. package/lib/cli/ascx/runtime.mjs +5 -27
  77. package/lib/cli/ascx/windows-spawn.mjs +348 -0
  78. package/lib/cli/commands/bootstrap.mjs +56 -0
  79. package/lib/cli/commands/dedup.mjs +500 -0
  80. package/lib/cli/commands/git-hook-generator.mjs +3 -2
  81. package/lib/cli/commands/global.mjs +109 -53
  82. package/lib/cli/commands/preview.mjs +34 -20
  83. package/package.json +1 -1
  84. package/plugin.yaml +3 -1
@@ -6,9 +6,7 @@ const os = require('os');
6
6
  * Computes a stable path for the workflow gate file based on the project path.
7
7
  * Normalizes the path (resolves absolute, lowercase, forward slashes, no trailing slash)
8
8
  * to ensure identical hashes regardless of OS path separator or drive letter casing.
9
- * Note: .toLowerCase() deliberately treats Linux/Mac paths case-insensitively,
10
- * accepting the negligible risk of collisions for the benefit of Windows stability.
11
- *
9
+ *
12
10
  * @param {string} projectPath - The root path of the project.
13
11
  * @returns {string} Absolute path to the global workflow gate JSON file.
14
12
  */
@@ -18,6 +16,122 @@ function getWorkflowGatePath(projectPath) {
18
16
  return path.join(os.homedir(), '.config', 'agentic-senior-core', 'gates', `${projectHash}.json`);
19
17
  }
20
18
 
19
+ /**
20
+ * Detects whether a hook stdin payload originates from Antigravity (App, CLI, or IDE).
21
+ */
22
+ function isAntigravityPayload(data) {
23
+ if (!data || typeof data !== 'object') return false;
24
+ return Boolean(
25
+ data.toolCall ||
26
+ Array.isArray(data.workspacePaths) ||
27
+ typeof data.transcriptPath === 'string' ||
28
+ typeof data.invocationNum === 'number' ||
29
+ typeof data.executionNum === 'number'
30
+ );
31
+ }
32
+
33
+ /**
34
+ * Resolves the active user workspace directory from hook stdin JSON.
35
+ * Antigravity executes hooks with cwd set to the directory containing hooks.json,
36
+ * so workspacePaths[0] or toolCall.args.Cwd must take precedence over process.cwd().
37
+ */
38
+ function resolveWorkspaceRoot(data) {
39
+ if (data && typeof data === 'object') {
40
+ if (data.toolCall && data.toolCall.args && typeof data.toolCall.args.Cwd === 'string' && data.toolCall.args.Cwd.trim()) {
41
+ return path.resolve(data.toolCall.args.Cwd);
42
+ }
43
+ if (Array.isArray(data.workspacePaths) && data.workspacePaths.length > 0 && typeof data.workspacePaths[0] === 'string' && data.workspacePaths[0].trim()) {
44
+ return path.resolve(data.workspacePaths[0]);
45
+ }
46
+ if (typeof data.cwd === 'string' && data.cwd.trim()) {
47
+ return path.resolve(data.cwd);
48
+ }
49
+ }
50
+ return process.cwd();
51
+ }
52
+
53
+ /**
54
+ * Normalizes file edit/write tool calls across Antigravity (edit_file, write_file,
55
+ * replace_file_content, multi_replace_file_content, write_to_file) and Claude/Codex/Copilot (Edit, Write).
56
+ */
57
+ function normalizeEditToolCall(toolName, rawArgs) {
58
+ const args = rawArgs && typeof rawArgs === 'object' ? rawArgs : {};
59
+ const name = String(toolName || '');
60
+
61
+ if (name === 'edit_file') {
62
+ return {
63
+ canonicalTool: 'Edit',
64
+ filePath: args.Path || args.file_path || '',
65
+ newContent: args.Replacement || '',
66
+ oldContent: args.Target || '',
67
+ };
68
+ }
69
+
70
+ if (name === 'replace_file_content') {
71
+ return {
72
+ canonicalTool: 'Edit',
73
+ filePath: args.TargetFile || args.file_path || '',
74
+ newContent: args.ReplacementContent || '',
75
+ oldContent: args.TargetContent || '',
76
+ };
77
+ }
78
+
79
+ if (name === 'multi_replace_file_content') {
80
+ const chunks = Array.isArray(args.ReplacementChunks) ? args.ReplacementChunks : [];
81
+ return {
82
+ canonicalTool: 'Edit',
83
+ filePath: args.TargetFile || args.file_path || '',
84
+ newContent: chunks.length > 0
85
+ ? chunks.map(function (c) { return (c && c.ReplacementContent) || ''; }).join('\n')
86
+ : (args.ReplacementContent || ''),
87
+ oldContent: chunks.length > 0
88
+ ? chunks.map(function (c) { return (c && c.TargetContent) || ''; }).join('\n')
89
+ : (args.TargetContent || ''),
90
+ };
91
+ }
92
+
93
+ if (name === 'Edit') {
94
+ return {
95
+ canonicalTool: 'Edit',
96
+ filePath: args.file_path || args.path || args.TargetFile || args.Path || '',
97
+ newContent: args.new_string || args.ReplacementContent || args.Replacement || args.content || '',
98
+ oldContent: args.old_string || args.TargetContent || args.Target || '',
99
+ };
100
+ }
101
+
102
+ if (name === 'write_file') {
103
+ return {
104
+ canonicalTool: 'Write',
105
+ filePath: args.Path || args.file_path || '',
106
+ newContent: args.Content || args.content || '',
107
+ oldContent: '',
108
+ };
109
+ }
110
+
111
+ if (name === 'write_to_file') {
112
+ return {
113
+ canonicalTool: 'Write',
114
+ filePath: args.TargetFile || args.file_path || '',
115
+ newContent: args.CodeContent || args.content || '',
116
+ oldContent: '',
117
+ };
118
+ }
119
+
120
+ if (name === 'Write') {
121
+ return {
122
+ canonicalTool: 'Write',
123
+ filePath: args.file_path || args.path || args.TargetFile || args.Path || '',
124
+ newContent: args.content || args.CodeContent || args.Content || '',
125
+ oldContent: '',
126
+ };
127
+ }
128
+
129
+ return null;
130
+ }
131
+
21
132
  module.exports = {
22
- getWorkflowGatePath
133
+ getWorkflowGatePath,
134
+ isAntigravityPayload,
135
+ resolveWorkspaceRoot,
136
+ normalizeEditToolCall,
23
137
  };
@@ -55,6 +55,13 @@ const {
55
55
  SESSION_DRIFT_THRESHOLD,
56
56
  LADDER_PULSE_INTERVAL,
57
57
  } = require('./constants.cjs');
58
+ const {
59
+ isAntigravityPayload,
60
+ resolveWorkspaceRoot,
61
+ normalizeEditToolCall,
62
+ } = require('./path-util.cjs');
63
+
64
+ let sourceEditCount = 0;
58
65
 
59
66
  // Module-level counter for Claude Code path (resets per process spawn)
60
67
  function logHook(event, msg) {
@@ -90,49 +97,63 @@ const stdinTimeout = setTimeout(() => {
90
97
  process.exit(0);
91
98
  }, 3000);
92
99
 
100
+ let handled = false;
93
101
  let inputBuffer = '';
102
+
103
+ function dispatchInput(data) {
104
+ if (handled) return;
105
+ handled = true;
106
+ clearTimeout(stdinTimeout);
107
+
108
+ if (isAntigravityPayload(data)) {
109
+ if (data.stepIdx !== undefined) {
110
+ // Antigravity PostToolUse strictly expects {} on stdout
111
+ process.stdout.write('{}\n');
112
+ process.exit(0);
113
+ return;
114
+ }
115
+ handleAntigravityPostInvocation(data);
116
+ return;
117
+ }
118
+
119
+ const workspaceRoot = resolveWorkspaceRoot(data);
120
+ const rawTool = data.tool_name || data.toolName || '';
121
+ const rawInput = data.tool_input || data.toolInput || {};
122
+ const norm = normalizeEditToolCall(rawTool, rawInput);
123
+
124
+ if (norm) {
125
+ const mappedInput = norm.canonicalTool === 'Edit'
126
+ ? { file_path: norm.filePath, new_string: norm.newContent, old_string: norm.oldContent }
127
+ : { file_path: norm.filePath, content: norm.newContent };
128
+ processSingleEdit(norm.canonicalTool, mappedInput, function (nudge) {
129
+ emitClaude(nudge);
130
+ }, workspaceRoot, false);
131
+ }
132
+ process.exit(0);
133
+ }
134
+
94
135
  process.stdin.setEncoding('utf8');
95
136
  process.stdin.on('data', chunk => {
96
137
  inputBuffer += chunk;
97
138
  logHook('DataChunk', `len=${chunk.length} preview=${chunk.slice(0, 100).replace(/[\r\n]+/g, ' ')}`);
98
139
  try {
99
140
  const data = JSON.parse(inputBuffer);
100
- clearTimeout(stdinTimeout);
101
141
  logHook('StdinData', 'keys=' + Object.keys(data).join(','));
102
-
103
- if (data.transcriptPath) {
104
- handleAntigravityPostInvocation(data);
105
- return;
106
- }
107
-
108
- const toolName = data.tool_name || '';
109
- const toolInput = data.tool_input || {};
110
- processSingleEdit(toolName, toolInput, function (nudge) {
111
- emitClaude(nudge);
112
- });
113
- process.exit(0);
142
+ dispatchInput(data);
114
143
  } catch (e) {
115
144
  // incomplete json, wait for more chunks
116
145
  }
117
146
  });
118
147
 
119
148
  process.stdin.on('end', () => {
149
+ if (handled) return;
120
150
  clearTimeout(stdinTimeout);
121
151
  logHook('StdinEnd', `bufferLen=${inputBuffer.length}`);
122
152
  if (inputBuffer.trim()) {
123
153
  try {
124
154
  const data = JSON.parse(inputBuffer);
125
155
  logHook('ParsedInputOnEnd', 'keys=' + Object.keys(data).join(','));
126
- if (data.transcriptPath) {
127
- handleAntigravityPostInvocation(data);
128
- return;
129
- }
130
- const toolName = data.tool_name || '';
131
- const toolInput = data.tool_input || {};
132
- processSingleEdit(toolName, toolInput, function (nudge) {
133
- emitClaude(nudge);
134
- });
135
- process.exit(0);
156
+ dispatchInput(data);
136
157
  } catch (e) {
137
158
  logHook('JsonParseErrorOnEnd', e.message);
138
159
  process.exit(0);
@@ -144,84 +165,78 @@ process.stdin.on('end', () => {
144
165
 
145
166
  function handleAntigravityPostInvocation(data) {
146
167
  try {
147
- if (!fs.existsSync(data.transcriptPath)) return;
168
+ const workspaceRoot = resolveWorkspaceRoot(data);
169
+ if (!data.transcriptPath || !fs.existsSync(data.transcriptPath)) {
170
+ process.stdout.write('{}\n');
171
+ process.exit(0);
172
+ return;
173
+ }
174
+
148
175
  const lines = fs.readFileSync(data.transcriptPath, 'utf8').split('\n').filter(Boolean);
149
176
  const findings = [];
177
+ const minStep = typeof data.initialNumSteps === 'number' ? data.initialNumSteps : 0;
150
178
 
151
- // Session drift check: count source file edits since initialNumSteps
152
179
  let sourceEditsSinceStart = 0;
153
180
  for (let i = 0; i < lines.length; i++) {
154
- const step = JSON.parse(lines[i]);
155
- if (step.step_index >= data.initialNumSteps && step.type === 'PLANNER_RESPONSE' && step.tool_calls) {
181
+ let step;
182
+ try {
183
+ step = JSON.parse(lines[i]);
184
+ } catch (_) {
185
+ continue;
186
+ }
187
+ if (step.step_index >= minStep && step.type === 'PLANNER_RESPONSE' && Array.isArray(step.tool_calls)) {
156
188
  for (let j = 0; j < step.tool_calls.length; j++) {
157
189
  const tc = step.tool_calls[j];
158
- let toolName = '';
159
- let toolInput = {};
160
-
161
- if (tc.name === 'replace_file_content' || tc.name === 'multi_replace_file_content') {
162
- toolName = 'Edit';
163
- toolInput = {
164
- file_path: tc.args.TargetFile || '',
165
- new_string: tc.args.ReplacementContent || '',
166
- old_string: tc.args.TargetContent || ''
167
- };
168
- } else if (tc.name === 'write_to_file') {
169
- toolName = 'Write';
170
- toolInput = {
171
- file_path: tc.args.TargetFile || '',
172
- content: tc.args.CodeContent || ''
173
- };
174
- }
190
+ const norm = tc ? normalizeEditToolCall(tc.name, tc.args) : null;
191
+ if (!norm || !norm.filePath) continue;
175
192
 
176
- if (toolName) {
177
- const fp = toolInput.file_path || '';
178
- const ext = path.extname(fp).slice(1);
179
- if (SOURCE_EXTENSIONS.has(ext)) sourceEditsSinceStart++;
193
+ const ext = path.extname(norm.filePath).slice(1);
194
+ if (SOURCE_EXTENSIONS.has(ext)) sourceEditsSinceStart++;
180
195
 
181
- processSingleEdit(toolName, toolInput, function (nudge) {
182
- findings.push(nudge);
183
- }, true);
184
- }
196
+ const mappedInput = norm.canonicalTool === 'Edit'
197
+ ? { file_path: norm.filePath, new_string: norm.newContent, old_string: norm.oldContent }
198
+ : { file_path: norm.filePath, content: norm.newContent };
199
+
200
+ processSingleEdit(norm.canonicalTool, mappedInput, function (nudge) {
201
+ findings.push(nudge);
202
+ }, workspaceRoot, true);
185
203
  }
186
204
  }
187
205
  }
188
206
 
189
- // Inject drift nudge if 4+ source files modified this invocation
190
207
  if (sourceEditsSinceStart >= SESSION_DRIFT_THRESHOLD) {
191
208
  findings.push('[ASC Session Drift] ' + sourceEditsSinceStart + ' source file edits this invocation. '
192
209
  + 'Re-read the decision ladder before continuing: (1) Does this need to be built? '
193
210
  + '(2) Does the codebase already have this? (3) Stdlib/native? (4) Existing dependency?');
194
211
  }
195
212
 
196
- const isPostToolUse = data.stepIdx !== undefined;
197
- logHook('AntigravityResult', `sourceEdits=${sourceEditsSinceStart}, findings=${findings.length}, isPostToolUse=${isPostToolUse}`);
213
+ logHook('AntigravityResult', `sourceEdits=${sourceEditsSinceStart}, findings=${findings.length}`);
198
214
 
199
- if (isPostToolUse) {
200
- process.stdout.write(JSON.stringify({}) + '\n');
201
- } else if (findings.length > 0) {
215
+ if (findings.length > 0) {
202
216
  const injectSteps = findings.map(function (f) { return { ephemeralMessage: f }; });
203
217
  process.stdout.write(JSON.stringify({ injectSteps: injectSteps }) + '\n');
204
218
  } else {
205
- process.stdout.write(JSON.stringify({}) + '\n');
219
+ process.stdout.write('{}\n');
206
220
  }
207
221
  } catch (e) {
208
222
  logHook('AntigravityError', e.message);
209
- process.stdout.write(JSON.stringify({}) + '\n');
223
+ process.stdout.write('{}\n');
210
224
  }
211
225
  process.exit(0);
212
226
  }
213
227
 
214
- function processSingleEdit(toolName, toolInput, emitFn, skipArray) {
215
- const filePath = toolInput.file_path || toolInput.TargetFile || toolInput.filePath || '';
216
- if (!filePath) return;
228
+ function processSingleEdit(toolName, toolInput, emitFn, workspaceRoot, skipSessionCounter) {
229
+ const rawFilePath = toolInput.file_path || toolInput.TargetFile || toolInput.Path || toolInput.filePath || '';
230
+ if (!rawFilePath) return;
231
+ const root = workspaceRoot || process.cwd();
232
+ const filePath = path.isAbsolute(rawFilePath) ? rawFilePath : path.resolve(root, rawFilePath);
217
233
  const findings = [];
218
234
 
219
- const isEdit = toolName === 'Edit' || toolName === 'replace_file_content' || toolName === 'multi_replace_file_content';
220
- const isWrite = toolName === 'Write' || toolName === 'write_to_file';
235
+ const isEdit = toolName === 'Edit' || toolName === 'edit_file' || toolName === 'replace_file_content' || toolName === 'multi_replace_file_content';
236
+ const isWrite = toolName === 'Write' || toolName === 'write_file' || toolName === 'write_to_file';
221
237
 
222
- // Increment per-process counter for Claude Code drift detection
223
238
  const ext = path.extname(filePath).slice(1);
224
- if (SOURCE_EXTENSIONS.has(ext)) {
239
+ if (!skipSessionCounter && SOURCE_EXTENSIONS.has(ext)) {
225
240
  sourceEditCount++;
226
241
  if (sourceEditCount === SESSION_DRIFT_THRESHOLD) {
227
242
  findings.push('[ASC Session Drift] ' + SESSION_DRIFT_THRESHOLD + '+ source file edits this session. '
@@ -252,13 +267,13 @@ function processSingleEdit(toolName, toolInput, emitFn, skipArray) {
252
267
  checkUiSlopPatterns(toolName, toolInput, filePath, findings);
253
268
  }
254
269
 
255
- checkUniversalLinter(filePath, ext, findings);
270
+ checkUniversalLinter(filePath, ext, findings, root);
256
271
 
257
272
  checkLivingDocNudge(filePath, findings);
258
- checkTestTampering(filePath, findings);
273
+ checkTestTampering(filePath, findings, root);
259
274
 
260
275
  if (ext !== 'md') {
261
- checkWorkflowGate(toolName, filePath, ext, findings);
276
+ checkWorkflowGate(toolName, filePath, ext, findings, root);
262
277
  }
263
278
 
264
279
  logHook('ProcessSingleEdit', 'tool=' + toolName + ', file=' + filePath + ', findings=' + findings.length);
@@ -439,9 +454,9 @@ function checkStubPatterns(toolName, toolInput, filePath, findings) {
439
454
  }
440
455
  }
441
456
 
442
- function checkUniversalLinter(filePath, ext, findings) {
457
+ function checkUniversalLinter(filePath, ext, findings, workspaceRoot) {
443
458
  var execSync = require('child_process').execSync;
444
- var cwd = process.cwd();
459
+ var cwd = workspaceRoot || process.cwd();
445
460
 
446
461
  if (ext === 'js' || ext === 'ts' || ext === 'jsx' || ext === 'tsx' || ext === 'mjs' || ext === 'cjs') {
447
462
  try {
@@ -519,14 +534,15 @@ function checkLivingDocNudge(filePath, findings) {
519
534
  }
520
535
  }
521
536
 
522
- function checkTestTampering(filePath, findings) {
537
+ function checkTestTampering(filePath, findings, workspaceRoot) {
523
538
  var isTest = /(?:\.test\.|\.spec\.|_test\.|__tests__[\/\\]|tests?[\/\\]).*\.(?:js|ts|jsx|tsx|py|go|rs|rb)$/i.test(filePath);
524
539
  if (!isTest) return;
525
540
 
526
541
  try {
542
+ var root = workspaceRoot || process.cwd();
527
543
  var pathUtil = require('./path-util.cjs');
528
- var localGate = path.join(process.cwd(), 'workflow-gate.json');
529
- var gatePath = fs.existsSync(localGate) ? localGate : pathUtil.getWorkflowGatePath(process.cwd());
544
+ var localGate = path.join(root, 'workflow-gate.json');
545
+ var gatePath = fs.existsSync(localGate) ? localGate : pathUtil.getWorkflowGatePath(root);
530
546
  if (!fs.existsSync(gatePath)) return;
531
547
 
532
548
  var gate = JSON.parse(fs.readFileSync(gatePath, 'utf8'));
@@ -539,11 +555,12 @@ function checkTestTampering(filePath, findings) {
539
555
  } catch (_) { }
540
556
  }
541
557
 
542
- function checkWorkflowGate(toolName, filePath, ext, findings) {
558
+ function checkWorkflowGate(toolName, filePath, ext, findings, workspaceRoot) {
543
559
  try {
560
+ var root = workspaceRoot || process.cwd();
544
561
  var pathUtil = require('./path-util.cjs');
545
- var localGate = path.join(process.cwd(), 'workflow-gate.json');
546
- var gatePath = fs.existsSync(localGate) ? localGate : pathUtil.getWorkflowGatePath(process.cwd());
562
+ var localGate = path.join(root, 'workflow-gate.json');
563
+ var gatePath = fs.existsSync(localGate) ? localGate : pathUtil.getWorkflowGatePath(root);
547
564
  if (!fs.existsSync(gatePath)) return;
548
565
 
549
566
  var gateStr = fs.readFileSync(gatePath, 'utf8');
@@ -564,16 +581,16 @@ function checkWorkflowGate(toolName, filePath, ext, findings) {
564
581
  + 'Stop and wait for phase approval. If you must proceed, log this bypass to the debt ledger.'
565
582
  );
566
583
  } else if (gate.phase === 'implement') {
567
- validateDocSpecs(gate.workflow, findings);
584
+ validateDocSpecs(gate.workflow, findings, root);
568
585
  }
569
586
  } catch (_) {
570
587
  // Silent fail
571
588
  }
572
589
  }
573
590
 
574
- function validateDocSpecs(workflow, findings) {
591
+ function validateDocSpecs(workflow, findings, workspaceRoot) {
575
592
  try {
576
- var cwd = process.cwd();
593
+ var cwd = workspaceRoot || process.cwd();
577
594
  var docsDir = path.join(cwd, 'docs');
578
595
 
579
596
  // Anti-typo check for common doc typos (e.g. Architectyre.md)