@event4u/agent-config 14.17.0 → 14.19.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.augment-plugin/marketplace.json +3 -3
- package/.augment-plugin/plugin.json +1 -1
- package/.claude-plugin/marketplace.json +1 -1
- package/CHANGELOG.md +207 -77
- package/README.md +72 -150
- package/dist/agent-src/README.md +1 -1
- package/dist/agent-src/commands/agent-handoff.md +2 -2
- package/dist/agent-src/commands/pr/merge.md +35 -44
- package/dist/agent-src/commands/review/changes.md +19 -19
- package/dist/agent-src/commands/review.md +2 -2
- package/dist/agent-src/contexts/execution/host-capability-manifest.md +30 -3
- package/dist/agent-src/rules/autonomous-execution.md +1 -1
- package/dist/agent-src/rules/git-history-discipline.md +2 -2
- package/dist/agent-src/rules/guidelines.md +1 -1
- package/dist/agent-src/rules/session-canary.md +2 -2
- package/dist/agent-src/rules/token-efficiency.md +1 -1
- package/dist/agent-src/skills/accessibility-auditor/SKILL.md +2 -0
- package/dist/agent-src/skills/adversarial-review/SKILL.md +0 -8
- package/dist/agent-src/skills/ai-code-blindspots/SKILL.md +27 -1
- package/dist/agent-src/skills/canvas-design/SKILL.md +1 -1
- package/dist/agent-src/skills/code-intelligence/SKILL.md +60 -12
- package/dist/agent-src/skills/complexity-first-planning/SKILL.md +27 -0
- package/dist/agent-src/skills/design-intelligence/SKILL.md +1 -1
- package/dist/agent-src/skills/design-intelligence/data/app-interface.csv +1 -1
- package/dist/agent-src/skills/design-intelligence/data/motion.csv +17 -17
- package/dist/agent-src/skills/design-intelligence/data/ux-guidelines.csv +2 -2
- package/dist/agent-src/skills/design-intelligence/references/context-and-registers.md +17 -0
- package/dist/agent-src/skills/design-intelligence/references/design-rules-checklist.md +4 -4
- package/dist/agent-src/skills/design-review/SKILL.md +3 -1
- package/dist/agent-src/skills/design-variations/SKILL.md +1 -1
- package/dist/agent-src/skills/existing-ui-audit/SKILL.md +1 -1
- package/dist/agent-src/skills/fe-design/SKILL.md +32 -5
- package/dist/agent-src/skills/fe-design/references/craft-floor.md +7 -0
- package/dist/agent-src/skills/fe-design/references/design-patterns.md +47 -2
- package/dist/agent-src/skills/feature-planning/SKILL.md +34 -0
- package/dist/agent-src/skills/git-workflow/SKILL.md +37 -12
- package/dist/agent-src/skills/git-workflow/references/push-closes-its-loop.md +176 -0
- package/dist/agent-src/skills/laravel-mail/SKILL.md +44 -0
- package/dist/agent-src/skills/laravel-scheduling/SKILL.md +3 -1
- package/dist/agent-src/skills/project-analyzer/SKILL.md +0 -6
- package/dist/agent-src/skills/react-native-setup/SKILL.md +3 -3
- package/dist/agent-src/skills/react-shadcn-ui/SKILL.md +1 -1
- package/dist/agent-src/skills/sequential-thinking/SKILL.md +18 -8
- package/dist/agent-src/skills/tailwind-engineer/SKILL.md +1 -1
- package/dist/agent-src/skills/testing-anti-patterns/SKILL.md +40 -0
- package/dist/agent-src/skills/ui-apply-generic/SKILL.md +1 -1
- package/dist/agent-src/skills/ui-component-architect/SKILL.md +1 -1
- package/dist/agent-src/skills/using-git-worktrees/SKILL.md +29 -2
- package/dist/agent-src/skills/verify-repair-loop/SKILL.md +53 -8
- package/dist/agent-src/skills/worktree-lifecycle/SKILL.md +3 -1
- package/dist/agent-src/templates/agents/agent-project-settings.example.yml +1 -1
- package/dist/cli/commands/recordTriggerEval.js +2 -2
- package/dist/cli/main.js +1 -1
- package/dist/cli-delegate/{chunk-AKJGUQNO.js → chunk-GZTMRYCN.js} +405 -341
- package/dist/cli-delegate/{chunk-7RXIKF62.js → chunk-IDHUQIXP.js} +283 -132
- package/dist/cli-delegate/cmd_adr_effective.js +318 -81
- package/dist/cli-delegate/cmd_conformance.js +749 -669
- package/dist/cli-delegate/cmd_doctor.js +1 -1
- package/dist/cli-delegate/cmd_export.js +1 -1
- package/dist/cli-delegate/cmd_handoff.js +1 -1
- package/dist/cli-delegate/cmd_prune.js +1 -1
- package/dist/cli-delegate/cmd_route_audit.js +1 -1
- package/dist/cli-delegate/cmd_settings_migrate.js +1 -1
- package/dist/cli-delegate/cmd_sync.js +1 -1
- package/dist/cli-delegate/cmd_uninstall.js +1 -1
- package/dist/cli-delegate/cmd_validate.js +1 -1
- package/dist/discovery/deprecation-report.md +1 -1
- package/dist/discovery/discovery-manifest.json +39 -38
- package/dist/discovery/discovery-manifest.json.sha256 +1 -1
- package/dist/discovery/discovery-manifest.summary.md +1 -1
- package/dist/discovery/dormancy-report.md +1 -1
- package/dist/discovery/orphan-report.md +1 -1
- package/dist/discovery/packs.json +2 -2
- package/dist/discovery/trust-report.md +1 -1
- package/dist/discovery/workspaces.json +2 -2
- package/dist/hooks/dispatch.js +2764 -2439
- package/dist/install/install.mjs +588 -527
- package/dist/mcp/registry-manifest.json +2 -2
- package/dist/mcp/server.json +2 -2
- package/dist/mcp/server.mjs +1381 -1138
- package/dist/scripts/_lib/model_tier.js +1 -1
- package/dist/shared/settingsConsent.js +1 -1
- package/docs/guidelines/agent-infra/evaluator-independence-mechanics.md +1 -1
- package/docs/guidelines/agent-infra/roadmap-progress-mechanics.md +1 -1
- package/docs/guidelines/design-antipatterns.md +87 -64
- package/package.json +1 -1
- package/src/agent-src/README.md +1 -1
- package/src/config/ci-local-parity.yml +6 -0
- package/src/config/conformance-claim-baseline.json +9 -0
- package/src/config/gate-coverage.yml +170 -0
- package/src/config/gate-violation-baselines.json +4 -4
- package/src/config/release-gate-locality.yml +141 -0
- package/src/scripts/_cli/cmd_doctor.ts +2 -2
- package/src/scripts/_cli/cmd_handoff.ts +1 -1
- package/src/scripts/_cli/cmd_update.ts +1 -1
- package/src/scripts/_cli/cmd_versions.ts +1 -1
- package/src/scripts/_lib/adr_frontmatter.ts +44 -0
- package/src/scripts/_lib/consequence_objects.ts +152 -0
- package/src/scripts/_lib/exec_evidence.ts +7 -0
- package/src/scripts/_lib/global_deploy_inventory.ts +2 -2
- package/src/scripts/_lib/host_permission_checks.ts +251 -0
- package/src/scripts/_lib/model_tier.ts +1 -1
- package/src/scripts/_lib/obligation_frequency.ts +2 -2
- package/src/scripts/_lib/release_highlights.ts +265 -1
- package/src/scripts/_lib/release_material.ts +129 -0
- package/src/scripts/_lib/repeated_run.ts +77 -0
- package/src/scripts/_lib/runtime_journal.ts +26 -3
- package/src/scripts/_lib/runtime_wiring_checks.ts +12 -1
- package/src/scripts/_lib/session_register.ts +11 -6
- package/src/scripts/_lib/sqlite_schema.ts +22 -0
- package/src/scripts/_lib/surface.ts +86 -0
- package/src/scripts/_lib/tamper_vocabulary.ts +242 -0
- package/src/scripts/_lib/ui_authority.ts +116 -0
- package/src/scripts/_lib/unified_diff.ts +121 -0
- package/src/scripts/adr/regenerate_index.ts +54 -1
- package/src/scripts/adr_cite_check.ts +71 -15
- package/src/scripts/ai_council/clients.ts +1 -1
- package/src/scripts/autonomy_friction_corpus.ts +229 -0
- package/src/scripts/build_proof.ts +15 -2
- package/src/scripts/check_beta_review_markers.ts +141 -17
- package/src/scripts/check_enforcement_coverage.ts +1 -1
- package/src/scripts/check_enforcement_denominator.ts +121 -11
- package/src/scripts/check_finding_dispositions.ts +239 -6
- package/src/scripts/check_gate_coverage.ts +96 -0
- package/src/scripts/check_installed_hooks_fresh.ts +381 -0
- package/src/scripts/check_release_highlights.ts +99 -14
- package/src/scripts/check_skill_activation_claim.ts +179 -0
- package/src/scripts/check_suppression_hygiene.ts +11 -0
- package/src/scripts/check_trigger_evals.ts +1 -1
- package/src/scripts/code_graph/build.ts +230 -33
- package/src/scripts/code_graph/extract.ts +168 -33
- package/src/scripts/code_graph/types.ts +37 -2
- package/src/scripts/code_graph/validate.ts +12 -1
- package/src/scripts/conformance_scan.ts +45 -4
- package/src/scripts/detect_silent_catch.ts +403 -0
- package/src/scripts/detect_verification_tampering.ts +538 -0
- package/src/scripts/explain_run.ts +1 -1
- package/src/scripts/generate_capability_matrix.ts +1 -1
- package/src/scripts/git_authorization_hook.ts +228 -18
- package/src/scripts/hook_manifest.json +1 -1
- package/src/scripts/hook_manifest.yaml +47 -30
- package/src/scripts/hooks/augment-dispatcher.sh +1 -1
- package/src/scripts/hooks/block_no_verify.ts +3 -3
- package/src/scripts/hooks/category_a.ts +291 -0
- package/src/scripts/hooks/cline-dispatcher.sh +1 -1
- package/src/scripts/hooks/concern_registry.ts +2 -2
- package/src/scripts/hooks/cursor-dispatcher.sh +11 -5
- package/src/scripts/hooks/dispatch_hook.ts +17 -22
- package/src/scripts/hooks/evidence_independence.ts +21 -2
- package/src/scripts/hooks/{block_unauthorized_git.ts → git_command_classifier.ts} +175 -364
- package/src/scripts/hooks/host_lowering.ts +215 -0
- package/src/scripts/hooks/host_lowering.yaml +185 -0
- package/src/scripts/hooks/host_semantics.ts +105 -37
- package/src/scripts/hooks/journal_record_hook.ts +10 -0
- package/src/scripts/hooks/permission_gate.ts +117 -0
- package/src/scripts/hooks/push_settle_hook.ts +250 -0
- package/src/scripts/hooks/ship_diff_volume_hook.ts +1 -1
- package/src/scripts/hooks_status.ts +12 -10
- package/src/scripts/install-hooks.sh +134 -1
- package/src/scripts/install.ts +53 -82
- package/src/scripts/install_anthropic_key.sh +2 -2
- package/src/scripts/language_mirror_hook.ts +27 -5
- package/src/scripts/lint_design_antipattern_parity.ts +80 -0
- package/src/scripts/lint_evidence_artifacts.ts +45 -1
- package/src/scripts/lint_hook_manifest.ts +87 -3
- package/src/scripts/lint_motion_authority_drift.ts +731 -0
- package/src/scripts/lint_readme_size.ts +158 -12
- package/src/scripts/lint_reasoning_fixtures.ts +216 -0
- package/src/scripts/lint_skill_trigger_contradiction.ts +161 -0
- package/src/scripts/measure_sibling_naming.ts +150 -0
- package/src/scripts/redact_hook_capture.ts +1 -1
- package/src/scripts/release.ts +31 -143
- package/src/scripts/release_drill.ts +71 -1
- package/src/scripts/release_publication.ts +243 -3
- package/src/scripts/release_verify.ts +220 -0
- package/src/scripts/report_skill_activation.ts +319 -7
- package/src/scripts/schemas/review-findings.schema.json +6 -1
- package/src/scripts/schemas/skill.schema.json +12 -2
- package/src/scripts/self_review_gate.ts +10 -1
- package/src/scripts/session_canary_hook.ts +1 -1
- package/src/scripts/session_register_hook.ts +4 -3
- package/src/scripts/trigger_eval_grandfather.json +5 -1
- package/src/scripts/validate_agent_settings.ts +1 -1
- package/src/shared/settingsConsent.ts +1 -1
|
@@ -1,11 +1,28 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
1
|
/**
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
* `
|
|
7
|
-
*
|
|
8
|
-
*
|
|
2
|
+
* Git / GitHub / npm command classifier — a pure library, not a gate.
|
|
3
|
+
*
|
|
4
|
+
* WHAT THIS IS NOT, AND WHY. Until 2026-09-04 this file was the
|
|
5
|
+
* `block-unauthorized-git` `pre_tool_use` concern: it read the authorization
|
|
6
|
+
* ledger and REFUSED an operation the turn had not authorized. The owner
|
|
7
|
+
* removed that enforcement (ADR-254) after it was measured to refuse
|
|
8
|
+
* authorizations the owner had in fact given — a guardrail sentence inside an
|
|
9
|
+
* authorization ("never merge empty PRs") read as a withdrawal and erased the
|
|
10
|
+
* whole ledger, and an answer given as an option number ("1") matched no
|
|
11
|
+
* authorizing pattern at all.
|
|
12
|
+
*
|
|
13
|
+
* What survives is the half that never decided anything: the command parser and
|
|
14
|
+
* the operation vocabulary. Two callers depend on it and neither gates git —
|
|
15
|
+
* `block_no_verify.ts` needs `substitutionPayloads` so `git $(echo --no-verify)`
|
|
16
|
+
* cannot slip past the hook-bypass guard, and `conformance_scan.ts` needs
|
|
17
|
+
* `commandOp` / `BLOCK_OPS` to MEASURE how often an irreversible operation ran
|
|
18
|
+
* on an unauthorized turn. Measurement is all that is left here.
|
|
19
|
+
*
|
|
20
|
+
* `BLOCK_OPS` and `WARN_OPS` keep their names because they name the
|
|
21
|
+
* reversibility split `non-destructive-by-default` draws, and that split is
|
|
22
|
+
* still true. They no longer name an action this file takes.
|
|
23
|
+
*
|
|
24
|
+
* SEVERITY SPLIT — by reversibility, not by confidence. Retained below because
|
|
25
|
+
* it is what the two sets still mean.
|
|
9
26
|
*
|
|
10
27
|
* SEVERITY SPLIT — by reversibility, not by confidence.
|
|
11
28
|
*
|
|
@@ -56,33 +73,7 @@
|
|
|
56
73
|
* substitution in every position tried, `sh -c` and `eval` payloads, env
|
|
57
74
|
* assignment prefixes, and quotes inside the command word.
|
|
58
75
|
*/
|
|
59
|
-
import
|
|
60
|
-
import path from "node:path";
|
|
61
|
-
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
62
|
-
|
|
63
|
-
import { readHookStdin } from "./hook_stdin.js";
|
|
64
|
-
import { analyseMergeImpact, describeImpact } from "./merge_impact.js";
|
|
65
|
-
import { atomic_write_json } from "./state_io.js";
|
|
66
|
-
import {
|
|
67
|
-
consumeGrantTarget,
|
|
68
|
-
ledgerFileFor,
|
|
69
|
-
pendingFileFor,
|
|
70
|
-
PR_NUMBER_MAX,
|
|
71
|
-
readGrants,
|
|
72
|
-
STATE_FILE,
|
|
73
|
-
type GitOp,
|
|
74
|
-
type MergeGrant,
|
|
75
|
-
} from "../git_authorization_hook.js";
|
|
76
|
-
|
|
77
|
-
const EXIT_ALLOW = 0;
|
|
78
|
-
// MUST equal dispatch_hook.EXIT_BLOCK. The dispatcher's internal ladder is
|
|
79
|
-
// 0 allow / 1 block / 2 warn — NOT the 2-means-block shape a PreToolUse guard
|
|
80
|
-
// reads naturally from Claude's own native contract. This constant was 2 when
|
|
81
|
-
// this gate first shipped, so the dispatcher reduced every refusal to a WARN
|
|
82
|
-
// and the gate emitted advisory context while the operation went through.
|
|
83
|
-
// Pinned against the dispatcher's export by
|
|
84
|
-
// tests/hooks/concern_block_exit_parity.test.ts.
|
|
85
|
-
const EXIT_BLOCK = 1;
|
|
76
|
+
import { PR_NUMBER_MAX, type GitOp } from "../git_authorization_hook.js";
|
|
86
77
|
|
|
87
78
|
type JsonValue = string | number | boolean | null | JsonValue[] | { [k: string]: JsonValue };
|
|
88
79
|
type JsonObject = { [k: string]: JsonValue };
|
|
@@ -125,6 +116,22 @@ export const BLOCK_OPS: ReadonlySet<GitOp> = new Set<GitOp>([
|
|
|
125
116
|
"worktree-remove",
|
|
126
117
|
/** `-x` is the flag that takes `.env` and local certificates. */
|
|
127
118
|
"clean-ignored",
|
|
119
|
+
|
|
120
|
+
// ── The consequence vocabulary, added 2026-09-06. Every one is already
|
|
121
|
+
// never-autonomous in the `non-destructive-by-default` prose.
|
|
122
|
+
//
|
|
123
|
+
// MEMBERSHIP HERE IS A COUNTING DECISION, NOT A REFUSAL. The set's name
|
|
124
|
+
// predates ADR-254, which deleted the reader that blocked: the one consumer
|
|
125
|
+
// that acts on it now is `conformance_scan`'s after-the-fact tally, so
|
|
126
|
+
// "blocked" describes what the Hard Floor requires of the model, never what
|
|
127
|
+
// this package does at tool-call time.
|
|
128
|
+
"prod-deploy",
|
|
129
|
+
"prod-data-destroy",
|
|
130
|
+
"prod-infra-change",
|
|
131
|
+
"external-send",
|
|
132
|
+
"money-movement",
|
|
133
|
+
"trunk-force-push",
|
|
134
|
+
"out-of-scope-destruction",
|
|
128
135
|
]);
|
|
129
136
|
|
|
130
137
|
/** Recoverable operations — warned without a this-turn authorization. */
|
|
@@ -163,6 +170,17 @@ const G = "(?:-[A-Za-z-]+(?:=\\S+)?(?:\\s+\\S+)?\\s+)*";
|
|
|
163
170
|
/** Optional absolute/relative path in front of the tool word: `/usr/bin/npm`. */
|
|
164
171
|
const P = "^(?:\\S*\\/)?";
|
|
165
172
|
|
|
173
|
+
/**
|
|
174
|
+
* Tool words the consequence rows classify, as a head anchor.
|
|
175
|
+
*
|
|
176
|
+
* Kept as one constant rather than folded into each row: `commandOp` filters
|
|
177
|
+
* segments by head BEFORE consulting the table, so a row whose tool word is
|
|
178
|
+
* missing here never runs — a silent no-op that looks exactly like a pattern
|
|
179
|
+
* that did not match.
|
|
180
|
+
*/
|
|
181
|
+
const CONSEQUENCE_TOOL_HEAD =
|
|
182
|
+
/^(?:\S*\/)?(terraform|pulumi|kubectl|helm|aws|gcloud|az|vercel|netlify|flyctl|fly|wrangler|serverless|sls|eb|heroku|cap|psql|mysql|mariadb|sqlite3|mongosh|mongo|redis-cli|sendmail|msmtp|mail|mailx|curl|stripe|braintree|paypal|rm)\b/i;
|
|
183
|
+
|
|
166
184
|
/**
|
|
167
185
|
* A `gh api` call that WRITES. Read-only `gh api` is the standard way to inspect
|
|
168
186
|
* GitHub state and must never be refused.
|
|
@@ -276,6 +294,13 @@ const COMMAND_OPS: ReadonlyArray<{ op: GitOp; re: RegExp }> = [
|
|
|
276
294
|
// Above `push`, and both spellings: `--force` and `--force-with-lease`. The
|
|
277
295
|
// "safe force" is in the same class here because what it is safe against is a
|
|
278
296
|
// stale local view, not a collaborator who pushed after your fetch.
|
|
297
|
+
{
|
|
298
|
+
op: "trunk-force-push",
|
|
299
|
+
re: new RegExp(
|
|
300
|
+
`${P}git\\s+${G}push\\b[^\\n;|&]*(--force\\b|--force-with-lease\\b|\\s-f\\b)[^\\n;|&]*\\b(main|master|trunk|production|prod)\\b`,
|
|
301
|
+
"i",
|
|
302
|
+
),
|
|
303
|
+
},
|
|
279
304
|
{
|
|
280
305
|
op: "force-push",
|
|
281
306
|
re: new RegExp(`${P}git\\s+${G}push\\b[^\\n;|&]*(--force\\b|--force-with-lease\\b|\\s-f\\b)`, "i"),
|
|
@@ -305,6 +330,73 @@ const COMMAND_OPS: ReadonlyArray<{ op: GitOp; re: RegExp }> = [
|
|
|
305
330
|
},
|
|
306
331
|
{ op: "close", re: new RegExp(`${P}gh\\s+(pr|issue)\\s+close\\b`, "i") },
|
|
307
332
|
|
|
333
|
+
// ── Consequence operations, added 2026-09-06
|
|
334
|
+
// (`road-to-authorization-that-reaches-further` 2.1).
|
|
335
|
+
//
|
|
336
|
+
// ORDER: above the generic `push`/`commit` fallbacks, because
|
|
337
|
+
// `trunk-force-push` is a `git push` and the most specific pattern must win —
|
|
338
|
+
// the rule this table states at its head.
|
|
339
|
+
//
|
|
340
|
+
// These classify commands the count could not see before. They add NOTHING to
|
|
341
|
+
// any refusal path: `commandOp` and `BLOCK_OPS` have exactly one consumer
|
|
342
|
+
// that acts on them, the after-the-fact tally in `conformance_scan`, and this
|
|
343
|
+
// change does not add a second.
|
|
344
|
+
{
|
|
345
|
+
op: "prod-data-destroy",
|
|
346
|
+
re: new RegExp(
|
|
347
|
+
`${P}(psql|mysql|mariadb|sqlite3|mongosh?)\\b[^\\n]*\\b(drop\\s+(table|database|schema)|truncate|delete\\s+from)\\b` +
|
|
348
|
+
`|${P}aws\\s+s3\\s+(rm|rb)\\b` +
|
|
349
|
+
`|${P}aws\\s+(dynamodb|rds)\\s+delete-\\S+` +
|
|
350
|
+
`|${P}redis-cli\\b[^\\n]*\\bflush(all|db)\\b`,
|
|
351
|
+
"i",
|
|
352
|
+
),
|
|
353
|
+
},
|
|
354
|
+
{
|
|
355
|
+
op: "prod-infra-change",
|
|
356
|
+
re: new RegExp(
|
|
357
|
+
`${P}terraform\\s+(apply|destroy)\\b` +
|
|
358
|
+
`|${P}pulumi\\s+(up|destroy)\\b` +
|
|
359
|
+
`|${P}kubectl\\s+(apply|delete|scale|drain)\\b` +
|
|
360
|
+
`|${P}helm\\s+(install|upgrade|uninstall|rollback)\\b` +
|
|
361
|
+
`|${P}aws\\s+(iam|ec2|route53|elbv2)\\s+(create|delete|put|update|modify|attach|detach)-\\S+` +
|
|
362
|
+
`|${P}(gcloud|az)\\s+\\S+\\s+(create|delete|update)\\b`,
|
|
363
|
+
"i",
|
|
364
|
+
),
|
|
365
|
+
},
|
|
366
|
+
{
|
|
367
|
+
op: "prod-deploy",
|
|
368
|
+
re: new RegExp(
|
|
369
|
+
`${P}(vercel|netlify|flyctl|fly|wrangler|serverless|sls|eb|heroku)\\s+${G}deploy\\b` +
|
|
370
|
+
`|${P}(vercel|netlify)\\b[^\\n]*--prod\\b` +
|
|
371
|
+
`|${P}(npm|pnpm|yarn)\\s+run\\s+deploy\\b` +
|
|
372
|
+
`|${P}cap\\s+production\\s+deploy\\b`,
|
|
373
|
+
"i",
|
|
374
|
+
),
|
|
375
|
+
},
|
|
376
|
+
{
|
|
377
|
+
op: "external-send",
|
|
378
|
+
re: new RegExp(
|
|
379
|
+
`${P}(sendmail|msmtp|mailx?)\\b` +
|
|
380
|
+
`|${P}curl\\b(?=[^\\n]*(?:-X|--request)\\s+(?:POST|PUT|PATCH)\\b)(?=[^\\n]*https?:\\/\\/)`,
|
|
381
|
+
"i",
|
|
382
|
+
),
|
|
383
|
+
},
|
|
384
|
+
{
|
|
385
|
+
op: "money-movement",
|
|
386
|
+
re: new RegExp(
|
|
387
|
+
`${P}(stripe|braintree|paypal)\\s+\\S*(charge|refund|payout|transfer|payment)\\S*\\b`,
|
|
388
|
+
"i",
|
|
389
|
+
),
|
|
390
|
+
},
|
|
391
|
+
{
|
|
392
|
+
// `rm -rf` on an ABSOLUTE path or a home-relative one. Deliberately not
|
|
393
|
+
// "outside the working tree" — this function receives a command string and
|
|
394
|
+
// no cwd, so that reading is not decidable here and a classifier that
|
|
395
|
+
// guesses is worse than one that under-reports.
|
|
396
|
+
op: "out-of-scope-destruction",
|
|
397
|
+
re: new RegExp(`${P}rm\\s+(-\\S+\\s+)*-\\S*[rR]\\S*\\s+(-\\S+\\s+)*(~|/)\\S*`, "i"),
|
|
398
|
+
},
|
|
399
|
+
|
|
308
400
|
{ op: "push", re: new RegExp(`${P}git\\s+${G}push\\b`, "i") },
|
|
309
401
|
{ op: "commit", re: new RegExp(`${P}git\\s+${G}commit\\b`, "i") },
|
|
310
402
|
{ op: "branch", re: new RegExp(`${P}git\\s+${G}(checkout\\s+-b|switch\\s+-c)\\b`, "i") },
|
|
@@ -601,7 +693,14 @@ export function commandOp(command: string): GitOp | null {
|
|
|
601
693
|
// unchecked once `publish` was authorized.
|
|
602
694
|
let found: GitOp | null = null;
|
|
603
695
|
for (const seg of invokedSegments(command)) {
|
|
604
|
-
|
|
696
|
+
// Widened 2026-09-06 for the consequence vocabulary. Before this the gate
|
|
697
|
+
// admitted five tool words, so `terraform apply` and `aws s3 rm` were not
|
|
698
|
+
// merely unclassified — they could not reach the table at all, and the
|
|
699
|
+
// count read them as nothing rather than as unmeasured.
|
|
700
|
+
if (
|
|
701
|
+
!/^(?:\S*\/)?(git|gh|npm|pnpm|yarn)\b/.test(seg) &&
|
|
702
|
+
!CONSEQUENCE_TOOL_HEAD.test(seg)
|
|
703
|
+
) {
|
|
605
704
|
continue;
|
|
606
705
|
}
|
|
607
706
|
for (const { op, re } of COMMAND_OPS) {
|
|
@@ -643,343 +742,55 @@ export function mergeTargetOf(command: string): number | null {
|
|
|
643
742
|
return api ? _boundedTarget(api[1] as string) : null;
|
|
644
743
|
}
|
|
645
744
|
|
|
646
|
-
/** A target only counts inside the API's own range; anything else is no target. */
|
|
647
|
-
function _boundedTarget(digits: string): number | null {
|
|
648
|
-
const n = Number.parseInt(digits, 10);
|
|
649
|
-
return Number.isSafeInteger(n) && n > 0 && n <= PR_NUMBER_MAX ? n : null;
|
|
650
|
-
}
|
|
651
|
-
|
|
652
745
|
/**
|
|
653
|
-
*
|
|
746
|
+
* The object a consequence command acts on, or `null` when the command does not
|
|
747
|
+
* name one.
|
|
654
748
|
*
|
|
655
|
-
*
|
|
656
|
-
*
|
|
749
|
+
* The sibling of `mergeTargetOf`, generalised past pull-request numbers, and it
|
|
750
|
+
* inherits that function's fail-closed stance verbatim: a command that names no
|
|
751
|
+
* object matches no record, so the caller reports nothing rather than guessing.
|
|
752
|
+
* Under-reporting is the safe direction for a measurement; a guessed object
|
|
753
|
+
* would manufacture a violation out of a parse failure.
|
|
657
754
|
*
|
|
658
|
-
*
|
|
659
|
-
*
|
|
660
|
-
* user name this operation AND this pull request". The second question does not
|
|
661
|
-
* decay with time, because the object it names cannot drift into a different
|
|
662
|
-
* object — a force-push changes the SHA, but the grant is spent on first use, so
|
|
663
|
-
* a merge that already happened can never be replayed against a moved head.
|
|
755
|
+
* Normalised to lower case with quotes stripped, so it compares against the
|
|
756
|
+
* prose side's `extractObjects` without either end re-deriving the rule.
|
|
664
757
|
*/
|
|
665
|
-
export function
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
grants.find(
|
|
675
|
-
(g) => g.op === "pr-merge" && g.targets.includes(target) && !g.consumed.includes(target),
|
|
676
|
-
) ?? null
|
|
677
|
-
);
|
|
678
|
-
}
|
|
679
|
-
|
|
680
|
-
/**
|
|
681
|
-
* A ledger older than this is not "this turn" under any reading.
|
|
682
|
-
*
|
|
683
|
-
* **Widening this constant for a long run is forbidden practice.** On
|
|
684
|
-
* 2026-08-21 it was sed-patched to twelve times this value — six hours —
|
|
685
|
-
* behind a marker promising a revert that never came, so an autonomous PR-drain could
|
|
686
|
-
* merge past the 30-minute bound. The widening was committed to the trunk and
|
|
687
|
-
* left there. It is a twelvefold expansion of the authorization lifetime on
|
|
688
|
-
* the guard that gates `pr-merge`, which is a `BLOCK_OPS` member precisely
|
|
689
|
-
* because it is irreversible.
|
|
690
|
-
*
|
|
691
|
-
* Neither the widened expression nor the marker text is written out here: the
|
|
692
|
-
* obvious regression check for either is a grep, and a comment reciting the
|
|
693
|
-
* literal makes that grep match the guard's own prose on a clean tree.
|
|
694
|
-
*
|
|
695
|
-
* The supported answer to "my run is longer than the window" is that the run
|
|
696
|
-
* STOPS and REPORTS at expiry and the user re-authorizes — never that the
|
|
697
|
-
* window grows. The agent never edits this constant, this file, or the built
|
|
698
|
-
* bundle; `dist/hooks/dispatch.js` is what actually executes, and a source
|
|
699
|
-
* edit without a rebuild is silently inert.
|
|
700
|
-
*
|
|
701
|
-
* **ADR-251 (2026-08-30) decided this question and KEPT the value.** An AI
|
|
702
|
-
* council converged 2/2 on a different authorization SHAPE rather than a wider
|
|
703
|
-
* window, against the measured pressure that 7 of the 10 most recent sessions
|
|
704
|
-
* of this package outlive a 30-minute window (median span 3.1 h) and that the
|
|
705
|
-
* constant has been hand-widened twice. `LEDGER_MAX_AGE_MS` is unchanged at 30
|
|
706
|
-
* minutes and raising it remains forbidden — that transition is owner-reserved,
|
|
707
|
-
* and this record did not take it.
|
|
708
|
-
*
|
|
709
|
-
* What the record DID change is the behaviour at expiry: the run PAUSES,
|
|
710
|
-
* reports the remaining work, and ASKS for re-authorization, instead of ending.
|
|
711
|
-
* The user's reply renews this ledger through the path that already exists —
|
|
712
|
-
* `git_authorization_hook.ts` rewrites the session's ledger with a fresh
|
|
713
|
-
* `detected_at` on every human-typed prompt (`:512-537`), so a mid-run reply
|
|
714
|
-
* carrying an authorization phrase resets the age this constant is compared
|
|
715
|
-
* against. No new machinery, and the signal stays agent-unforgeable.
|
|
716
|
-
*
|
|
717
|
-
* See `docs/decisions/ADR-251-authorization-window-shape-not-width.md`.
|
|
718
|
-
*/
|
|
719
|
-
export const LEDGER_MAX_AGE_MS = 30 * 60 * 1000;
|
|
720
|
-
|
|
721
|
-
/**
|
|
722
|
-
* Load the ledger, and refuse one that does not belong to THIS session and
|
|
723
|
-
* THIS turn.
|
|
724
|
-
*
|
|
725
|
-
* The header claims the gate answers "did the user authorize this THIS turn?".
|
|
726
|
-
* Before this check it answered "did anyone, in any session, ever authorize
|
|
727
|
-
* it?" — the file records `session_id` and `detected_at` and neither was read.
|
|
728
|
-
* Two concurrent worktrees share one state root, so session A typing
|
|
729
|
-
* `mach den release` authorized session B.
|
|
730
|
-
*/
|
|
731
|
-
function _loadLedger(
|
|
732
|
-
consumer_root: string,
|
|
733
|
-
session_id: string,
|
|
734
|
-
now: number,
|
|
735
|
-
): { authorized: Set<GitOp>; present: boolean; grants: MergeGrant[] } {
|
|
736
|
-
// Grants are read on their OWN path, and the separation is load-bearing.
|
|
737
|
-
// `_readLedgerFile` below discards the entire ledger once `detected_at` is
|
|
738
|
-
// older than `LEDGER_MAX_AGE_MS`, so folding grants into its return value
|
|
739
|
-
// would let the clock silently kill them too — the change would ship, look
|
|
740
|
-
// correct, and do nothing after thirty minutes, with no test going red.
|
|
741
|
-
// `readGrants` performs the session check and no age check, which is exactly
|
|
742
|
-
// the contract: object identity, not recency (ADR-252).
|
|
743
|
-
const grants = readGrants(consumer_root, session_id);
|
|
744
|
-
// This session's own ledger first; the flat legacy file only as a fallback,
|
|
745
|
-
// so a ledger written by an older build still counts. Reading the flat file
|
|
746
|
-
// FIRST was the defect: any concurrent session in the repo overwrites it, and
|
|
747
|
-
// the session check below then discarded a real authorization as foreign.
|
|
748
|
-
const candidates = [ledgerFileFor(session_id)];
|
|
749
|
-
if (!candidates.includes(STATE_FILE)) {
|
|
750
|
-
candidates.push(STATE_FILE);
|
|
751
|
-
}
|
|
752
|
-
for (const rel of candidates) {
|
|
753
|
-
const found = _readLedgerFile(path.join(consumer_root, rel), session_id, now);
|
|
754
|
-
if (found !== null) {
|
|
755
|
-
return { ...found, grants };
|
|
756
|
-
}
|
|
757
|
-
}
|
|
758
|
-
return { authorized: new Set<GitOp>(), present: false, grants };
|
|
759
|
-
}
|
|
760
|
-
|
|
761
|
-
/**
|
|
762
|
-
* Read one ledger file. `null` means "this file does not answer the question" —
|
|
763
|
-
* absent, malformed, foreign session, or expired — so the caller may try the
|
|
764
|
-
* next candidate. A parsed, in-session, fresh ledger answers definitively.
|
|
765
|
-
*/
|
|
766
|
-
function _readLedgerFile(
|
|
767
|
-
file: string,
|
|
768
|
-
session_id: string,
|
|
769
|
-
now: number,
|
|
770
|
-
): { authorized: Set<GitOp>; present: boolean } | null {
|
|
771
|
-
try {
|
|
772
|
-
const raw = fs.readFileSync(file, "utf8");
|
|
773
|
-
const decoded = JSON.parse(raw) as unknown;
|
|
774
|
-
if (_isObject(decoded) && Array.isArray(decoded["authorized"])) {
|
|
775
|
-
const ledgerSession = typeof decoded["session_id"] === "string" ? decoded["session_id"] : "";
|
|
776
|
-
// A ledger from a different session is another conversation's consent.
|
|
777
|
-
if (session_id && ledgerSession && ledgerSession !== session_id) {
|
|
778
|
-
return null;
|
|
779
|
-
}
|
|
780
|
-
const at = Date.parse(String(decoded["detected_at"] ?? ""));
|
|
781
|
-
if (Number.isFinite(at) && now - at > LEDGER_MAX_AGE_MS) {
|
|
782
|
-
return null;
|
|
783
|
-
}
|
|
784
|
-
return {
|
|
785
|
-
authorized: new Set((decoded["authorized"] as string[]).filter(Boolean) as GitOp[]),
|
|
786
|
-
present: true,
|
|
787
|
-
};
|
|
758
|
+
export function commandObject(op: GitOp, command: string): string | null {
|
|
759
|
+
const patterns = COMMAND_OBJECTS[op];
|
|
760
|
+
if (!patterns) return null;
|
|
761
|
+
for (const re of patterns) {
|
|
762
|
+
const m = new RegExp(re.source, re.flags.replace("g", "")).exec(command);
|
|
763
|
+
const raw = m?.[1];
|
|
764
|
+
if (raw) {
|
|
765
|
+
const value = raw.trim().replace(/^["'`]+|["'`]+$/g, "").replace(/[.,;:]+$/, "").toLowerCase();
|
|
766
|
+
if (value) return value;
|
|
788
767
|
}
|
|
789
|
-
} catch {
|
|
790
|
-
/* fall through */
|
|
791
768
|
}
|
|
792
769
|
return null;
|
|
793
770
|
}
|
|
794
771
|
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
*/
|
|
815
|
-
consumedTarget?: number;
|
|
816
|
-
}
|
|
817
|
-
|
|
818
|
-
export function decide(
|
|
819
|
-
command: string | null,
|
|
820
|
-
ledger: { authorized: Set<GitOp>; present: boolean; grants?: MergeGrant[] },
|
|
821
|
-
impact?: string,
|
|
822
|
-
): Decision {
|
|
823
|
-
if (command === null) {
|
|
824
|
-
return { exit: EXIT_ALLOW, stdout: "", stderr: "" };
|
|
825
|
-
}
|
|
826
|
-
const op = commandOp(command);
|
|
827
|
-
if (op === null || ledger.authorized.has(op)) {
|
|
828
|
-
return { exit: EXIT_ALLOW, stdout: "", stderr: "" };
|
|
829
|
-
}
|
|
830
|
-
|
|
831
|
-
// A standing grant is the SECOND way an operation can be authorized, and it
|
|
832
|
-
// is strictly narrower than the first rather than an escape from it. Only
|
|
833
|
-
// `pr-merge` can hold one, the command must name its pull request, and that
|
|
834
|
-
// number must sit unspent in a set the user's own sentence froze. An unnamed
|
|
835
|
-
// target matches nothing and a spent target matches nothing, so every path
|
|
836
|
-
// that is not exactly "the user named this PR and it has not been merged yet"
|
|
837
|
-
// falls through to the clock-bound decision below unchanged (ADR-252).
|
|
838
|
-
const target = mergeTargetOf(command);
|
|
839
|
-
if (grantCovers(ledger.grants ?? [], op, target) && target !== null) {
|
|
840
|
-
return { exit: EXIT_ALLOW, stdout: "", stderr: "", consumedTarget: target };
|
|
841
|
-
}
|
|
842
|
-
|
|
843
|
-
const shown = command.length > 120 ? `${command.slice(0, 117)}…` : command;
|
|
844
|
-
|
|
845
|
-
if (BLOCK_OPS.has(op)) {
|
|
846
|
-
// STAGE 2. The refusal stands either way — what the scan changes is whether
|
|
847
|
-
// the user has to go read the diff to answer it. `impact` is injected by
|
|
848
|
-
// `run`, never computed here, so `decide` stays pure and testable without a
|
|
849
|
-
// repository.
|
|
850
|
-
const scan = op === "pr-merge" && target !== null && impact ? ` ${impact}` : "";
|
|
851
|
-
const reason =
|
|
852
|
-
`Blocked: \`${op}\` with no authorization in this turn's prompt. ` +
|
|
853
|
-
`This operation is irreversible and non-destructive-by-default already declares it ` +
|
|
854
|
-
`never-autonomous. Command: ${shown}.${scan} ` +
|
|
855
|
-
`Ask the user, in one numbered-options block, and run it only after they answer this turn.`;
|
|
856
|
-
return { exit: EXIT_BLOCK, stdout: "", stderr: `${reason}\n`, blockedOp: op };
|
|
857
|
-
}
|
|
858
|
-
|
|
859
|
-
if (WARN_OPS.has(op)) {
|
|
860
|
-
const reason =
|
|
861
|
-
`No \`${op}\` authorization found in this turn's prompt (commit-policy: a one-shot ` +
|
|
862
|
-
`authorization is spent on the operation it named). If the user did authorize it this ` +
|
|
863
|
-
`turn, proceed; otherwise stop and ask. Command: ${shown}`;
|
|
864
|
-
return {
|
|
865
|
-
exit: EXIT_ALLOW,
|
|
866
|
-
stdout: `${JSON.stringify({ decision: "warn", reason })}\n`,
|
|
867
|
-
stderr: "",
|
|
868
|
-
};
|
|
869
|
-
}
|
|
772
|
+
/** Per-operation object patterns on the COMMAND side. */
|
|
773
|
+
const COMMAND_OBJECTS: Readonly<Record<string, readonly RegExp[]>> = {
|
|
774
|
+
"prod-data-destroy": [
|
|
775
|
+
/\bdrop\s+(?:table|database|schema|collection)\s+(?:if\s+exists\s+)?["'`]?([\w.$-]+)/i,
|
|
776
|
+
/\btruncate\s+(?:table\s+)?["'`]?([\w.$-]+)/i,
|
|
777
|
+
/\bdelete\s+from\s+["'`]?([\w.$-]+)/i,
|
|
778
|
+
/\bs3:\/\/([\w.-]+)/i,
|
|
779
|
+
/--table-name[= ]["'`]?([\w.-]+)/i,
|
|
780
|
+
],
|
|
781
|
+
"external-send": [
|
|
782
|
+
/\bhttps?:\/\/([^\s/"'`]+)/i,
|
|
783
|
+
/([A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,})/,
|
|
784
|
+
],
|
|
785
|
+
"money-movement": [
|
|
786
|
+
/--customer[= ]["'`]?([\w.-]+)/i,
|
|
787
|
+
/--amount[= ]["'`]?([\d.,]+)/i,
|
|
788
|
+
/\b(\d[\d.,]*)\s*(?:eur|usd)\b/i,
|
|
789
|
+
],
|
|
790
|
+
};
|
|
870
791
|
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
let envelope: JsonObject = {};
|
|
876
|
-
if (stdin_text.trim()) {
|
|
877
|
-
try {
|
|
878
|
-
const decoded = JSON.parse(stdin_text) as unknown;
|
|
879
|
-
if (_isObject(decoded)) {
|
|
880
|
-
envelope = decoded;
|
|
881
|
-
}
|
|
882
|
-
} catch {
|
|
883
|
-
return EXIT_ALLOW;
|
|
884
|
-
}
|
|
885
|
-
}
|
|
886
|
-
const session_id = typeof envelope["session_id"] === "string" ? envelope["session_id"] : "";
|
|
887
|
-
const command = extractCommand(envelope);
|
|
888
|
-
const ledger = _loadLedger(options.consumer_root, session_id, Date.now());
|
|
889
|
-
|
|
890
|
-
// STAGE 2 runs only on the path where it can change something: a merge that is
|
|
891
|
-
// ABOUT to be refused. Not on an allowed merge (nothing to tell the user), not
|
|
892
|
-
// on an op whose target the command does not name, and never on publish or
|
|
893
|
-
// release — a scan costs a diff read, and spending one where it cannot change
|
|
894
|
-
// the outcome is latency in the user's critical path for nothing.
|
|
895
|
-
let impact: string | undefined;
|
|
896
|
-
const stage1 = command === null ? null : commandOp(command);
|
|
897
|
-
const stage2Target = command === null ? null : mergeTargetOf(command);
|
|
898
|
-
if (
|
|
899
|
-
stage1 === "pr-merge" &&
|
|
900
|
-
stage2Target !== null &&
|
|
901
|
-
!ledger.authorized.has("pr-merge") &&
|
|
902
|
-
!grantCovers(ledger.grants ?? [], "pr-merge", stage2Target)
|
|
903
|
-
) {
|
|
904
|
-
try {
|
|
905
|
-
impact = describeImpact(
|
|
906
|
-
stage2Target,
|
|
907
|
-
analyseMergeImpact(stage2Target, { cwd: options.consumer_root }),
|
|
908
|
-
);
|
|
909
|
-
} catch {
|
|
910
|
-
// The scan is an enrichment, never a gate. A refusal without it is the
|
|
911
|
-
// pre-stage-2 message, which is still correct.
|
|
912
|
-
}
|
|
913
|
-
}
|
|
914
|
-
|
|
915
|
-
const decision = decide(command, ledger, impact);
|
|
916
|
-
if (decision.consumedTarget !== undefined) {
|
|
917
|
-
// Spend the target at the moment the merge is actually let through, never
|
|
918
|
-
// when the grant was minted. A grant the run never reached stays whole, and
|
|
919
|
-
// a pull request that merged can never be replayed — which is what makes a
|
|
920
|
-
// force-push back to an already-merged head harmless here.
|
|
921
|
-
consumeGrantTarget(options.consumer_root, session_id, decision.consumedTarget);
|
|
922
|
-
}
|
|
923
|
-
if (decision.blockedOp) {
|
|
924
|
-
// Record WHAT was refused, so the user's answer to the question this
|
|
925
|
-
// refusal just told the agent to ask can authorize that one operation.
|
|
926
|
-
// Best-effort: a failed write degrades to the previous behaviour, where a
|
|
927
|
-
// numbered answer is unrecordable and the refusal simply repeats.
|
|
928
|
-
try {
|
|
929
|
-
atomic_write_json(path.join(options.consumer_root, pendingFileFor(session_id)), {
|
|
930
|
-
op: decision.blockedOp,
|
|
931
|
-
session_id,
|
|
932
|
-
refused_at: new Date().toISOString(),
|
|
933
|
-
});
|
|
934
|
-
} catch {
|
|
935
|
-
/* observability only — see above */
|
|
936
|
-
}
|
|
937
|
-
}
|
|
938
|
-
if (decision.stdout) {
|
|
939
|
-
process.stdout.write(decision.stdout);
|
|
940
|
-
}
|
|
941
|
-
if (decision.stderr) {
|
|
942
|
-
process.stderr.write(decision.stderr);
|
|
943
|
-
}
|
|
944
|
-
return decision.exit;
|
|
945
|
-
}
|
|
946
|
-
|
|
947
|
-
export function main(argv?: string[]): number {
|
|
948
|
-
const args = argv ?? process.argv.slice(2);
|
|
949
|
-
let consumer_root = process.cwd();
|
|
950
|
-
for (let i = 0; i < args.length; i += 1) {
|
|
951
|
-
const a = args[i];
|
|
952
|
-
if (a === "--project-dir" && args[i + 1] !== undefined) {
|
|
953
|
-
consumer_root = args[i + 1] as string;
|
|
954
|
-
i += 1;
|
|
955
|
-
} else if (a !== undefined && a.startsWith("--project-dir=")) {
|
|
956
|
-
consumer_root = a.slice("--project-dir=".length);
|
|
957
|
-
}
|
|
958
|
-
}
|
|
959
|
-
return run(readHookStdin(), { consumer_root });
|
|
960
|
-
}
|
|
961
|
-
|
|
962
|
-
declare const __AGENT_CONFIG_BUNDLE__: boolean | undefined;
|
|
963
|
-
function _isCliEntry(): boolean {
|
|
964
|
-
if (typeof __AGENT_CONFIG_BUNDLE__ !== "undefined" && __AGENT_CONFIG_BUNDLE__) {
|
|
965
|
-
return false;
|
|
966
|
-
}
|
|
967
|
-
if (process.argv[1] === undefined) {
|
|
968
|
-
return false;
|
|
969
|
-
}
|
|
970
|
-
const argvUrl = pathToFileURL(path.resolve(process.argv[1])).href;
|
|
971
|
-
if (import.meta.url === argvUrl) {
|
|
972
|
-
return true;
|
|
973
|
-
}
|
|
974
|
-
try {
|
|
975
|
-
const here = fs.realpathSync(fileURLToPath(import.meta.url));
|
|
976
|
-
const argv = fs.realpathSync(path.resolve(process.argv[1]));
|
|
977
|
-
return here === argv;
|
|
978
|
-
} catch {
|
|
979
|
-
return false;
|
|
980
|
-
}
|
|
981
|
-
}
|
|
982
|
-
|
|
983
|
-
if (_isCliEntry()) {
|
|
984
|
-
process.exit(main());
|
|
792
|
+
/** A target only counts inside the API's own range; anything else is no target. */
|
|
793
|
+
function _boundedTarget(digits: string): number | null {
|
|
794
|
+
const n = Number.parseInt(digits, 10);
|
|
795
|
+
return Number.isSafeInteger(n) && n > 0 && n <= PR_NUMBER_MAX ? n : null;
|
|
985
796
|
}
|