@event4u/agent-config 14.15.0 → 14.17.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 +84 -153
- package/README.md +1 -1
- package/dist/agent-src/commands/analyze/inbox.md +172 -6
- package/dist/agent-src/commands/fix/commit-messages.md +336 -0
- package/dist/agent-src/commands/fix.md +5 -3
- package/dist/agent-src/rules/commit-conventions.md +1 -1
- package/dist/agent-src/rules/security-sensitive-stop.md +7 -5
- package/dist/agent-src/skills/accessibility-auditor/SKILL.md +20 -2
- package/dist/agent-src/skills/aws-infrastructure/SKILL.md +41 -0
- package/dist/agent-src/skills/conventional-commits-writing/SKILL.md +234 -4
- package/dist/agent-src/skills/design-intelligence/data/ux-guidelines.csv +99 -99
- package/dist/agent-src/skills/fe-design/SKILL.md +6 -3
- package/dist/agent-src/skills/fe-design/references/design-patterns.md +28 -0
- package/dist/agent-src/skills/iconography/SKILL.md +37 -6
- package/dist/agent-src/skills/react-shadcn-ui/SKILL.md +1 -1
- package/dist/agent-src/skills/terraform/SKILL.md +29 -0
- package/dist/agent-src/skills/threat-modeling/data/reasoning.csv +1 -0
- package/dist/agent-src/skills/threat-modeling/data/surfaces.csv +1 -0
- package/dist/agent-src/skills/threat-modeling/data/threats.csv +5 -0
- package/dist/agent-src/templates/agents/agent-project-settings.example.yml +1 -1
- package/dist/cli-delegate/cmd_conformance.js +158 -33
- package/dist/cli-delegate/cmd_explain.js +24 -112
- package/dist/discovery/deprecation-report.md +1 -1
- package/dist/discovery/discovery-manifest.json +47 -21
- package/dist/discovery/discovery-manifest.json.sha256 +1 -1
- package/dist/discovery/discovery-manifest.summary.md +3 -3
- package/dist/discovery/dormancy-report.md +1 -1
- package/dist/discovery/orphan-report.md +1 -1
- package/dist/discovery/packs.json +6 -5
- package/dist/discovery/trust-report.md +2 -2
- package/dist/discovery/workspaces.json +5 -4
- package/dist/hooks/dispatch.js +169 -41
- package/dist/mcp/registry-manifest.json +3 -3
- package/dist/mcp/server.json +2 -2
- package/dist/mcp/server.mjs +160 -35
- package/dist/router.json +1 -1
- package/docs/guidelines/design-antipatterns.md +10 -2
- package/package.json +1 -1
- package/src/config/gate-violation-baselines.json +2 -2
- package/src/config/web-launch-readiness.json +1 -1
- package/src/scripts/_cli/explain_last/index.ts +1 -1
- package/src/scripts/_cli/explain_last/memory.ts +8 -104
- package/src/scripts/check_artefact_count_messaging.ts +9 -0
- package/src/scripts/check_release_highlights.ts +77 -4
- package/src/scripts/check_web_launch_readiness.ts +295 -15
- package/src/scripts/design_slop_rules.ts +19 -9
- package/src/scripts/git_authorization_hook.ts +257 -35
- package/src/scripts/hooks/block_unauthorized_git.ts +16 -4
- package/src/scripts/hooks/merge_impact.ts +68 -0
- package/src/scripts/measure_release_mix.ts +384 -0
- package/src/scripts/release.ts +4 -17
- package/src/scripts/release_drill.ts +154 -1
- package/src/scripts/release_mix_taxonomy.json +172 -0
- package/src/scripts/release_publication.ts +91 -0
- package/src/scripts/attest_artifact.ts +0 -193
|
@@ -171,6 +171,34 @@ Before adding any animation to a UI element, run through this decision tree:
|
|
|
171
171
|
- **Never animate layout properties** (width, height, top, left, padding). Why: triggers browser layout recalculation on every frame; always solvable with `transform`.
|
|
172
172
|
- **Always add `@media (prefers-reduced-motion: reduce)`** — gentler animation (reduced distance/opacity), NOT display:none. Why: vestibular disorders make motion UI unusable; reducing is better than removing.
|
|
173
173
|
|
|
174
|
+
**4. Is the trigger the reader's position, or the reader's action?**
|
|
175
|
+
|
|
176
|
+
Position-triggered motion is the interaction layer's current tell, and it is
|
|
177
|
+
the one case where the decision tree above returns "no" and gets overridden
|
|
178
|
+
anyway. Two shapes, both catalogued:
|
|
179
|
+
|
|
180
|
+
- **Scroll-reveal on every section** (catalog M6). The reader has already
|
|
181
|
+
scrolled to the content; fading it in delays what they asked for. Animate a
|
|
182
|
+
reveal only where position carries meaning — a stepped narrative whose beats
|
|
183
|
+
must land in order, or a long-form piece using position to signal progress —
|
|
184
|
+
and then say so in the brief. A reveal applied per section is a default.
|
|
185
|
+
- **A pointer-tracking spotlight** (catalog M7). The cursor is the one element
|
|
186
|
+
on the page whose position the reader already knows, so highlighting it
|
|
187
|
+
carries no information. Legitimate only where the spotlight *is* the
|
|
188
|
+
interaction: an inspection tool, a reveal mechanic, a magnifier.
|
|
189
|
+
|
|
190
|
+
**5. Does the hover state spend contrast or add it?**
|
|
191
|
+
|
|
192
|
+
A control that lowers its own opacity on hover (catalog M8) reads as retreating
|
|
193
|
+
from the pointer, and it reduces legibility at the moment the reader is
|
|
194
|
+
committing to the target. Hover is carried by brightness, background, border or
|
|
195
|
+
elevation — never by removing contrast. A fade is correct only when it
|
|
196
|
+
communicates a real state change, such as an item being dismissed.
|
|
197
|
+
|
|
198
|
+
The six interaction states every interactive element must assert are
|
|
199
|
+
[`design-review`](../../design-review/SKILL.md)'s; this entry is the motion half
|
|
200
|
+
of the hover one.
|
|
201
|
+
|
|
174
202
|
**4. What to animate?**
|
|
175
203
|
Animate `transform` and `opacity` only. Why: these run on the GPU compositor thread, not the main thread; they never trigger layout or paint.
|
|
176
204
|
`scale(0)` → `scale(1)` is wrong. Why: nothing in the real world appears from nothing. Use `scale(0.95)` + `opacity: 0` → `scale(1)` + `opacity: 1` instead.
|
|
@@ -37,12 +37,30 @@ execution:
|
|
|
37
37
|
1. **Identify the icon need** — what does the icon communicate? Name the semantic
|
|
38
38
|
intent (e.g., "download action", "warning state", "user profile") rather than
|
|
39
39
|
visual description alone.
|
|
40
|
-
2. **
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
40
|
+
2. **Choose the icon set — three rungs, and record which one answered.** The
|
|
41
|
+
set is *chosen*, never inherited: adopting one because a scaffold shipped it
|
|
42
|
+
is the anti-pattern [`icon-consistency`](../../rules/icon-consistency.md)
|
|
43
|
+
§ What this gates names by name. Stop at the first rung that answers.
|
|
44
|
+
1. **A brand token or brand guide that names an icon set** — authoritative,
|
|
45
|
+
stop here ([`brand-source-of-truth`](../../rules/brand-source-of-truth.md)).
|
|
46
|
+
2. **The set already in use in this project** — detected at the Inspect step
|
|
47
|
+
of the Iconography floor below. One icon system per project; a second
|
|
48
|
+
visual language is the defect, not a preference.
|
|
49
|
+
3. **Neither exists → select from the open candidates by the criterion the
|
|
50
|
+
surface actually imposes, and state which criterion decided it.** The
|
|
51
|
+
candidates, unordered: **Heroicons**, **Lucide**, **Phosphor**, **Tabler**.
|
|
52
|
+
|
|
53
|
+
| Criterion the surface imposes | Candidates that satisfy it |
|
|
54
|
+
|---|---|
|
|
55
|
+
| Several stroke weights inside one UI (thin captions, bold empty states) | Phosphor (six weights) |
|
|
56
|
+
| One uniform stroke across a dense admin UI | Lucide, Tabler |
|
|
57
|
+
| First-party match to a component library the project already adopted | Heroicons (Tailwind UI), Lucide (shadcn/ui) |
|
|
58
|
+
| Widest glyph coverage for domain-specific nouns | Tabler, Phosphor |
|
|
59
|
+
|
|
60
|
+
Two criteria pointing at different candidates is a real trade-off — name it
|
|
61
|
+
and pick one; do not split the UI across both. Brand and provider marks are
|
|
62
|
+
a separate axis and use **lobe-icons** (`lobe-icons:openai-color`) whatever
|
|
63
|
+
the UI set is.
|
|
46
64
|
3. **Resolve to a concrete Iconify name** in `set:name` format, e.g.
|
|
47
65
|
`lucide:arrow-right`, `heroicons:user-solid`, `ph:warning-bold`,
|
|
48
66
|
`tabler:download`. Verify the name exists on
|
|
@@ -54,6 +72,19 @@ execution:
|
|
|
54
72
|
5. **Verify** — check the resolved name on `iconify.design/icon/{set}:{name}`;
|
|
55
73
|
confirm the icon renders as expected before shipping.
|
|
56
74
|
|
|
75
|
+
## Which set each stack ships with — an observation, not a pick
|
|
76
|
+
|
|
77
|
+
Recorded so rung 2 above can recognise an incumbent. Finding one of these in a
|
|
78
|
+
project is rung-2 evidence. Reaching for it because the stack is *new* skips
|
|
79
|
+
rung 3 and is exactly the scaffold inheritance
|
|
80
|
+
[`icon-consistency`](../../rules/icon-consistency.md) forbids.
|
|
81
|
+
|
|
82
|
+
| Stack / consumer | Ships with |
|
|
83
|
+
|---|---|
|
|
84
|
+
| `react-shadcn-ui` | Lucide |
|
|
85
|
+
| `blade-ui` | Heroicons (Tailwind first-party) |
|
|
86
|
+
| `tailwind-engineer` | none of its own — any web-font-friendly set |
|
|
87
|
+
|
|
57
88
|
## Iconography floor (design fidelity)
|
|
58
89
|
|
|
59
90
|
Before resolving any new icon, honour the project's existing icon system — the
|
|
@@ -122,7 +122,7 @@ Do NOT use when:
|
|
|
122
122
|
components in the same surface.
|
|
123
123
|
- **Anti-AI-slop catalog + linter.** Pull
|
|
124
124
|
[`docs/guidelines/design-antipatterns.md`](../../../docs/guidelines/design-antipatterns.md)
|
|
125
|
-
before the polish step (Visual V1–
|
|
125
|
+
before the polish step (Visual V1–V8, Layout L1–L10 are the React-component
|
|
126
126
|
slop tells); the objective quality floors (WCAG contrast, focus-visible,
|
|
127
127
|
reduced-motion) are validated via `accessibility-auditor`'s checklist —
|
|
128
128
|
cite its verdict rather than eyeballing.
|
|
@@ -96,6 +96,35 @@ Check existing modules in the project for which registry modules are used.
|
|
|
96
96
|
- Security groups follow least-privilege: only allow traffic between known services.
|
|
97
97
|
- IAM policies use specific resource ARNs, not wildcards (except where unavoidable).
|
|
98
98
|
|
|
99
|
+
### Permissive defaults and missing security blocks
|
|
100
|
+
|
|
101
|
+
The failure that reaches production is almost never a pinned-version mistake;
|
|
102
|
+
it is a resource that works either way and was left on its permissive default.
|
|
103
|
+
Four constructs carry it. Each backstop grep is over the `.tf` tree you are
|
|
104
|
+
changing, not over this repository.
|
|
105
|
+
|
|
106
|
+
| Backstop grep | What a hit means | Override condition |
|
|
107
|
+
|---|---|---|
|
|
108
|
+
| `grep -rn '0\.0\.0\.0/0' --include='*.tf' .` | An ingress or egress rule whose CIDR is 0.0.0.0/0, i.e. open to the whole internet. On a management or database port stop and treat it as CRITICAL - the corpus row enumerates which ports those are. | A deliberately public listener - an ALB or CloudFront origin on 80/443. Nothing else. Record the reason next to the rule. |
|
|
109
|
+
| `grep -rn 'publicly_accessible\|block_public_acls\|ignore_public_acls' --include='*.tf' .` | Either `publicly_accessible = true` on a database, or a bucket with no public-access block present at all. Absence is the more common hit and the grep returning nothing is the failing case. | A bucket that genuinely serves public web assets, with the policy written explicitly rather than inherited from a default. |
|
|
110
|
+
| `grep -rnE '"(Action\|Resource)" *: *"\*"\|= *\["\*"\]' --include='*.tf' .` | A wildcard IAM action or resource. One compromised workload credential then reaches everything in the account. | A named, reviewed exception - `iam:PassRole` scoped by condition, or a bootstrap role. Never the default. |
|
|
111
|
+
| `grep -rL 'encrypt' --include='*.tf' .` (lists files with NO encryption mention; `-L` prints names only, so no `-n`) | A storage, volume, snapshot, or database resource with no encryption block. | None for data at rest. A resource holding no data at all. |
|
|
112
|
+
|
|
113
|
+
Read the surface class before the fix, not after: the mitigation text, the
|
|
114
|
+
abuse case, and the negative test live in the `infrastructure` rows of
|
|
115
|
+
[`threat-modeling`](../threat-modeling/SKILL.md)'s corpus, which is the single
|
|
116
|
+
authority for them. Ask that skill's grounded-corpus step for the
|
|
117
|
+
`infrastructure` surface class - it carries the invocation - and describe the
|
|
118
|
+
change you are making, e.g. *terraform security group ingress open to
|
|
119
|
+
0.0.0.0/0*. Do not copy the command here: one skill owns that interface, for
|
|
120
|
+
the same reason one file owns the control text.
|
|
121
|
+
|
|
122
|
+
Those rows also carry a `Decided by:` clause stating which check actually
|
|
123
|
+
decides each one. For all four constructs above that clause reads NO CHECK IN
|
|
124
|
+
THIS REPOSITORY - no HCL or IAM-policy parser ships here, so these greps and
|
|
125
|
+
your own IaC policy scanner are the whole enforcement story. Do not write or
|
|
126
|
+
imply otherwise.
|
|
127
|
+
|
|
99
128
|
## Common patterns
|
|
100
129
|
|
|
101
130
|
### ECS service with CodeDeploy (Blue/Green)
|
|
@@ -8,3 +8,4 @@ public-endpoint,Rate limit + cache + minimal payload,"{""if_search_endpoint"": "
|
|
|
8
8
|
billing,Ledger + idempotency + server-side amounts,"{""if_refunds"": ""one-way-state-machine"", ""if_webhooks_from_psp"": ""verify-signature-and-amount""}",Mutable balance column updates,CRITICAL
|
|
9
9
|
secrets,Manager + redaction + scan,"{""if_third_party_keys"": ""scope-and-rotate-quarterly"", ""if_ci_pipeline"": ""masked-vars-only""}",Secrets in env committed to VCS,HIGH
|
|
10
10
|
data-exposure,Allowlist serializers + contract tests,"{""if_admin_fields"": ""separate-serializer"", ""if_logs_contain_payloads"": ""logger-redaction-keys""}",Spreading model attributes into responses,MEDIUM
|
|
11
|
+
infrastructure,Deny-by-default ingress + encryption on + scoped IAM,"{""if_management_or_db_port"": ""no-internet-cidr-use-bastion-or-private-link"", ""if_object_storage"": ""block-public-access-at-account-and-bucket"", ""if_iam_policy"": ""enumerate-actions-and-resource-arns""}",Permissive defaults left in place because the resource works either way,HIGH
|
|
@@ -8,3 +8,4 @@ Public unauthenticated endpoint,public endpoint health status unauthenticated an
|
|
|
8
8
|
Billing / payment / balance mutation,billing payment charge refund invoice subscription balance credit,billing,money movement,Anything that changes an amount owed/paid
|
|
9
9
|
Secrets / key handling,secret api-key signing key env vault kms rotation,secrets,credentials,"Storage, rotation, and logging paths"
|
|
10
10
|
Data export / serializer / log channel,export serializer api-resource log audit csv download report,data-exposure,"PII, internal fields",Response shaping and log sinks
|
|
11
|
+
Infrastructure / IaC resource definition,terraform iac infrastructure provisioning security group ingress egress cidr bucket storage iam role subnet vpc encryption,infrastructure,"network reachability, data at rest, cloud credentials",Any IaC change to networking storage IAM or encryption defaults
|
|
@@ -14,3 +14,8 @@ billing,Race condition on balance / double-spend,CWE-362,Parallel requests redee
|
|
|
14
14
|
billing,Price/amount tampering,CWE-602,Client-supplied price or currency trusted at checkout,Recompute amounts server-side from catalog; signed cart or server-held cart only,Test: tampered client amount → server total unchanged,CRITICAL
|
|
15
15
|
secrets,Secret leakage via logs / errors / VCS,"CWE-532, ATT&CK T1552","Key appears in exception payload, debug log, or committed .env",Centralized secret manager; logger redaction list; pre-commit secret scan; scoped short-lived tokens,Test: forced exception on auth path → no credential substring in log sink,HIGH
|
|
16
16
|
data-exposure,Over-broad serializer / API resource,"CWE-213, OWASP API3","Serializer returns internal fields (cost, flags, foreign tenant refs) the UI ignores but attackers read",Explicit field allowlist per resource; separate admin vs public serializers; contract test on response shape,Test: schema-diff response against documented contract → no undocumented fields,MEDIUM
|
|
17
|
+
infrastructure,Unrestricted ingress to a management or database port,"CWE-1327, ATT&CK T1190",Attacker reaches SSH/RDP or a database engine straight from the internet because a security-group or firewall rule allows 0.0.0.0/0,"Deny-by-default ingress; management and database ports reachable only via bastion, VPN, or private link; CIDR scoped to known ranges; never 0.0.0.0/0 on 22/3389/3306/5432/6379/27017","Test: a rule with cidr_blocks 0.0.0.0/0 on a management or database port is rejected. Decided by: NO CHECK IN THIS REPOSITORY - nothing here parses HCL or cloud config; the deciding check is the consumer's own IaC policy scanner",CRITICAL
|
|
18
|
+
infrastructure,Object storage public by default,"CWE-732, OWASP A05",Bucket or container created without a block-public-access setting so any object URL is world-readable,Block public access at account AND bucket level; explicit policy allowlist instead of a public ACL; serve objects via signed URLs or a CDN origin identity,"Test: storage created with no explicit public-access block is rejected; anonymous GET of a stored object returns 403. Decided by: NO CHECK IN THIS REPOSITORY - the deciding check is the consumer's own IaC policy scanner or a cloud posture tool",HIGH
|
|
19
|
+
infrastructure,Missing encryption at rest or in transit,"CWE-311, CWE-319",Volume snapshot bucket or database instance created with no encryption block; endpoint accepts a plaintext connection,Encryption block mandatory on every storage and database resource; customer-managed key where the data class requires it; TLS-only bucket policy and listener; reject plaintext listeners,"Test: a storage or database resource with no encryption block is rejected; a plaintext connection to the endpoint is refused. Decided by: NO CHECK IN THIS REPOSITORY - the deciding check is the consumer's own IaC policy scanner",HIGH
|
|
20
|
+
infrastructure,Wildcard IAM action or resource,"CWE-269, ATT&CK T1078.004",A role policy grants Action * or Resource * so one compromised workload credential reaches every service and object in the account,Enumerate actions and resource ARNs; one role per workload; permission boundary or service control policy on privileged roles; no wildcard outside a named reviewed exception,"Test: a policy document containing Action ""*"" or Resource ""*"" is rejected or carries a named exception. Decided by: NO CHECK IN THIS REPOSITORY - nothing here parses IAM policy documents; the deciding check is the consumer's own IaC policy scanner",CRITICAL
|
|
21
|
+
infrastructure,Credential in an IaC or vars file instead of a secret manager,"CWE-798, ATT&CK T1552.001",Password token or key written as a literal into a .tf .tfvars or .yaml file and committed,Secret-manager reference or runtime-injected env var; never a literal in IaC or vars files; treat the state file as sensitive and store it encrypted with restricted access,"Test: a literal credential in a tracked IaC or vars file fails the build. Decided by: check_secret_leak (src/scripts/check_secret_leak.ts) inside THIS repository - it has no extension allowlist so .tf and .yaml are in scope; for a CONSUMER the deciding check is the gitleaks workflow the package ships as a template that the consumer must copy",HIGH
|
|
@@ -39,7 +39,7 @@ schema_version: 1
|
|
|
39
39
|
# CI guard: a release bump of `package.json` must update this value
|
|
40
40
|
# in lockstep — see scripts/check_template_pin_drift.ts (road-to-
|
|
41
41
|
# portable-runtime-and-update-check P3.3).
|
|
42
|
-
agent_config_version: "14.
|
|
42
|
+
agent_config_version: "14.17.0"
|
|
43
43
|
|
|
44
44
|
# --- Project identity ---
|
|
45
45
|
project:
|
|
@@ -807,20 +807,22 @@ var PHRASES = [
|
|
|
807
807
|
// "never auto-merge" each returned ["pr-merge"] — a prompt saying DO NOT
|
|
808
808
|
// MERGE authorized a merge. The noun-sense lookahead was never a negator.
|
|
809
809
|
//
|
|
810
|
-
// The lookBEHIND
|
|
811
|
-
//
|
|
812
|
-
// its negated-claim window (`:490-493`). A sentence boundary ends a
|
|
813
|
-
// negation's reach: "Do not push. Merge PR #12." is two instructions, and a
|
|
814
|
-
// guard that let the first suppress the second would silently stop
|
|
815
|
-
// authorizing merges the user DID order — the failure mode that is worse
|
|
816
|
-
// than the defect, because nothing happens and nothing says why.
|
|
817
|
-
//
|
|
818
|
-
// Reusing that vocabulary rather than inventing a third is deliberate: two
|
|
810
|
+
// The inline negative lookBEHIND that used to live in this pattern was
|
|
811
|
+
// REMOVED on 2026-09-03, and its own comment is why. It warned that "two
|
|
819
812
|
// negation vocabularies in one tree drift, and the drift is invisible until
|
|
820
|
-
// a prompt lands in the gap between them
|
|
813
|
+
// a prompt lands in the gap between them" — and by then they had drifted:
|
|
814
|
+
// `negatedBefore` became clause-scanned in both directions while this
|
|
815
|
+
// lookbehind stayed a blind 30-character window with no forward half. The
|
|
816
|
+
// prompt that landed in the gap is `"nicht pushen, aber mergen"`, which the
|
|
817
|
+
// lookbehind refused (13 characters, inside its window, across a contrast
|
|
818
|
+
// cue it could not see) while the clause scan correctly authorizes it.
|
|
819
|
+
//
|
|
820
|
+
// Every phrase match already passes through `negatedBefore` at the call
|
|
821
|
+
// site, so the guard is not weakened by the removal — it is applied once,
|
|
822
|
+
// by the one vocabulary this file's comment asked for.
|
|
821
823
|
{
|
|
822
824
|
op: "pr-merge",
|
|
823
|
-
re:
|
|
825
|
+
re: /\b(merge|merg(e|en|st|t)|zusammenf(ü|ue)hren|reinmergen)\b(?!\s*[- ]?(conflict|konflikt|commit|base|queue|state|status))/i
|
|
824
826
|
},
|
|
825
827
|
// A tag is an ACTION here — bare "Tag" is the German word for day, and
|
|
826
828
|
// "Version" is an ordinary noun. Both authorized a BLOCK op before this.
|
|
@@ -942,13 +944,16 @@ function isInterrogative(prose) {
|
|
|
942
944
|
if (!t) {
|
|
943
945
|
return false;
|
|
944
946
|
}
|
|
945
|
-
|
|
947
|
+
if (/\?\s*$/.test(t)) {
|
|
948
|
+
return true;
|
|
949
|
+
}
|
|
950
|
+
const hasImperative = /\b(mach|mache|bitte|leg|lege|erstell|erstelle|f(ü|ue)hr|f(ü|ue)hre|setz|setze|starte|los|jetzt|now|asap|go ahead|do it|ja[,.]?\s|ok[,.]?\s)\b/i.test(
|
|
946
951
|
t
|
|
947
952
|
);
|
|
948
953
|
if (hasImperative) {
|
|
949
954
|
return false;
|
|
950
955
|
}
|
|
951
|
-
return
|
|
956
|
+
return /^(was|wie|warum|wieso|weshalb|wann|wer|welche[rs]?|wo|ist|sind|kann|kannst|k(ö|oe)nnen|soll|sollen|darf|d(ü|ue)rfen|why|what|how|when|which|who|is|are|can|could|should|does|do)\b/i.test(
|
|
952
957
|
t
|
|
953
958
|
);
|
|
954
959
|
}
|
|
@@ -995,13 +1000,18 @@ function splitFences(prompt) {
|
|
|
995
1000
|
function _commandLines(fence) {
|
|
996
1001
|
return fence.split("\n").map((l) => l.trim()).filter((l) => l.length > 0).filter((l) => !/^(remote:|error:|hint:|fatal:|warning:|To |!\s|\s*\^)/i.test(l)).map((l) => l.replace(/^[$>#]\s*/, "")).filter((l) => /^(git|gh|npm|pnpm|yarn|task)\b/.test(l));
|
|
997
1002
|
}
|
|
1003
|
+
function _errText(err) {
|
|
1004
|
+
const t = err instanceof Error ? err.message : String(err);
|
|
1005
|
+
return t.replace(/\s+/g, " ").trim().slice(0, 200);
|
|
1006
|
+
}
|
|
1007
|
+
var PR_NUMBER_MAX = 2147483647;
|
|
998
1008
|
function extractMergeTargets(prompt) {
|
|
999
1009
|
const out = /* @__PURE__ */ new Set();
|
|
1000
|
-
const re = /(?:#|\bpr[- ]?|\bpull[- ]request\s+#?)(\d{1,
|
|
1010
|
+
const re = /(?:#|\bpr[- ]?|\bpull[- ]request\s+#?)(\d{1,10})\b/giu;
|
|
1001
1011
|
let m;
|
|
1002
1012
|
while ((m = re.exec(prompt)) !== null) {
|
|
1003
1013
|
const n = Number.parseInt(m[1], 10);
|
|
1004
|
-
if (Number.isSafeInteger(n) && n > 0) {
|
|
1014
|
+
if (Number.isSafeInteger(n) && n > 0 && n <= PR_NUMBER_MAX) {
|
|
1005
1015
|
out.add(n);
|
|
1006
1016
|
}
|
|
1007
1017
|
}
|
|
@@ -1045,27 +1055,109 @@ function foldGrants(prior, prompt, authorized, session_id, now) {
|
|
|
1045
1055
|
];
|
|
1046
1056
|
}
|
|
1047
1057
|
var NEGATION_WORD = /\b(nicht|nichts|kein(e|en|em|er|es)?|niemals|nie|no|not|dont|don't|never|without|ohne)\b/i;
|
|
1048
|
-
|
|
1058
|
+
var CONTRAST_CUE = /\b(aber|sondern|jedoch|but|however|instead)\b/gi;
|
|
1059
|
+
var ABBREVIATION_BEFORE_DOT = /(?:^|[\s("'\u2018\u201c])(?:[a-z]|z\.b|bzw|ca|usw|d\.h|u\.a|evtl|ggf|inkl|max|min|nr|abb|e\.g|i\.e|etc|vs|approx|fig|no|st|dr|prof)\.$/i;
|
|
1060
|
+
function isSentenceBoundary(text, at) {
|
|
1061
|
+
const mark = text[at];
|
|
1062
|
+
if (mark !== ".") {
|
|
1063
|
+
return true;
|
|
1064
|
+
}
|
|
1065
|
+
if (/\d/.test(text[at - 1] ?? "") && /\d/.test(text[at + 1] ?? "")) {
|
|
1066
|
+
return false;
|
|
1067
|
+
}
|
|
1068
|
+
return !ABBREVIATION_BEFORE_DOT.test(text.slice(0, at + 1));
|
|
1069
|
+
}
|
|
1070
|
+
function sentenceBounds(text, index) {
|
|
1049
1071
|
let start = 0;
|
|
1050
|
-
for (
|
|
1051
|
-
const
|
|
1052
|
-
if (
|
|
1053
|
-
start =
|
|
1072
|
+
for (let i = index - 1; i >= 0; i -= 1) {
|
|
1073
|
+
const c = text[i];
|
|
1074
|
+
if ((c === "." || c === "!" || c === "?" || c === "\n") && isSentenceBoundary(text, i)) {
|
|
1075
|
+
start = i + 1;
|
|
1076
|
+
break;
|
|
1054
1077
|
}
|
|
1055
1078
|
}
|
|
1056
|
-
|
|
1057
|
-
|
|
1079
|
+
let end = text.length;
|
|
1080
|
+
for (let i = index; i < text.length; i += 1) {
|
|
1081
|
+
const c = text[i];
|
|
1082
|
+
if ((c === "." || c === "!" || c === "?" || c === "\n") && isSentenceBoundary(text, i)) {
|
|
1083
|
+
end = i;
|
|
1084
|
+
break;
|
|
1085
|
+
}
|
|
1086
|
+
}
|
|
1087
|
+
return { start, end };
|
|
1088
|
+
}
|
|
1089
|
+
function _sentences(text) {
|
|
1090
|
+
const out = [];
|
|
1091
|
+
let from = 0;
|
|
1092
|
+
for (let i = 0; i < text.length; i += 1) {
|
|
1093
|
+
const c = text[i];
|
|
1094
|
+
if ((c === "." || c === "!" || c === "?" || c === "\n") && isSentenceBoundary(text, i)) {
|
|
1095
|
+
out.push(text.slice(from, i + 1));
|
|
1096
|
+
from = i + 1;
|
|
1097
|
+
}
|
|
1098
|
+
}
|
|
1099
|
+
if (from < text.length) {
|
|
1100
|
+
out.push(text.slice(from));
|
|
1101
|
+
}
|
|
1102
|
+
return out.filter((t) => t.trim() !== "");
|
|
1103
|
+
}
|
|
1104
|
+
function clauseStart(text, from, to) {
|
|
1105
|
+
let start = from;
|
|
1106
|
+
const seg = text.slice(from, to);
|
|
1107
|
+
for (let i = seg.length - 1; i >= 0; i -= 1) {
|
|
1108
|
+
const c = seg[i];
|
|
1109
|
+
if (c === "," || c === ";" || c === ":") {
|
|
1110
|
+
start = from + i + 1;
|
|
1111
|
+
break;
|
|
1112
|
+
}
|
|
1113
|
+
}
|
|
1114
|
+
CONTRAST_CUE.lastIndex = 0;
|
|
1115
|
+
let m;
|
|
1116
|
+
while ((m = CONTRAST_CUE.exec(seg)) !== null) {
|
|
1117
|
+
const after = from + m.index + m[0].length;
|
|
1118
|
+
if (after > start) {
|
|
1119
|
+
start = after;
|
|
1120
|
+
}
|
|
1121
|
+
}
|
|
1122
|
+
return start;
|
|
1123
|
+
}
|
|
1124
|
+
function clauseEnd(text, from, to) {
|
|
1125
|
+
const seg = text.slice(from, to);
|
|
1126
|
+
let end = to;
|
|
1127
|
+
const punct = seg.search(/[,;:]/);
|
|
1128
|
+
if (punct !== -1) {
|
|
1129
|
+
end = from + punct;
|
|
1130
|
+
}
|
|
1131
|
+
CONTRAST_CUE.lastIndex = 0;
|
|
1132
|
+
const m = CONTRAST_CUE.exec(seg);
|
|
1133
|
+
if (m !== null && from + m.index < end) {
|
|
1134
|
+
end = from + m.index;
|
|
1135
|
+
}
|
|
1136
|
+
return end;
|
|
1137
|
+
}
|
|
1138
|
+
function negatedBefore(text, index, matched = "") {
|
|
1139
|
+
const { start, end } = sentenceBounds(text, index);
|
|
1140
|
+
const before = text.slice(clauseStart(text, start, index), index);
|
|
1141
|
+
const afterFrom = index + matched.length;
|
|
1142
|
+
const after = afterFrom < end ? text.slice(afterFrom, clauseEnd(text, afterFrom, end)) : "";
|
|
1143
|
+
return NEGATION_WORD.test(before) || NEGATION_WORD.test(matched) || NEGATION_WORD.test(after);
|
|
1058
1144
|
}
|
|
1059
1145
|
function classifyAuthorization(prompt) {
|
|
1060
1146
|
const { prose, fences } = splitFences(prompt);
|
|
1061
1147
|
const authorized = /* @__PURE__ */ new Set();
|
|
1062
1148
|
const evidence = {};
|
|
1063
1149
|
const instruction = prose.split("\n").filter((l) => !OUTPUT_LINE.test(l)).join("\n");
|
|
1064
|
-
for (const { op, re } of
|
|
1065
|
-
const
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1150
|
+
for (const { op, re } of PHRASES) {
|
|
1151
|
+
for (const sentence of _sentences(instruction)) {
|
|
1152
|
+
if (isInterrogative(sentence)) {
|
|
1153
|
+
continue;
|
|
1154
|
+
}
|
|
1155
|
+
const m = re.exec(sentence);
|
|
1156
|
+
if (m && !negatedBefore(sentence, m.index, m[0])) {
|
|
1157
|
+
authorized.add(op);
|
|
1158
|
+
evidence[op] = `prose: "${m[0]}"`;
|
|
1159
|
+
break;
|
|
1160
|
+
}
|
|
1069
1161
|
}
|
|
1070
1162
|
}
|
|
1071
1163
|
for (const fence of fences) {
|
|
@@ -1143,7 +1235,11 @@ function consumeGrantTarget(consumer_root, session_id, target) {
|
|
|
1143
1235
|
if (touched) {
|
|
1144
1236
|
try {
|
|
1145
1237
|
atomic_write_json(file, decoded);
|
|
1146
|
-
} catch {
|
|
1238
|
+
} catch (err) {
|
|
1239
|
+
process.stderr.write(
|
|
1240
|
+
`git-authorization: grant-consumption write failed (${_errText(err)}) \u2014 the target stays UNSPENT, so one further merge of a PR the user named will still be authorized. Authorization outcome unchanged.
|
|
1241
|
+
`
|
|
1242
|
+
);
|
|
1147
1243
|
}
|
|
1148
1244
|
}
|
|
1149
1245
|
}
|
|
@@ -1236,7 +1332,11 @@ function run2(stdin_text, options) {
|
|
|
1236
1332
|
};
|
|
1237
1333
|
try {
|
|
1238
1334
|
atomic_write_json(path2.join(options.consumer_root, ledgerFileFor(session)), ledger);
|
|
1239
|
-
} catch {
|
|
1335
|
+
} catch (err) {
|
|
1336
|
+
process.stderr.write(
|
|
1337
|
+
`git-authorization: session-ledger write failed (${_errText(err)}) \u2014 the gate degrades to "no ledger" for this session, which denies the irreversible subset and warns on the rest. Authorization outcome unchanged.
|
|
1338
|
+
`
|
|
1339
|
+
);
|
|
1240
1340
|
}
|
|
1241
1341
|
return EXIT_ALLOW2;
|
|
1242
1342
|
}
|
|
@@ -1300,6 +1400,24 @@ var DESTRUCTIVE_MARKERS = [
|
|
|
1300
1400
|
{ label: "a route or endpoint was removed", re: /^-.*\b(Route::(get|post|put|patch|delete)|@(Get|Post|Put|Patch|Delete)Mapping|app\.(get|post|put|patch|delete)\s*\()/m },
|
|
1301
1401
|
{ label: "a whole file was deleted", re: /^deleted file mode /m }
|
|
1302
1402
|
];
|
|
1403
|
+
var SCHEMA_REMOVAL_BODY = /\b(dropColumn|dropTable|dropIfExists|removeColumn|drop_column|drop_table|DROP\s+(TABLE|COLUMN|INDEX|CONSTRAINT)|TRUNCATE\s+TABLE)\b/i;
|
|
1404
|
+
var RENAME_METADATA = /^rename (?:from|to) /m;
|
|
1405
|
+
function fileBlocks(patch) {
|
|
1406
|
+
return patch.split(/^(?=diff --git )/m).filter((b) => b.startsWith("diff --git "));
|
|
1407
|
+
}
|
|
1408
|
+
function renamedSchemaRemoval(patch) {
|
|
1409
|
+
for (const block of fileBlocks(patch)) {
|
|
1410
|
+
if (!RENAME_METADATA.test(block)) {
|
|
1411
|
+
continue;
|
|
1412
|
+
}
|
|
1413
|
+
for (const line of block.split("\n")) {
|
|
1414
|
+
if (line.startsWith("-") && !line.startsWith("---") && SCHEMA_REMOVAL_BODY.test(line)) {
|
|
1415
|
+
return true;
|
|
1416
|
+
}
|
|
1417
|
+
}
|
|
1418
|
+
}
|
|
1419
|
+
return false;
|
|
1420
|
+
}
|
|
1303
1421
|
var MAJOR_BUMP = /^-\s*"version"\s*:\s*"(\d+)\.[^"]*"[\s\S]{0,200}?^\+\s*"version"\s*:\s*"(\d+)\./m;
|
|
1304
1422
|
function classifyDiff(patch) {
|
|
1305
1423
|
if (patch.trim() === "") {
|
|
@@ -1311,6 +1429,9 @@ function classifyDiff(patch) {
|
|
|
1311
1429
|
markers.push(label);
|
|
1312
1430
|
}
|
|
1313
1431
|
}
|
|
1432
|
+
if (renamedSchemaRemoval(patch)) {
|
|
1433
|
+
markers.push("a renamed file drops a column or table");
|
|
1434
|
+
}
|
|
1314
1435
|
const bump = MAJOR_BUMP.exec(patch);
|
|
1315
1436
|
if (bump && bump[1] !== bump[2]) {
|
|
1316
1437
|
markers.push(`major version bump ${bump[1]}.x to ${bump[2]}.x`);
|
|
@@ -1732,12 +1853,16 @@ function commandOp(command) {
|
|
|
1732
1853
|
return found;
|
|
1733
1854
|
}
|
|
1734
1855
|
function mergeTargetOf(command) {
|
|
1735
|
-
const gh = /\bgh\s+pr\s+merge\s+(?:-{1,2}\S+\s+)*?(\d{1,
|
|
1856
|
+
const gh = /\bgh\s+pr\s+merge\s+(?:-{1,2}\S+\s+)*?(\d{1,10})\b/i.exec(command);
|
|
1736
1857
|
if (gh) {
|
|
1737
|
-
return
|
|
1858
|
+
return _boundedTarget(gh[1]);
|
|
1738
1859
|
}
|
|
1739
|
-
const api = /\/pulls\/(\d{1,
|
|
1740
|
-
return api ?
|
|
1860
|
+
const api = /\/pulls\/(\d{1,10})\/merge\b/i.exec(command);
|
|
1861
|
+
return api ? _boundedTarget(api[1]) : null;
|
|
1862
|
+
}
|
|
1863
|
+
function _boundedTarget(digits) {
|
|
1864
|
+
const n = Number.parseInt(digits, 10);
|
|
1865
|
+
return Number.isSafeInteger(n) && n > 0 && n <= PR_NUMBER_MAX ? n : null;
|
|
1741
1866
|
}
|
|
1742
1867
|
function grantCovers(grants, op, target) {
|
|
1743
1868
|
if (op !== "pr-merge" || target === null) {
|