@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
@@ -1,11 +1,16 @@
1
1
  #!/usr/bin/env node
2
- // Agentic Senior Core — PreToolUse dependency gate hook
3
- // Hard-blocks edits/writes to package.json AND shell commands (npm install/yarn add/ascx)
4
- // if new dependencies duplicate stdlib/platform features.
5
- // Supports Claude Code, Antigravity IDE (run_command), and Copilot CLI hook response formats.
2
+ // Agentic Senior Core — PreToolUse dependency & safety gate hook
3
+ // Gates edits/writes to dependency manifests AND shell commands (npm install/yarn add/ascx,
4
+ // git commit/push, unverified/insecure git clone).
5
+ // Supports Antigravity (App, CLI, IDE), Claude Code, Codex CLI, and Copilot CLI.
6
6
 
7
7
  const fs = require('fs');
8
8
  const path = require('path');
9
+ const {
10
+ isAntigravityPayload,
11
+ resolveWorkspaceRoot,
12
+ normalizeEditToolCall,
13
+ } = require('./path-util.cjs');
9
14
 
10
15
  let knownDuplicates = new Set();
11
16
  let reviewDuplicates = new Set();
@@ -14,8 +19,6 @@ try {
14
19
  const knownPath = path.join(__dirname, 'lib', 'known-duplicates.json');
15
20
  if (fs.existsSync(knownPath)) {
16
21
  const raw = JSON.parse(fs.readFileSync(knownPath, 'utf8'));
17
- // Tiered structure (v5): 'block' = always redundant, 'review' = context-dependent.
18
- // Legacy flat 'duplicates' arrays are treated as review-tier for backward compatibility.
19
22
  if (Array.isArray(raw.block) || Array.isArray(raw.review)) {
20
23
  knownDuplicates = new Set(raw.block || []);
21
24
  reviewDuplicates = new Set(raw.review || []);
@@ -25,193 +28,184 @@ try {
25
28
  }
26
29
  }
27
30
  } catch (_) {
28
- // Fallback if JSON loading fails: block only the always-redundant set
29
31
  knownDuplicates = new Set(['left-pad', 'is-odd', 'mkdirp', 'rimraf', 'node-fetch', 'underscore']);
30
32
  }
31
33
 
32
- let inputBuffer = '';
33
- process.stdin.setEncoding('utf8');
34
- process.stdin.on('data', chunk => {
35
- inputBuffer += chunk;
36
- try {
37
- const data = JSON.parse(inputBuffer);
38
- const isAntigravity = !!data.toolCall;
39
- const toolName = isAntigravity ? data.toolCall.name : (data.tool_name || data.toolName || '');
40
- const toolInput = isAntigravity ? data.toolCall.args : (data.tool_input || data.toolInput || {});
41
- let added = [];
42
-
43
- const isTerminal = ['Bash', 'run_command', 'run_shell_command', 'terminal', 'execute_command'].includes(toolName);
44
- const isFileEdit = ['Edit', 'Write', 'replace_file_content', 'write_to_file', 'write_file', 'multi_replace_file_content'].includes(toolName);
45
-
46
- if (isTerminal) {
47
- const command = toolInput.command || toolInput.CommandLine || toolInput.cmd || toolInput.commandLine || '';
48
-
49
- // Hard-block git commit/push unless explicitly allowed
50
- if (isGitCommitOrPush(command)) {
51
- const reason = '[ASC Hard-Block] git commit/push detected. Never run git commit, git push, or git push --force unless the user explicitly requests it this turn.';
52
- let output;
53
- if (isAntigravity) {
54
- output = { decision: "deny", reason: reason };
55
- } else {
56
- output = {
57
- allow_tool: false,
58
- deny_reason: reason,
59
- hookSpecificOutput: {
60
- hookEventName: 'PreToolUse',
61
- permissionDecision: 'deny',
62
- permissionDecisionReason: reason
63
- }
64
- };
65
- }
66
- process.stdout.write(JSON.stringify(output) + '\n');
67
- process.exit(2);
68
- return;
69
- }
34
+ function emitAllow(isAntigravity) {
35
+ if (isAntigravity) {
36
+ process.stdout.write(JSON.stringify({ decision: 'allow' }) + '\n');
37
+ }
38
+ process.exit(0);
39
+ }
70
40
 
71
- const allowlist = loadAllowlist();
72
-
73
- // Hard-block unverified git clone targets (HalluSquatting mitigation)
74
- const cloneCheck = checkGitClone(command, allowlist);
75
- if (cloneCheck && cloneCheck.blocked) {
76
- const reason = cloneCheck.reason;
77
- let output;
78
- if (isAntigravity) {
79
- output = { decision: "deny", reason: reason };
80
- } else {
81
- output = {
82
- allow_tool: false,
83
- deny_reason: reason,
84
- hookSpecificOutput: {
85
- hookEventName: 'PreToolUse',
86
- permissionDecision: 'deny',
87
- permissionDecisionReason: reason
88
- }
89
- };
90
- }
91
- process.stdout.write(JSON.stringify(output) + '\n');
92
- process.exit(2);
93
- return;
94
- }
41
+ function emitGateDecision(antigravityDecision, reason, isAntigravity) {
42
+ if (isAntigravity) {
43
+ process.stdout.write(JSON.stringify({
44
+ decision: antigravityDecision,
45
+ reason: reason,
46
+ }) + '\n');
47
+ process.exit(0);
48
+ return;
49
+ }
95
50
 
96
- added = extractCommandDeps(command);
97
- } else if (isFileEdit) {
98
- const filePath = toolInput.file_path || toolInput.TargetFile || toolInput.path || toolInput.target_file || '';
99
- const manifestFiles = ['package.json', 'requirements.txt', 'pyproject.toml', 'go.mod', 'Cargo.toml', 'Gemfile'];
100
- const isManifest = manifestFiles.some(function (m) { return filePath.endsWith(m); });
101
- if (!isManifest) {
102
- process.exit(0);
103
- return;
104
- }
105
- const target = (toolName === 'Edit' || toolName === 'replace_file_content')
106
- ? (toolInput.new_string || toolInput.ReplacementContent || toolInput.content || '')
107
- : (toolInput.content || toolInput.CodeContent || '');
108
- const baseline = (toolName === 'Edit' || toolName === 'replace_file_content')
109
- ? (toolInput.old_string || toolInput.TargetContent || '')
110
- : '';
111
-
112
- const depPattern = /"([^"]+)"\s*:\s*"[~^>=<*]?\d/g;
113
- const newDeps = extractDeps(target, depPattern);
114
- const oldDeps = extractDeps(baseline, depPattern);
115
-
116
- added = newDeps.filter(function (d) { return oldDeps.indexOf(d) === -1; });
117
- }
51
+ const output = {
52
+ allow_tool: false,
53
+ deny_reason: reason,
54
+ hookSpecificOutput: {
55
+ hookEventName: 'PreToolUse',
56
+ permissionDecision: 'deny',
57
+ permissionDecisionReason: reason,
58
+ },
59
+ };
60
+ process.stdout.write(JSON.stringify(output) + '\n');
61
+ process.exit(2);
62
+ }
63
+
64
+ function handlePayload(data) {
65
+ const isAntigravity = isAntigravityPayload(data);
66
+ if (!data || typeof data !== 'object') {
67
+ emitAllow(isAntigravity);
68
+ return;
69
+ }
70
+
71
+ const workspaceRoot = resolveWorkspaceRoot(data);
72
+ const toolName = data.toolCall ? (data.toolCall.name || '') : (data.tool_name || data.toolName || '');
73
+ const toolInput = data.toolCall ? (data.toolCall.args || {}) : (data.tool_input || data.toolInput || {});
74
+ let added = [];
75
+
76
+ const isTerminal = ['Bash', 'run_command', 'run_shell_command', 'terminal', 'execute_command'].includes(toolName);
77
+ const normalizedEdit = !isTerminal ? normalizeEditToolCall(toolName, toolInput) : null;
78
+
79
+ if (isTerminal) {
80
+ const command = toolInput.command || toolInput.CommandLine || toolInput.cmd || toolInput.commandLine || '';
118
81
 
119
- if (added.length === 0) {
120
- process.exit(0);
82
+ if (isGitCommitOrPush(command)) {
83
+ const reason = isAntigravity
84
+ ? '[ASC Gate] Confirm git commit/push was explicitly requested by the user this turn.'
85
+ : '[ASC Hard-Block] git commit/push detected. Never run git commit, git push, or git push --force unless the user explicitly requests it this turn.';
86
+ emitGateDecision('force_ask', reason, isAntigravity);
121
87
  return;
122
88
  }
123
89
 
124
- const allowlist = loadAllowlist();
125
- const notAllowed = added.filter(function (dep) {
126
- return !allowlist.has(dep);
127
- });
128
- const forbidden = notAllowed.filter(function (dep) {
129
- return knownDuplicates.has(dep) || reviewDuplicates.has(dep);
130
- });
131
-
132
- const blocked = forbidden.filter(function (dep) {
133
- return knownDuplicates.has(dep);
134
- });
135
- const reviewOnly = forbidden.filter(function (dep) {
136
- return !knownDuplicates.has(dep) && reviewDuplicates.has(dep);
137
- });
138
-
139
- if (blocked.length > 0) {
140
- const reason = '[ASC Hard-Block] Dependency ' + blocked.map(function (d) { return "'" + d + "'"; }).join(', ')
141
- + ' duplicates standard library or native platform features. '
142
- + 'Ladder step 3: use stdlib/native features instead, or add to .asc/dependency-allowlist.json to override.';
143
-
144
- let output;
145
- if (isAntigravity) {
146
- output = {
147
- decision: "deny",
148
- reason: reason
149
- };
150
- } else {
151
- output = {
152
- allow_tool: false,
153
- deny_reason: reason,
154
- hookSpecificOutput: {
155
- hookEventName: 'PreToolUse',
156
- permissionDecision: 'deny',
157
- permissionDecisionReason: reason
158
- }
159
- };
160
- }
161
- process.stdout.write(JSON.stringify(output) + '\n');
162
- process.exit(2);
90
+ const allowlist = loadAllowlist(workspaceRoot);
91
+
92
+ const cloneCheck = checkGitClone(command, allowlist);
93
+ if (cloneCheck && cloneCheck.blocked) {
94
+ emitGateDecision('deny', cloneCheck.reason, isAntigravity);
163
95
  return;
164
96
  }
165
97
 
166
- if (reviewOnly.length > 0) {
167
- // Context-dependent packages: nudge with the specific reason instead of blocking.
168
- // A PreToolUse deny is the only injection channel on some hosts, so deny with a
169
- // clear "allowed after review" message pointing at the allowlist escape hatch.
170
- const reasons = reviewOnly.map(function (dep) {
171
- return "'" + dep + "': " + (reviewReasons[dep] || 'native alternative exists; confirm the package-specific need');
172
- }).join(' ');
173
- const reason = '[ASC Review] Dependency ' + reviewOnly.join(', ')
174
- + ' may duplicate stdlib/native features. ' + reasons
175
- + ' If the need is real, add it to .asc/dependency-allowlist.json and retry.';
176
-
177
- let output;
178
- if (isAntigravity) {
179
- output = {
180
- decision: "deny",
181
- reason: reason
182
- };
183
- } else {
184
- output = {
185
- allow_tool: false,
186
- deny_reason: reason,
187
- hookSpecificOutput: {
188
- hookEventName: 'PreToolUse',
189
- permissionDecision: 'deny',
190
- permissionDecisionReason: reason
191
- }
192
- };
193
- }
194
- process.stdout.write(JSON.stringify(output) + '\n');
195
- process.exit(2);
98
+ added = extractCommandDeps(command);
99
+ } else if (normalizedEdit) {
100
+ const filePath = normalizedEdit.filePath || '';
101
+ const manifestFiles = ['package.json', 'requirements.txt', 'pyproject.toml', 'go.mod', 'Cargo.toml', 'Gemfile'];
102
+ const isManifest = manifestFiles.some(function (m) { return filePath.endsWith(m); });
103
+ if (!isManifest) {
104
+ emitAllow(isAntigravity);
196
105
  return;
197
106
  }
107
+
108
+ const depPattern = /"([^"]+)"\s*:\s*"[~^>=<*]?\d/g;
109
+ const newDeps = extractDeps(normalizedEdit.newContent, depPattern);
110
+ const oldDeps = extractDeps(normalizedEdit.oldContent, depPattern);
111
+
112
+ added = newDeps.filter(function (d) { return oldDeps.indexOf(d) === -1; });
113
+ }
114
+
115
+ if (added.length === 0) {
116
+ emitAllow(isAntigravity);
117
+ return;
118
+ }
119
+
120
+ const allowlist = loadAllowlist(workspaceRoot);
121
+ const notAllowed = added.filter(function (dep) {
122
+ return !allowlist.has(dep);
123
+ });
124
+ const forbidden = notAllowed.filter(function (dep) {
125
+ return knownDuplicates.has(dep) || reviewDuplicates.has(dep);
126
+ });
127
+
128
+ const blocked = forbidden.filter(function (dep) {
129
+ return knownDuplicates.has(dep);
130
+ });
131
+ const reviewOnly = forbidden.filter(function (dep) {
132
+ return !knownDuplicates.has(dep) && reviewDuplicates.has(dep);
133
+ });
134
+
135
+ if (blocked.length > 0) {
136
+ const reason = '[ASC Hard-Block] Dependency ' + blocked.map(function (d) { return "'" + d + "'"; }).join(', ')
137
+ + ' duplicates standard library or native platform features. '
138
+ + 'Ladder step 3: use stdlib/native features instead, or add to .asc/dependency-allowlist.json to override.';
139
+ emitGateDecision('deny', reason, isAntigravity);
140
+ return;
141
+ }
142
+
143
+ if (reviewOnly.length > 0) {
144
+ const reasons = reviewOnly.map(function (dep) {
145
+ return "'" + dep + "': " + (reviewReasons[dep] || 'native alternative exists; confirm the package-specific need');
146
+ }).join(' ');
147
+ const reason = '[ASC Review] Dependency ' + reviewOnly.join(', ')
148
+ + ' may duplicate stdlib/native features. ' + reasons
149
+ + ' If the need is real, add it to .asc/dependency-allowlist.json and retry.';
150
+ emitGateDecision('deny', reason, isAntigravity);
151
+ return;
152
+ }
153
+
154
+ emitAllow(isAntigravity);
155
+ }
156
+
157
+ if (process.stdin.isTTY) {
158
+ process.exit(0);
159
+ }
160
+
161
+ let handled = false;
162
+ let inputBuffer = '';
163
+ const stdinTimer = setTimeout(function () {
164
+ if (!handled) {
165
+ handled = true;
166
+ process.exit(0);
167
+ }
168
+ }, 3000);
169
+
170
+ process.stdin.setEncoding('utf8');
171
+ process.stdin.on('data', function (chunk) {
172
+ inputBuffer += chunk;
173
+ try {
174
+ const data = JSON.parse(inputBuffer);
175
+ if (!handled) {
176
+ handled = true;
177
+ clearTimeout(stdinTimer);
178
+ handlePayload(data);
179
+ }
180
+ } catch (_) {
181
+ // Wait for remaining chunks
182
+ }
183
+ });
184
+
185
+ process.stdin.on('end', function () {
186
+ if (handled) return;
187
+ handled = true;
188
+ clearTimeout(stdinTimer);
189
+ if (!inputBuffer.trim()) {
190
+ process.exit(0);
191
+ return;
192
+ }
193
+ try {
194
+ handlePayload(JSON.parse(inputBuffer));
195
+ } catch (_) {
198
196
  process.exit(0);
199
- } catch (e) {
200
- // incomplete json, wait for more chunks
201
197
  }
202
198
  });
203
199
 
204
200
  function isGitCommitOrPush(command) {
205
201
  if (!command || typeof command !== 'string') return false;
206
202
 
207
- // Split multi-command strings (chained by &&, ||, ;, \n, |)
208
203
  const subcommands = command.split(/[\r\n;&|]+/);
209
204
 
210
205
  for (let i = 0; i < subcommands.length; i++) {
211
206
  const sub = subcommands[i].trim();
212
207
  if (!sub || !/\b(?:git|git\.exe)\b/i.test(sub)) continue;
213
208
 
214
- // Tokenize sub-command handling quotes
215
209
  const tokens = sub.match(/(?:[^\s"']+|"[^"]*"|'[^']*')+/g) || [];
216
210
  let gitIdx = -1;
217
211
 
@@ -225,21 +219,17 @@ function isGitCommitOrPush(command) {
225
219
 
226
220
  if (gitIdx === -1) continue;
227
221
 
228
- // Parse tokens after 'git' to find the git subcommand
229
222
  for (let k = gitIdx + 1; k < tokens.length; k++) {
230
223
  let tok = tokens[k].replace(/^['"]|['"]$/g, '');
231
224
  if (!tok) continue;
232
225
 
233
- // If token is a flag (-C, --git-dir, -c, --no-pager, etc.)
234
226
  if (tok.startsWith('-')) {
235
- // Skip standalone argument for options expecting a value after whitespace
236
227
  if (['-C', '-c', '--git-dir', '--work-tree', '--namespace', '--exec-path'].includes(tok)) {
237
228
  k++;
238
229
  }
239
230
  continue;
240
231
  }
241
232
 
242
- // First non-flag token after git global options is the git subcommand
243
233
  const gitSub = tok.toLowerCase();
244
234
  if (gitSub === 'commit' || gitSub === 'push' || gitSub === 'commit-tree') {
245
235
  return true;
@@ -255,14 +245,12 @@ function isGitCommitOrPush(command) {
255
245
  function checkGitClone(command, allowlist) {
256
246
  if (!command || typeof command !== 'string') return null;
257
247
 
258
- // Split multi-command strings (chained by &&, ||, ;, \n, |)
259
248
  const subcommands = command.split(/[\r\n;&|]+/);
260
249
 
261
250
  for (let i = 0; i < subcommands.length; i++) {
262
251
  const sub = subcommands[i].trim();
263
252
  if (!sub || !/\b(?:git|git\.exe)\b/i.test(sub)) continue;
264
253
 
265
- // Tokenize sub-command handling quotes
266
254
  const tokens = sub.match(/(?:[^\s"']+|"[^"]*"|'[^']*')+/g) || [];
267
255
  let gitIdx = -1;
268
256
 
@@ -276,7 +264,6 @@ function checkGitClone(command, allowlist) {
276
264
 
277
265
  if (gitIdx === -1) continue;
278
266
 
279
- // Parse tokens after 'git' to find the git subcommand
280
267
  let isClone = false;
281
268
  let nextIdx = gitIdx + 1;
282
269
  for (; nextIdx < tokens.length; nextIdx++) {
@@ -299,14 +286,12 @@ function checkGitClone(command, allowlist) {
299
286
 
300
287
  if (!isClone) continue;
301
288
 
302
- // Find the target repository token (skip clone options like --depth, -b, --branch, etc.)
303
289
  let repoArg = '';
304
290
  for (let k = nextIdx; k < tokens.length; k++) {
305
291
  let tok = tokens[k].replace(/^['"]|['"]$/g, '');
306
292
  if (!tok) continue;
307
293
 
308
294
  if (tok.startsWith('-')) {
309
- // Flags that take an argument after whitespace
310
295
  if (['-b', '--branch', '--depth', '--origin', '-o', '--reference', '--filter', '-u', '--upload-pack', '--template'].includes(tok)) {
311
296
  k++;
312
297
  }
@@ -319,19 +304,16 @@ function checkGitClone(command, allowlist) {
319
304
 
320
305
  if (!repoArg) continue;
321
306
 
322
- // 1. Block insecure protocols (git://, http://)
323
307
  if (repoArg.startsWith('git://') || repoArg.startsWith('http://')) {
324
308
  return {
325
309
  blocked: true,
326
310
  reason: '[ASC Hard-Block] Insecure git clone protocol detected (' + repoArg + '). '
327
- + 'Use secure HTTPS or SSH (git@) with verified TLS.'
311
+ + 'Use secure HTTPS or SSH (git@) with verified TLS.',
328
312
  };
329
313
  }
330
314
 
331
- // 2. Allow checking against allowlist (exact URL, owner/repo, or repo name)
332
315
  const normalized = repoArg.toLowerCase().replace(/\.git$/, '');
333
316
 
334
- // Extract owner/repo if possible (e.g. from https://github.com/owner/repo or git@github.com:owner/repo)
335
317
  let ownerRepo = '';
336
318
  const ghMatch = repoArg.match(/(?:github\.com|gitlab\.com|bitbucket\.org)[:\/]([^\/\s]+\/[^\/\s#?]+)/i);
337
319
  if (ghMatch) {
@@ -348,7 +330,7 @@ function checkGitClone(command, allowlist) {
348
330
  blocked: true,
349
331
  reason: '[ASC Hard-Block] Unverified git clone target: \'' + repoArg + '\'. '
350
332
  + 'Verify repository provenance against trusted sources (mitigate HalluSquatting) '
351
- + 'or add to allowedRepositories in .asc/dependency-allowlist.json to allow.'
333
+ + 'or add to allowedRepositories in .asc/dependency-allowlist.json to allow.',
352
334
  };
353
335
  }
354
336
  }
@@ -366,11 +348,6 @@ function extractDeps(text, pattern) {
366
348
  }
367
349
 
368
350
  function extractCommandDeps(command) {
369
- // JS: npm/yarn/pnpm/bun/ascx install/add
370
- // Python: pip/pip3/uv install, poetry add
371
- // Go: go get
372
- // Rust: cargo add
373
- // Ruby: gem install, bundle add
374
351
  const installRegex = /(?:npm|yarn|pnpm|bun|ascx|pip3?|uv|poetry|cargo|gem|bundle)\s+(?:install|i|add|get)(?:\s+[^\s]+)*/i;
375
352
  if (!installRegex.test(command)) return [];
376
353
 
@@ -380,11 +357,10 @@ function extractCommandDeps(command) {
380
357
 
381
358
  const rawArgs = parts.slice(pkgIndex + 1);
382
359
  const deps = [];
383
- for (let arg of rawArgs) {
384
- if (arg.startsWith('-')) continue; // Skip flags like -D, --save-dev
360
+ for (const arg of rawArgs) {
361
+ if (arg.startsWith('-')) continue;
385
362
  if (arg.startsWith('http://') || arg.startsWith('https://') || arg.startsWith('git+')) continue;
386
363
 
387
- // Remove scope/version if any, e.g. lodash@^4.17 -> lodash, @types/node@18 -> @types/node
388
364
  let name = arg;
389
365
  if (name.startsWith('@')) {
390
366
  const slashIndex = name.indexOf('/');
@@ -401,12 +377,13 @@ function extractCommandDeps(command) {
401
377
  return deps;
402
378
  }
403
379
 
404
- function loadAllowlist() {
380
+ function loadAllowlist(workspaceRoot) {
381
+ const root = workspaceRoot || process.cwd();
405
382
  const allowed = new Set();
406
383
  allowed.repositories = new Set();
407
384
  const candidates = [
408
- path.join(process.cwd(), '.asc', 'dependency-allowlist.json'),
409
- path.join(process.cwd(), '.agents', 'dependency-allowlist.json')
385
+ path.join(root, '.asc', 'dependency-allowlist.json'),
386
+ path.join(root, '.agents', 'dependency-allowlist.json'),
410
387
  ];
411
388
 
412
389
  for (let i = 0; i < candidates.length; i++) {
@@ -1,45 +1,58 @@
1
1
  #!/usr/bin/env node
2
- // Agentic Senior Core — PreInvocation session pulse hook (Antigravity-specific)
3
- // Antigravity has no SessionStart event. This fires on the FIRST model call
4
- // (invocationNum === 0) to inject AGENTS.md into the conversation — the same
5
- // role session-start.js fills for Claude Code / Codex / Copilot via their
6
- // real SessionStart hook.
7
- // Does NOT replace session-start.js — that file still serves other hosts.
2
+ // Agentic Senior Core — Optional SessionStart / PreInvocation pulse helper.
3
+ // Note: Native Antigravity plugins already load rules/agentic-senior-core.md via always_on rules,
4
+ // so this helper safely emits {} unless standalone rule injection is explicitly requested.
8
5
 
9
6
  const fs = require('fs');
10
7
  const path = require('path');
11
8
 
12
9
  const pluginRoot = process.env.CLAUDE_PLUGIN_ROOT || path.resolve(__dirname, '..');
13
10
 
11
+ if (process.stdin.isTTY) {
12
+ process.stdout.write('{}\n');
13
+ process.exit(0);
14
+ }
15
+
16
+ let handled = false;
14
17
  let inputBuffer = '';
15
- process.stdin.setEncoding('utf8');
16
- process.stdin.on('data', chunk => {
17
- inputBuffer += chunk;
18
- try {
19
- const data = JSON.parse(inputBuffer);
20
-
21
- // Only fire on the very first model call of the session
22
- if (data.invocationNum !== 0) {
23
- process.stdout.write(JSON.stringify({}) + '\n');
24
- process.exit(0);
25
- return;
26
- }
27
-
28
- const agentsPath = path.join(pluginRoot, 'rules', 'agentic-senior-core.md');
29
- let content;
30
- try {
31
- content = fs.readFileSync(agentsPath, 'utf8');
32
- } catch (_) {
33
- process.stdout.write(JSON.stringify({}) + '\n');
34
- process.exit(0);
35
- return;
36
- }
37
-
38
- process.stdout.write(JSON.stringify({
39
- injectSteps: [{ ephemeralMessage: content }],
40
- }) + '\n');
41
- process.exit(0);
42
- } catch (e) {
43
- // wait for more chunks
18
+
19
+ function finish(data) {
20
+ if (handled) return;
21
+ handled = true;
22
+ clearTimeout(timer);
23
+
24
+ try {
25
+ if (!data || data.invocationNum !== 0 || !process.env.ASC_STANDALONE_PULSE) {
26
+ process.stdout.write('{}\n');
27
+ process.exit(0);
28
+ return;
44
29
  }
30
+
31
+ const agentsPath = path.join(pluginRoot, 'rules', 'agentic-senior-core.md');
32
+ const content = fs.readFileSync(agentsPath, 'utf8');
33
+ process.stdout.write(JSON.stringify({
34
+ injectSteps: [{ ephemeralMessage: content }],
35
+ }) + '\n');
36
+ } catch (_) {
37
+ process.stdout.write('{}\n');
38
+ }
39
+ process.exit(0);
40
+ }
41
+
42
+ const timer = setTimeout(function () { finish(null); }, 2000);
43
+
44
+ process.stdin.setEncoding('utf8');
45
+ process.stdin.on('data', function (chunk) {
46
+ inputBuffer += chunk;
47
+ try {
48
+ finish(JSON.parse(inputBuffer));
49
+ } catch (_) {}
50
+ });
51
+
52
+ process.stdin.on('end', function () {
53
+ let parsed = null;
54
+ if (inputBuffer.trim()) {
55
+ try { parsed = JSON.parse(inputBuffer); } catch (_) {}
56
+ }
57
+ finish(parsed);
45
58
  });