@ryuenn3123/agentic-senior-core 6.16.2 → 6.16.5

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 (47) 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/hooks/dedup-gate.js +137 -128
  5. package/.agents/plugins/agentic-senior-core/hooks/hooks.json +97 -0
  6. package/.agents/plugins/agentic-senior-core/hooks/ladder-pulse.js +53 -36
  7. package/.agents/plugins/agentic-senior-core/hooks/lib/known-ui-slop-patterns.json +20 -0
  8. package/.agents/plugins/agentic-senior-core/hooks/path-util.cjs +118 -4
  9. package/.agents/plugins/agentic-senior-core/hooks/post-edit-enforce.js +98 -81
  10. package/.agents/plugins/agentic-senior-core/hooks/pre-tool-dependency-gate.js +168 -191
  11. package/.agents/plugins/agentic-senior-core/hooks/session-pulse.js +48 -35
  12. package/.agents/plugins/agentic-senior-core/hooks/session-start.js +62 -11
  13. package/.agents/plugins/agentic-senior-core/hooks.json +4 -94
  14. package/.agents/plugins/agentic-senior-core/plugin.json +5 -13
  15. package/.agents/plugins/agentic-senior-core/rules/agentic-senior-core.md +6 -1
  16. package/.agents/plugins/agentic-senior-core/skills/asc/SKILL.md +5 -2
  17. package/.agents/plugins/agentic-senior-core/skills/asc-develop/SKILL.md +3 -3
  18. package/.agents/plugins/agentic-senior-core/skills/asc-pentest/SKILL.md +97 -0
  19. package/.agents/plugins/agentic-senior-core/skills/asc-pentest/references/api-security-idor.md +105 -0
  20. package/.agents/plugins/agentic-senior-core/skills/asc-pentest/references/dast-baseline.md +78 -0
  21. package/.agents/plugins/agentic-senior-core/skills/asc-pentest/references/fuzzing-dsl.md +66 -0
  22. package/.agents/plugins/agentic-senior-core/skills/asc-pentest/references/pentest-task-tree.md +70 -0
  23. package/.agents/plugins/agentic-senior-core/skills/asc-review/SKILL.md +2 -2
  24. package/.agents/plugins/agentic-senior-core/skills/asc-test/SKILL.md +8 -1
  25. package/.agents/plugins/agentic-senior-core/skills/asc-test/references/mutation-testing.md +55 -0
  26. package/.agents/plugins/agentic-senior-core/skills/asc-test/references/performance-budgets.md +67 -0
  27. package/.agents/plugins/agentic-senior-core/skills/asc-test/references/property-testing.md +72 -0
  28. package/.agents/plugins/agentic-senior-core/skills/asc-ui/SKILL.md +17 -14
  29. package/.agents/plugins/agentic-senior-core/skills/asc-ui/references/copy-i18n.md +56 -8
  30. package/.agents/plugins/agentic-senior-core/skills/asc-ui/references/craft/color-surfaces.md +25 -0
  31. package/.agents/plugins/agentic-senior-core/skills/asc-ui/references/craft/themes-catalog.md +198 -0
  32. package/.agents/plugins/agentic-senior-core/skills/asc-ui/references/divergence.md +34 -2
  33. package/.agents/plugins/agentic-senior-core/skills/asc-ui/references/modes/genre-calibration.md +89 -0
  34. package/.agents/plugins/agentic-senior-core/skills/asc-ui/references/modes/operate.md +26 -0
  35. package/.agents/plugins/agentic-senior-core/skills/asc-ui/references/modes/persuade.md +10 -9
  36. package/.agents/plugins/agentic-senior-core/skills/asc-ui/references/redesign-audit.md +4 -9
  37. package/bin/agentic-senior-core.js +19 -4
  38. package/gemini-extension.json +2 -2
  39. package/lib/cli/ascx/runtime.mjs +5 -27
  40. package/lib/cli/ascx/windows-spawn.mjs +348 -0
  41. package/lib/cli/commands/bootstrap.mjs +56 -0
  42. package/lib/cli/commands/dedup.mjs +500 -0
  43. package/lib/cli/commands/git-hook-generator.mjs +3 -2
  44. package/lib/cli/commands/global.mjs +109 -53
  45. package/lib/cli/commands/preview.mjs +18 -9
  46. package/package.json +1 -1
  47. package/plugin.yaml +3 -1
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agentic-senior-core",
3
- "version": "6.16.2",
3
+ "version": "6.16.5",
4
4
  "description": "Universal AI coding rules. Write code like a staff engineer.",
5
5
  "skills": "./skills/",
6
6
  "hooks": "./hooks/hooks.json",
@@ -0,0 +1,2 @@
1
+ Run the adversarial penetration testing, DAST, and API security verification standards from skills/asc-pentest/SKILL.md. Execute non-destructive reconnaissance, dynamic request fuzzing, BOLA/IDOR dual-identity verification, and generate deterministic proof-of-exploit ledgers with safe remediation.
2
+
@@ -0,0 +1,3 @@
1
+ description = "Adversarial penetration testing, DAST, API security fuzzing, and BOLA/IDOR verification"
2
+ prompt = "Load and follow the asc-pentest skill. Execute defensive penetration testing against local/staging targets using the Pentesting Task Tree, run DAST baseline scans, probe BOLA/IDOR with dual-identity replay, fuzz input boundaries safely, and output deterministic proof-of-exploit curl ledgers."
3
+
@@ -14,6 +14,11 @@ const {
14
14
  LOC_DELTA_THRESHOLD,
15
15
  NEW_FILE_LINE_THRESHOLD,
16
16
  } = require('./constants.cjs');
17
+ const {
18
+ isAntigravityPayload,
19
+ resolveWorkspaceRoot,
20
+ normalizeEditToolCall,
21
+ } = require('./path-util.cjs');
17
22
 
18
23
  const JSCPD_TIMEOUT_MS = 10000;
19
24
 
@@ -77,133 +82,136 @@ function isFrameworkConventional(basename) {
77
82
  return FRAMEWORK_CONVENTIONAL_BASENAMES.has(basename) || hasConventionalSuffix(basename);
78
83
  }
79
84
 
85
+ let handled = false;
80
86
  let inputBuffer = '';
81
- process.stdin.setEncoding('utf8');
82
- process.stdin.on('data', chunk => {
83
- inputBuffer += chunk;
84
- try {
85
- const data = JSON.parse(inputBuffer);
86
87
 
87
- let toolName = '';
88
- let toolInput = {};
89
- let isAntigravity = false;
90
-
91
- if (data.toolCall) {
92
- // Antigravity PreToolUse shape (has toolCall directly)
93
- isAntigravity = true;
94
- toolName = data.toolCall.name;
95
- toolInput = data.toolCall.args || {};
96
- } else if (data.stepIdx !== undefined && data.transcriptPath) {
97
- // Antigravity PostToolUse shape — no toolCall in payload,
98
- // read the tool call details from the transcript at stepIdx
99
- isAntigravity = true;
100
- var extracted = extractToolCallFromTranscript(data.transcriptPath, data.stepIdx);
101
- if (!extracted) { process.exit(0); return; }
102
- toolName = extracted.toolName;
103
- toolInput = extracted.toolInput;
104
- } else {
105
- // Claude Code / Codex / Copilot path
106
- toolName = data.tool_name || data.toolName || '';
107
- toolInput = data.tool_input || data.toolInput || {};
108
- }
88
+ function handleDedupPayload(data) {
89
+ if (handled) return;
90
+ handled = true;
109
91
 
110
- const filePath = toolInput.file_path || toolInput.TargetFile || toolInput.path || toolInput.target_file || '';
111
- if (!filePath) { process.exit(0); return; }
92
+ const isAntigravity = isAntigravityPayload(data);
93
+ if (isAntigravity && data && data.stepIdx !== undefined) {
94
+ // Antigravity PostToolUse strictly expects {} on stdout
95
+ process.stdout.write('{}\n');
96
+ process.exit(0);
97
+ return;
98
+ }
112
99
 
113
- const ext = path.extname(filePath).slice(1);
114
- if (!SOURCE_EXTENSIONS.has(ext)) { process.exit(0); return; }
100
+ const workspaceRoot = resolveWorkspaceRoot(data);
101
+ let rawTool = '';
102
+ let rawArgs = {};
115
103
 
116
- const config = loadDedupConfig();
117
- if (!isQualifyingEdit(toolName, toolInput, config)) { process.exit(0); return; }
104
+ if (data && data.toolCall) {
105
+ rawTool = data.toolCall.name || '';
106
+ rawArgs = data.toolCall.args || {};
107
+ } else if (data) {
108
+ rawTool = data.tool_name || data.toolName || '';
109
+ rawArgs = data.tool_input || data.toolInput || {};
110
+ }
111
+
112
+ const norm = normalizeEditToolCall(rawTool, rawArgs);
113
+ if (!norm || !norm.filePath) {
114
+ if (isAntigravity) process.stdout.write('{}\n');
115
+ process.exit(0);
116
+ return;
117
+ }
118
118
 
119
- if (hasValidInlineIgnore(filePath)) { process.exit(0); return; }
119
+ const resolvedFile = path.isAbsolute(norm.filePath)
120
+ ? norm.filePath
121
+ : path.resolve(workspaceRoot, norm.filePath);
122
+ const ext = path.extname(resolvedFile).slice(1);
123
+ if (!SOURCE_EXTENSIONS.has(ext)) {
124
+ if (isAntigravity) process.stdout.write('{}\n');
125
+ process.exit(0);
126
+ return;
127
+ }
120
128
 
121
- const scanDir = resolveScanDir(filePath, config);
122
- const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'asc-dedup-'));
129
+ const config = loadDedupConfig(workspaceRoot);
130
+ const mappedInput = norm.canonicalTool === 'Edit'
131
+ ? { new_string: norm.newContent, old_string: norm.oldContent }
132
+ : { content: norm.newContent };
123
133
 
124
- const ignoreFlags = (config.ignoreDirs || []).map(function (d) { return '--ignore "' + d + '"'; }).join(' ');
125
- const minTokens = config.minTokens || 30;
126
- const scanCmd = ' "' + scanDir + '" --min-tokens ' + minTokens
127
- + ' --reporters json --silent --output "' + tmpDir + '" ' + ignoreFlags;
134
+ if (!isQualifyingEdit(norm.canonicalTool, mappedInput, config)) {
135
+ if (isAntigravity) process.stdout.write('{}\n');
136
+ process.exit(0);
137
+ return;
138
+ }
128
139
 
129
- const report = runJscpdScan(scanCmd, tmpDir);
130
- if (!report) { cleanup(tmpDir); process.exit(0); return; }
140
+ if (hasValidInlineIgnore(resolvedFile)) {
141
+ if (isAntigravity) process.stdout.write('{}\n');
142
+ process.exit(0);
143
+ return;
144
+ }
131
145
 
132
- const finding = checkForDuplicates(report, filePath, config);
133
- if (!finding) { cleanup(tmpDir); process.exit(0); return; }
146
+ const scanDir = resolveScanDir(resolvedFile, config, workspaceRoot);
147
+ const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'asc-dedup-'));
134
148
 
135
- const nudge = '[ASC Dedup] ' + path.basename(filePath) + ' looks similar to '
136
- + finding.matchedFile + ' (' + finding.percent + '% overlap, '
137
- + finding.lines + ' lines). Ladder step 2: does the codebase already have this? '
138
- + 'Reuse it, or confirm this is intentional.';
149
+ const ignoreFlags = (config.ignoreDirs || []).map(function (d) { return '--ignore "' + d + '"'; }).join(' ');
150
+ const minTokens = config.minTokens || 30;
151
+ const scanCmd = ' "' + scanDir + '" --min-tokens ' + minTokens
152
+ + ' --reporters json --silent --output "' + tmpDir + '" ' + ignoreFlags;
139
153
 
140
- if (config.mode === 'block') {
141
- emitDeny(nudge, isAntigravity);
142
- } else {
143
- emitAdvisory(nudge, isAntigravity);
144
- }
154
+ const report = runJscpdScan(scanCmd, tmpDir, workspaceRoot);
155
+ if (!report) {
156
+ cleanup(tmpDir);
157
+ if (isAntigravity) process.stdout.write('{}\n');
158
+ process.exit(0);
159
+ return;
160
+ }
145
161
 
162
+ const finding = checkForDuplicates(report, resolvedFile, config, workspaceRoot);
163
+ if (!finding) {
146
164
  cleanup(tmpDir);
165
+ if (isAntigravity) process.stdout.write('{}\n');
147
166
  process.exit(0);
148
- } catch (e) {
167
+ return;
168
+ }
169
+
170
+ const nudge = '[ASC Dedup] ' + path.basename(resolvedFile) + ' looks similar to '
171
+ + finding.matchedFile + ' (' + finding.percent + '% overlap, '
172
+ + finding.lines + ' lines). Ladder step 2: does the codebase already have this? '
173
+ + 'Reuse it, or confirm this is intentional.';
174
+
175
+ cleanup(tmpDir);
176
+ if (config.mode === 'block') {
177
+ emitDeny(nudge, isAntigravity);
178
+ } else {
179
+ emitAdvisory(nudge, isAntigravity);
180
+ }
181
+ process.exit(0);
182
+ }
183
+
184
+ if (process.stdin.isTTY) {
185
+ process.exit(0);
186
+ }
187
+
188
+ process.stdin.setEncoding('utf8');
189
+ process.stdin.on('data', chunk => {
190
+ inputBuffer += chunk;
191
+ try {
192
+ const data = JSON.parse(inputBuffer);
193
+ handleDedupPayload(data);
194
+ } catch (_) {
149
195
  // incomplete json, wait for more chunks
150
196
  }
151
197
  });
152
198
 
153
- // Extract tool call details from the Antigravity transcript at a given step index.
154
- // PostToolUse on Antigravity only provides stepIdx — the actual tool call args
155
- // must be recovered from the transcript log. Same approach as post-edit-enforce.js.
156
- function extractToolCallFromTranscript(transcriptPath, stepIdx) {
199
+ process.stdin.on('end', () => {
200
+ if (handled) return;
201
+ if (!inputBuffer.trim()) {
202
+ process.exit(0);
203
+ return;
204
+ }
157
205
  try {
158
- if (!fs.existsSync(transcriptPath)) return null;
159
- var lines = fs.readFileSync(transcriptPath, 'utf8').split('\n').filter(Boolean);
160
-
161
- // Find the step with matching step_index that contains tool_calls
162
- for (var i = lines.length - 1; i >= 0; i--) {
163
- try {
164
- var step = JSON.parse(lines[i]);
165
- if (step.step_index !== stepIdx || !step.tool_calls) continue;
166
-
167
- for (var j = 0; j < step.tool_calls.length; j++) {
168
- var tc = step.tool_calls[j];
169
- if (tc.name === 'replace_file_content' || tc.name === 'multi_replace_file_content') {
170
- return {
171
- toolName: 'Edit',
172
- toolInput: {
173
- file_path: tc.args.TargetFile || '',
174
- new_string: tc.args.ReplacementContent || '',
175
- old_string: tc.args.TargetContent || '',
176
- },
177
- };
178
- } else if (tc.name === 'write_to_file') {
179
- return {
180
- toolName: 'Write',
181
- toolInput: {
182
- file_path: tc.args.TargetFile || '',
183
- content: tc.args.CodeContent || '',
184
- },
185
- };
186
- }
187
- }
188
- } catch (lineErr) {
189
- if (process.env.ASC_DEBUG) {
190
- console.error('[ASC Debug] Transcript line parse failed line ' + i + ':', lineErr.message);
191
- }
192
- // Skip malformed line and keep scanning
193
- }
194
- }
195
- return null;
196
- } catch (err) {
197
- if (process.env.ASC_DEBUG) {
198
- console.error('[ASC Debug] extractToolCallFromTranscript failed:', err.message);
199
- }
200
- return null;
206
+ handleDedupPayload(JSON.parse(inputBuffer));
207
+ } catch (_) {
208
+ process.exit(0);
201
209
  }
202
- }
210
+ });
203
211
 
204
212
  function isQualifyingEdit(toolName, toolInput, config) {
205
213
  var isWrite = ['Write', 'write_to_file', 'write_file'].indexOf(toolName) !== -1;
206
- var isEdit = ['Edit', 'replace_file_content', 'multi_replace_file_content'].indexOf(toolName) !== -1;
214
+ var isEdit = ['Edit', 'edit_file', 'replace_file_content', 'multi_replace_file_content'].indexOf(toolName) !== -1;
207
215
 
208
216
  var newFileThreshold = (config && typeof config.NEW_FILE_LINE_THRESHOLD === 'number')
209
217
  ? config.NEW_FILE_LINE_THRESHOLD
@@ -225,8 +233,8 @@ function isQualifyingEdit(toolName, toolInput, config) {
225
233
  return false;
226
234
  }
227
235
 
228
- function resolveScanDir(filePath, config) {
229
- var cwd = process.cwd();
236
+ function resolveScanDir(filePath, config, workspaceRoot) {
237
+ var cwd = workspaceRoot || process.cwd();
230
238
 
231
239
  // User override takes priority
232
240
  if (config.scanRoot) {
@@ -248,10 +256,11 @@ function resolveScanDir(filePath, config) {
248
256
  return cwd;
249
257
  }
250
258
 
251
- function loadDedupConfig() {
259
+ function loadDedupConfig(workspaceRoot) {
260
+ var root = workspaceRoot || process.cwd();
252
261
  var candidates = [
253
- path.join(process.cwd(), '.asc', 'dedup-config.json'),
254
- path.join(process.cwd(), '.agents', 'dedup-config.json'),
262
+ path.join(root, '.asc', 'dedup-config.json'),
263
+ path.join(root, '.agents', 'dedup-config.json'),
255
264
  ];
256
265
  for (var i = 0; i < candidates.length; i++) {
257
266
  try {
@@ -273,18 +282,20 @@ function loadDedupConfig() {
273
282
 
274
283
  // minimal: attempt-then-fallback — try scan directly, fall back on failure.
275
284
  // No separate --version probes. Upgrade if jscpd provides a stable JS API.
276
- function runJscpdScan(scanCmd, tmpDir) {
277
- var binaries = ['bunx jscpd', 'npx jscpd@5', 'npx jscpd'];
285
+ function runJscpdScan(scanCmd, tmpDir, workspaceRoot) {
286
+ var cwd = workspaceRoot || process.cwd();
287
+ var binaries = ['bunx jscpd', 'npx --yes jscpd@5', 'npx --yes jscpd'];
278
288
  for (var i = 0; i < binaries.length; i++) {
279
289
  try {
280
290
  execSync(binaries[i] + scanCmd, {
281
291
  timeout: JSCPD_TIMEOUT_MS,
282
292
  stdio: 'pipe',
283
- cwd: process.cwd(),
293
+ cwd: cwd,
284
294
  });
285
295
  return loadReport(tmpDir);
286
296
  } catch (_) {
287
- // Try next binary
297
+ var partial = loadReport(tmpDir);
298
+ if (partial) return partial;
288
299
  }
289
300
  }
290
301
  return null;
@@ -333,7 +344,7 @@ function isAllowedDuplicate(fileA, fileB, allowedDuplicates) {
333
344
  return false;
334
345
  }
335
346
 
336
- function checkForDuplicates(report, filePath, config) {
347
+ function checkForDuplicates(report, filePath, config, workspaceRoot) {
337
348
  var duplicates = report.duplicates || [];
338
349
  if (duplicates.length === 0) return null;
339
350
 
@@ -354,17 +365,13 @@ function checkForDuplicates(report, filePath, config) {
354
365
  continue;
355
366
  }
356
367
 
357
- // Skip framework-conventional filenames in different directories — identical names
358
- // are mandated by the framework (e.g. Next.js page.tsx, Angular *.component.ts),
359
- // not copy-paste duplication.
360
368
  if (isFrameworkConventional(targetBasename)
361
369
  && isFrameworkConventional(otherBasename)
362
370
  && path.dirname(path.resolve(filePath)) !== path.dirname(path.resolve(otherRaw))) {
363
371
  continue;
364
372
  }
365
373
 
366
- // Show relative path instead of bare basename so the developer knows WHICH file.
367
- var cwd = process.cwd();
374
+ var cwd = workspaceRoot || process.cwd();
368
375
  var matchedFile = path.relative(cwd, path.resolve(otherRaw)).replace(/\\/g, '/');
369
376
  var lines = dup.lines || 0;
370
377
  var totalFileLines = 1;
@@ -375,7 +382,6 @@ function checkForDuplicates(report, filePath, config) {
375
382
  }
376
383
  var percent = Math.round((lines / totalFileLines) * 100);
377
384
 
378
- // Skip trivial matches (import boilerplate, small overlaps)
379
385
  if (lines < 10 && percent < 10) {
380
386
  continue;
381
387
  }
@@ -408,18 +414,21 @@ function emitAdvisory(nudge, isAntigravity) {
408
414
 
409
415
  function emitDeny(reason, isAntigravity) {
410
416
  if (isAntigravity) {
411
- process.stdout.write(JSON.stringify({ decision: 'deny', reason: reason }) + '\n');
412
- } else {
413
417
  process.stdout.write(JSON.stringify({
414
- allow_tool: false,
415
- deny_reason: reason,
416
- hookSpecificOutput: {
417
- hookEventName: 'PostToolUse',
418
- permissionDecision: 'deny',
419
- permissionDecisionReason: reason,
420
- },
418
+ injectSteps: [{ ephemeralMessage: reason }],
421
419
  }) + '\n');
420
+ process.exit(0);
421
+ return;
422
422
  }
423
+ process.stdout.write(JSON.stringify({
424
+ allow_tool: false,
425
+ deny_reason: reason,
426
+ hookSpecificOutput: {
427
+ hookEventName: 'PostToolUse',
428
+ permissionDecision: 'deny',
429
+ permissionDecisionReason: reason,
430
+ },
431
+ }) + '\n');
423
432
  process.exit(2);
424
433
  }
425
434
 
@@ -0,0 +1,97 @@
1
+ {
2
+ "hooks": {
3
+ "SessionStart": [
4
+ {
5
+ "matcher": "startup|resume|clear|compact",
6
+ "hooks": [
7
+ {
8
+ "type": "command",
9
+ "command": "node hooks/session-start.js",
10
+ "commandWindows": "node hooks/session-start.js",
11
+ "timeout": 5,
12
+ "statusMessage": "Loading ASC rules..."
13
+ }
14
+ ]
15
+ }
16
+ ],
17
+ "SubagentStart": [
18
+ {
19
+ "hooks": [
20
+ {
21
+ "type": "command",
22
+ "command": "node hooks/subagent-start.js",
23
+ "commandWindows": "node hooks/subagent-start.js",
24
+ "timeout": 5,
25
+ "statusMessage": "Loading ASC rules..."
26
+ }
27
+ ]
28
+ }
29
+ ],
30
+ "PreToolUse": [
31
+ {
32
+ "matcher": "Edit|Write|edit_file|replace_file_content|write_to_file|write_file|multi_replace_file_content",
33
+ "hooks": [
34
+ {
35
+ "type": "command",
36
+ "if": "Edit(**/package.json)|Edit(**/requirements.txt)|Edit(**/pyproject.toml)|Edit(**/go.mod)|Edit(**/Cargo.toml)|Edit(**/Gemfile)",
37
+ "command": "node hooks/pre-tool-dependency-gate.js",
38
+ "commandWindows": "node hooks/pre-tool-dependency-gate.js",
39
+ "timeout": 5,
40
+ "statusMessage": "ASC Pre-tool dependency check (Edit)..."
41
+ },
42
+ {
43
+ "type": "command",
44
+ "if": "Write(**/package.json)|Write(**/requirements.txt)|Write(**/pyproject.toml)|Write(**/go.mod)|Write(**/Cargo.toml)|Write(**/Gemfile)",
45
+ "command": "node hooks/pre-tool-dependency-gate.js",
46
+ "commandWindows": "node hooks/pre-tool-dependency-gate.js",
47
+ "timeout": 5,
48
+ "statusMessage": "ASC Pre-tool dependency check (Write)..."
49
+ }
50
+ ]
51
+ },
52
+ {
53
+ "matcher": "Bash|run_command|run_shell_command|terminal|execute_command",
54
+ "hooks": [
55
+ {
56
+ "type": "command",
57
+ "command": "node hooks/pre-tool-dependency-gate.js",
58
+ "commandWindows": "node hooks/pre-tool-dependency-gate.js",
59
+ "timeout": 5,
60
+ "statusMessage": "ASC Pre-tool dependency check (Terminal)..."
61
+ }
62
+ ]
63
+ }
64
+ ],
65
+ "PostToolUse": [
66
+ {
67
+ "matcher": "Edit|Write|edit_file|replace_file_content|write_to_file|write_file|multi_replace_file_content",
68
+ "hooks": [
69
+ {
70
+ "type": "command",
71
+ "command": "node hooks/post-edit-enforce.js",
72
+ "commandWindows": "node hooks/post-edit-enforce.js",
73
+ "timeout": 5,
74
+ "statusMessage": "ASC ladder & spec gate check..."
75
+ },
76
+ {
77
+ "type": "command",
78
+ "command": "node hooks/dedup-gate.js",
79
+ "commandWindows": "node hooks/dedup-gate.js",
80
+ "timeout": 15,
81
+ "statusMessage": "ASC duplicate-code scan..."
82
+ }
83
+ ]
84
+ }
85
+ ],
86
+ "PreCompact": [
87
+ {
88
+ "type": "command",
89
+ "command": "node hooks/pre-compact-pin.js",
90
+ "commandWindows": "node hooks/pre-compact-pin.js",
91
+ "timeout": 5,
92
+ "statusMessage": "ASC constraint pinning..."
93
+ }
94
+ ]
95
+ }
96
+ }
97
+
@@ -1,48 +1,65 @@
1
1
  #!/usr/bin/env node
2
2
  // Agentic Senior Core — PreInvocation ladder persistence hook
3
- // Counters context rot by re-injecting a short decision-ladder reminder
4
- // periodically, late in context, rather than relying only on rules/ content
5
- // that sits at the start of the conversation.
6
- // Antigravity-only: PreInvocation fires before every model call with
7
- // invocationNum in the input payload. For other hosts, the equivalent
8
- // pulse is integrated into post-edit-enforce.js via the sourceEditCount counter.
3
+ // Counters context rot by re-injecting a concise decision-ladder + security pin
4
+ // periodically during long Antigravity sessions.
9
5
 
10
- const { LADDER_PULSE_INTERVAL } = require('./constants.cjs');
6
+ const { getThresholds } = require('./constants.cjs');
7
+ const { resolveWorkspaceRoot } = require('./path-util.cjs');
11
8
 
12
- const LADDER_REMINDER = '[ASC] Ladder check: (1) needed? (2) exists already \u2014 reuse? '
13
- + '(3) stdlib/native? (4) existing dep? (5) one function? Then minimal code.';
14
-
15
- // Security constraints are negation-type ("never do X") — most vulnerable to context rot
16
- // per arXiv:2604.20911. Reinject verbatim alongside ladder pulse.
17
9
  const SECURITY_REMINDER = '[ASC SECURITY PIN — verbatim, do not paraphrase] '
18
10
  + 'NEVER: interpolate input into SQL/shell · commit secrets/tokens/credentials '
19
11
  + '· store plaintext passwords · leak stack traces/internals/PII in responses. '
20
12
  + 'ALWAYS: parameterize queries · enforce resource-level authz · rate-limit public endpoints.';
21
13
 
14
+ if (process.stdin.isTTY) {
15
+ process.stdout.write('{}\n');
16
+ process.exit(0);
17
+ }
18
+
19
+ let handled = false;
22
20
  let inputBuffer = '';
23
- process.stdin.setEncoding('utf8');
24
- process.stdin.on('data', chunk => {
25
- inputBuffer += chunk;
26
- try {
27
- const payload = JSON.parse(inputBuffer);
28
-
29
- let shouldInject = false;
30
- const invNum = payload.invocationNum || 0;
31
- if (invNum > 0 && invNum % LADDER_PULSE_INTERVAL === 0) {
32
- shouldInject = true;
33
- }
34
-
35
- if (shouldInject) {
36
- process.stdout.write(JSON.stringify({
37
- injectSteps: [{
38
- ephemeralMessage: `**ASC LADDER PULSE**: You have completed several steps. Remember to review the 1-6 decision ladder. Document deferred debt if you take shortcuts.\n\n${SECURITY_REMINDER}`
39
- }]
40
- }) + '\n');
41
- } else {
42
- process.stdout.write('{}\n');
43
- }
44
- process.exit(0);
45
- } catch (e) {
46
- // wait for more chunks
21
+
22
+ function finish(payload) {
23
+ if (handled) return;
24
+ handled = true;
25
+ clearTimeout(timer);
26
+
27
+ try {
28
+ const workspaceRoot = resolveWorkspaceRoot(payload);
29
+ const thresholds = getThresholds(workspaceRoot);
30
+ const interval = thresholds.LADDER_PULSE_INTERVAL || 3;
31
+ const invNum = payload && typeof payload.invocationNum === 'number' ? payload.invocationNum : 0;
32
+
33
+ if (invNum > 0 && invNum % interval === 0) {
34
+ process.stdout.write(JSON.stringify({
35
+ injectSteps: [{
36
+ ephemeralMessage: '**ASC LADDER PULSE**: Review the 1-6 decision ladder before writing new abstractions. Document deferred debt if taking shortcuts.\n\n' + SECURITY_REMINDER,
37
+ }],
38
+ }) + '\n');
39
+ } else {
40
+ process.stdout.write('{}\n');
47
41
  }
42
+ } catch (_) {
43
+ process.stdout.write('{}\n');
44
+ }
45
+ process.exit(0);
46
+ }
47
+
48
+ const timer = setTimeout(function () { finish(null); }, 2000);
49
+
50
+ process.stdin.setEncoding('utf8');
51
+ process.stdin.on('data', function (chunk) {
52
+ inputBuffer += chunk;
53
+ try {
54
+ const payload = JSON.parse(inputBuffer);
55
+ finish(payload);
56
+ } catch (_) {}
57
+ });
58
+
59
+ process.stdin.on('end', function () {
60
+ let payload = null;
61
+ if (inputBuffer.trim()) {
62
+ try { payload = JSON.parse(inputBuffer); } catch (_) {}
63
+ }
64
+ finish(payload);
48
65
  });
@@ -139,6 +139,26 @@
139
139
  "id": "ui-agency-micro-meta",
140
140
  "regex": "(?:\\b\\d{1,2}\\.\\d+°\\s*[NSEW]\\b|©\\s*20\\d\\d\\s*—\\s*Scroll)",
141
141
  "message": "note: performative agency metadata (geographic coordinates, fake version/scroll stamps) detected. Remove ornamental meta."
142
+ },
143
+ {
144
+ "id": "copy-ai-buzzwords",
145
+ "regex": "\\b(seamlessly?|robust(?:ness)?|empowers?|supercharg(?:ed?|ing)|cutting-edge|next-generation|blazing-fast|effortlessly?)\\b",
146
+ "message": "note: generic AI buzzword detected ('seamless', 'robust', 'supercharge', 'cutting-edge'). Use plain operational verbs and concrete metrics."
147
+ },
148
+ {
149
+ "id": "copy-cadence-tricolon",
150
+ "regex": "\\b[A-Z][a-z]+\\.\\s+[A-Z][a-z]+\\.\\s+[A-Z][a-z]+\\.",
151
+ "message": "note: tricolon cadence detected ('Word. Word. Word.'). State the capability or outcome directly instead of using advertising triplets."
152
+ },
153
+ {
154
+ "id": "copy-cadence-rebuttal",
155
+ "regex": "\\b(Not (?:just )?a|More than a) \\w+[,.] (?:it's|a) \\w+\\b",
156
+ "message": "note: synthetic rebuttal cadence ('Not just a X, it\\'s a Y') detected. State what the system does directly without the false dichotomy."
157
+ },
158
+ {
159
+ "id": "copy-filler-preamble",
160
+ "regex": "\\b(In today's (?:fast-paced|digital)|Built for (?:the )?modern teams?|Welcome to the)\\b",
161
+ "message": "note: AI preamble/filler detected ('In today\\'s fast-paced...', 'Built for modern teams'). Delete the introductory throat-clearing and lead with action."
142
162
  }
143
163
  ]
144
164
  }