@event4u/agent-config 7.3.0 → 7.5.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/.claude-plugin/marketplace.json +4 -1
- package/CHANGELOG.md +81 -0
- package/README.md +2 -2
- package/dist/agent-src/commands/fix/pr-comments.md +42 -13
- package/dist/agent-src/commands/review-changes.md +14 -7
- package/dist/agent-src/rules/artifact-engagement-recording.md +9 -0
- package/dist/agent-src/rules/cli-output-handling.md +18 -0
- package/dist/agent-src/rules/domain-safety-pii.md +4 -3
- package/dist/agent-src/rules/icon-consistency.md +1 -0
- package/dist/agent-src/rules/legal-safety-floor.md +2 -2
- package/dist/agent-src/rules/untrusted-input-defense.md +8 -5
- package/dist/agent-src/skills/adr-create/SKILL.md +5 -1
- package/dist/agent-src/skills/adversarial-review/SKILL.md +1 -1
- package/dist/agent-src/skills/blade-ui/SKILL.md +4 -0
- package/dist/agent-src/skills/brand-to-tokens/SKILL.md +2 -1
- package/dist/agent-src/skills/code-review/SKILL.md +4 -0
- package/dist/agent-src/skills/context-authoring/SKILL.md +1 -1
- package/dist/agent-src/skills/design-intelligence/SKILL.md +40 -0
- package/dist/agent-src/skills/design-review/SKILL.md +23 -11
- package/dist/agent-src/skills/design-system-capture/SKILL.md +41 -0
- package/dist/agent-src/skills/design-system-capture/reference/design-system-json.md +62 -0
- package/dist/agent-src/skills/design-tokens/SKILL.md +1 -0
- package/dist/agent-src/skills/existing-ui-audit/SKILL.md +6 -0
- package/dist/agent-src/skills/flux/SKILL.md +4 -0
- package/dist/agent-src/skills/iconography/SKILL.md +1 -0
- package/dist/agent-src/skills/image-editing/evals/triggers.json +13 -13
- package/dist/agent-src/skills/judge-artifact-completeness/SKILL.md +136 -0
- package/dist/agent-src/skills/judge-artifact-completeness/calibration/fixtures.json +149 -0
- package/dist/agent-src/skills/judge-artifact-completeness/evals/triggers.json +17 -0
- package/dist/agent-src/skills/judge-artifact-completeness/rubrics/architecture-score.json +73 -0
- package/dist/agent-src/skills/judge-artifact-completeness/rubrics/pr-review-score.json +65 -0
- package/dist/agent-src/skills/judge-artifact-completeness/rubrics/roadmap-score.json +73 -0
- package/dist/agent-src/skills/judge-artifact-completeness/rubrics/ticket-quality-score.json +73 -0
- package/dist/agent-src/skills/judge-injection-defense/SKILL.md +99 -0
- package/dist/agent-src/skills/judge-injection-defense/calibration/fixtures.json +24 -0
- package/dist/agent-src/skills/judge-injection-defense/evals/triggers.json +17 -0
- package/dist/agent-src/skills/judge-injection-defense/fixtures/behavioral-fixtures.json +63 -0
- package/dist/agent-src/skills/judge-injection-defense/fixtures/coverage-report.md +53 -0
- package/dist/agent-src/skills/judge-injection-defense/fixtures/perturbation-taxonomy.json +110 -0
- package/dist/agent-src/skills/judge-synthesis/SKILL.md +134 -0
- package/dist/agent-src/skills/judge-synthesis/evals/triggers.json +17 -0
- package/dist/agent-src/skills/logo-generation/SKILL.md +1 -1
- package/dist/agent-src/skills/markitdown/SKILL.md +1 -1
- package/dist/agent-src/skills/react-shadcn-ui/SKILL.md +18 -0
- package/dist/agent-src/skills/react-shadcn-ui/reference/registry.md +59 -0
- package/dist/agent-src/skills/refine-ticket/SKILL.md +5 -1
- package/dist/agent-src/skills/release-comms/SKILL.md +1 -1
- package/dist/agent-src/skills/roadmap-management/SKILL.md +4 -0
- package/dist/agent-src/skills/roadmap-writing/SKILL.md +1 -1
- package/dist/agent-src/skills/script-writing/SKILL.md +1 -1
- package/dist/agent-src/skills/sql-writing/SKILL.md +1 -1
- package/dist/agent-src/skills/systematic-debugging/SKILL.md +1 -1
- package/dist/agent-src/skills/tailwind-engineer/SKILL.md +4 -0
- package/dist/agent-src/skills/testing-anti-patterns/SKILL.md +1 -1
- package/dist/agent-src/skills/token-optimizer/SKILL.md +1 -1
- package/dist/agent-src/skills/typography-system/SKILL.md +2 -1
- package/dist/agent-src/templates/agents/agent-project-settings.example.yml +1 -1
- package/dist/cli/registry.js +1 -0
- package/dist/cli/registry.js.map +1 -1
- package/dist/discovery/deprecation-report.md +1 -1
- package/dist/discovery/discovery-manifest.json +109 -43
- package/dist/discovery/discovery-manifest.json.sha256 +1 -1
- package/dist/discovery/discovery-manifest.summary.md +3 -3
- package/dist/discovery/orphan-report.md +1 -1
- package/dist/discovery/packs.json +8 -5
- package/dist/discovery/trust-report.md +2 -2
- package/dist/discovery/workspaces.json +7 -4
- package/dist/mcp/registry-manifest.json +3 -3
- package/dist/router.json +1 -1
- package/dist/server/schemas/settings.js +6 -0
- package/dist/server/schemas/settings.js.map +1 -1
- package/docs/architecture.md +2 -2
- package/docs/catalog.md +28 -20
- package/docs/command-flows.md +1 -1
- package/docs/enforcement-by-host.md +9 -0
- package/docs/governance-advantage.md +72 -0
- package/docs/guidelines/design-antipatterns.md +26 -0
- package/docs/guidelines/design-canon.md +62 -0
- package/package.json +2 -1
- package/src/config/agent-settings.template.yml +21 -0
- package/src/scripts/_cli/cmd_analyze_session.ts +347 -0
- package/src/scripts/_dispatch.bash +8 -0
- package/src/scripts/_lib/token_count.ts +110 -35
- package/src/scripts/audit_initial_context.ts +37 -12
- package/src/scripts/bench_quality_run.ts +253 -0
- package/src/scripts/bench_rule_load_latency.ts +178 -0
- package/src/scripts/check_kernel_prefix_stability.ts +0 -0
- package/src/scripts/check_quality_regression.ts +244 -0
- package/src/scripts/check_token_quality_golden.ts +253 -0
- package/src/scripts/check_token_regression.ts +266 -0
- package/src/scripts/design_slop_rules.test.ts +193 -0
- package/src/scripts/design_slop_rules.ts +502 -0
- package/src/scripts/external_sources_denylist.json +7 -1
- package/src/scripts/hook_manifest.yaml +26 -3
- package/src/scripts/hooks/design_slop_hook.ts +181 -0
- package/src/scripts/hooks/rtk_wrap_hook.ts +228 -0
- package/src/scripts/lint_agent_security.ts +1 -0
- package/src/scripts/lint_confusables.ts +197 -0
- package/src/scripts/lint_design_slop.ts +245 -0
- package/src/scripts/probe_host_compliance.ts +145 -0
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
# The governance advantage — why compile-time, config-space governance is the moat
|
|
2
|
+
|
|
3
|
+
Most agent-config packages are a pile of skills. This one's defensible edge is
|
|
4
|
+
not skill *count* — it is **where the governance lives**: compiled into every
|
|
5
|
+
host's own instruction layer at projection time, so it travels with the config
|
|
6
|
+
and is present on every host by construction. Runtime-hook systems can only
|
|
7
|
+
enforce where the host exposes a tool lifecycle — a minority of hosts.
|
|
8
|
+
|
|
9
|
+
This is a **structural** claim, not a behaviour claim. See the honesty boundary
|
|
10
|
+
at the end.
|
|
11
|
+
|
|
12
|
+
## The two enforcement layers (and their reach)
|
|
13
|
+
|
|
14
|
+
| Layer | How it works | Reach |
|
|
15
|
+
|---|---|---|
|
|
16
|
+
| **Compile-time (config space)** | Rules + Iron Laws are compiled into each host's native instruction format at projection time (`.cursorrules`, `.windsurfrules`, `copilot-instructions.md`, `GEMINI.md`, Claude/Augment native rule dirs). | **Every projection target.** |
|
|
17
|
+
| **Runtime hooks** | On hosts exposing a tool lifecycle (`PreToolUse`/`PostToolUse`/`Stop`), a guard can deterministically block a call (e.g. `block_no_verify`). | **Hook-capable hosts only** (~2 of the supported set). |
|
|
18
|
+
|
|
19
|
+
Full per-host breakdown: [`enforcement-by-host.md`](enforcement-by-host.md).
|
|
20
|
+
|
|
21
|
+
The point: a runtime-first competitor's strongest enforcement simply **does not
|
|
22
|
+
exist** on a host that has no hook surface. The compile-time layer is there on
|
|
23
|
+
*that same host* because it rides in the config the host already reads.
|
|
24
|
+
|
|
25
|
+
## Governance in action — three concrete traces
|
|
26
|
+
|
|
27
|
+
### 1. A safety-floor rule survives projection into a static host
|
|
28
|
+
`commit-policy` (`tier: safety-floor`) is compiled into Cursor's `.cursorrules`
|
|
29
|
+
and Copilot's `copilot-instructions.md`. The host has no hook to block a
|
|
30
|
+
`git commit`, but the constraint is **in the instruction file the host loads at
|
|
31
|
+
session start** — present by construction, not dependent on a runtime primitive
|
|
32
|
+
the host lacks (a static host gives it no runtime attention guarantee, but a
|
|
33
|
+
runtime-only system gives it nothing at all here). A runtime-only system contributes *nothing* on these hosts.
|
|
34
|
+
|
|
35
|
+
### 2. Intent routing, not a 500-artefact dump
|
|
36
|
+
A prompt does not load all 258 skills. `dist/router.json` (88 rules, intent →
|
|
37
|
+
skill, keyword/phrase/path triggers) carries the right skill on intent. The
|
|
38
|
+
governance value is *selection discipline* — the host gets the relevant rule
|
|
39
|
+
set, not the whole catalogue as context ballast.
|
|
40
|
+
|
|
41
|
+
### 3. The enforcement spectrum is explicit, not implied
|
|
42
|
+
On Claude Code / Cline (MCP) the safety-floor constraints get **both** layers:
|
|
43
|
+
compile-time instruction **and** a deterministic runtime block. On Cursor /
|
|
44
|
+
Windsurf / Copilot / Gemini they get the compile-time layer alone. We state
|
|
45
|
+
which host gets which — we never imply "deterministic everywhere".
|
|
46
|
+
|
|
47
|
+
## What this is NOT (honesty boundary)
|
|
48
|
+
|
|
49
|
+
- **Not "un-strippable".** A human can edit any projected file. The claim is
|
|
50
|
+
*present-by-construction on every host* — a host's runtime cannot selectively
|
|
51
|
+
ignore it the way it ignores an absent runtime hook.
|
|
52
|
+
- **Not a measured behaviour-change claim.** A length-controlled A/B
|
|
53
|
+
(hardened-constraint blocks vs a length-matched placebo) measured **honest-null
|
|
54
|
+
at the discipline ceiling** — the cooperative rules already catch the test
|
|
55
|
+
traps, so *adding emphasis* changed nothing measurable. The moat is that
|
|
56
|
+
governance is **present and portable across hosts**, not that more governance
|
|
57
|
+
prose provably changes a model's behaviour.
|
|
58
|
+
- **Not deterministic on static hosts.** Compile-time governance is
|
|
59
|
+
model-cooperative; only hook-capable hosts get a hard block.
|
|
60
|
+
|
|
61
|
+
## Why it's hard to copy
|
|
62
|
+
|
|
63
|
+
A runtime-first system would have to re-author its governance to live in
|
|
64
|
+
config space and project into every host's native format — i.e. become a
|
|
65
|
+
content+governance layer — to match the reach. That is a different architecture,
|
|
66
|
+
not a feature bolt-on. Meanwhile this package adds runtime hooks as an opt-in
|
|
67
|
+
*superset* where hosts allow it, without depending on them for the floor.
|
|
68
|
+
|
|
69
|
+
## See also
|
|
70
|
+
|
|
71
|
+
- [`enforcement-by-host.md`](enforcement-by-host.md) — per-host enforcement mechanism.
|
|
72
|
+
- [`capability-matrix.md`](capability-matrix.md) — which artifact type projects to which host.
|
|
@@ -26,6 +26,7 @@ hierarchy, reduced-motion, focus indicator) is in `lint_design_quality`
|
|
|
26
26
|
|---|---|---|
|
|
27
27
|
| **Guidance** | Subjective taste — Visual / Typography / Color / Layout / Copy patterns, override judgment, the originality self-test | this catalog |
|
|
28
28
|
| **Enforcement** | Objective floors — Q1–Q6 (WCAG contrast, font-size, line-length, reduced-motion, heading hierarchy, focus indicator) as exit-code-2 CI | `lint_design_quality` |
|
|
29
|
+
| **Detection** | Deterministic *aesthetic-tell* flags (P0–P3, rebuttable via DESIGN.md) — the pattern-detectable subset of the entries below | `lint_design_slop` |
|
|
29
30
|
| **Audit method** | HOW to test WCAG (keyboard nav, ARIA, screen-reader procedures) | `accessibility-auditor` |
|
|
30
31
|
| **Stack manifestation** | Concrete Tailwind class / hex bans, shadcn defaults | `tailwind-engineer`, `react-shadcn-ui` (per `framework-neutrality-in-generic-skills`) |
|
|
31
32
|
| **Register** | Brand mode vs product mode — which patterns apply | `docs/guidelines/design-modes.md` |
|
|
@@ -34,6 +35,31 @@ This catalog is the canonical *index*; it states the pattern stack-agnostically.
|
|
|
34
35
|
The stack-specific bans live in the apply skills and link back here by entry ID.
|
|
35
36
|
The objective floors are cited from the linter, never re-eyeballed by an agent.
|
|
36
37
|
|
|
38
|
+
**Deterministic detector backing** (`lint_design_slop`, dependency-free, zero
|
|
39
|
+
runtime token cost): a pattern-detectable subset of these entries now has a
|
|
40
|
+
deterministic rule that `design-review` cites instead of re-deriving — `V1`,
|
|
41
|
+
`V3`, `V6`, `C2`, `C5`, `T4`, `T6`, `T7`, `L4`, `L8`, `M2`, `M4`, `CP1`, `CP2`,
|
|
42
|
+
plus the **Consistency Locks & layout caps** `V8`, `C6`, `L9`, `L10`
|
|
43
|
+
(rule ids `slop-<id>-*`, registry in `src/scripts/design_slop_rules.ts`). These
|
|
44
|
+
are **flags / rebuttable presumptions**, never hard blocks — a consumer
|
|
45
|
+
`DESIGN.md` that declares the pattern as intentional suppresses the flag. The
|
|
46
|
+
entries needing structural or aesthetic judgment (e.g. `T3` icon-tile stack,
|
|
47
|
+
`L2` three-identical-card grid, `V2` glassmorphism intent, `C7` theme-inversion)
|
|
48
|
+
stay agent-judgment-only and are deliberately **not** in the detector.
|
|
49
|
+
|
|
50
|
+
### Consistency Locks & layout caps (taste-dials roadmap)
|
|
51
|
+
|
|
52
|
+
Within-project invariants + repetition caps. Override = declare the value in
|
|
53
|
+
`DESIGN.md` (`## Consistency Locks` / a brutalist or uniform-grid style note).
|
|
54
|
+
|
|
55
|
+
| # | Pattern | Why it reads as AI-generated | Override condition |
|
|
56
|
+
|---|---|---|---|
|
|
57
|
+
| V8 | Shape Lock — ≥ 4 distinct corner-radius scales in one surface | A fragmented radius system reads as no shape decision | Declare the radius scale in `DESIGN.md`; a genuine multi-tier scale documents each tier |
|
|
58
|
+
| C6 | Colour Lock — ≥ 3 distinct saturated accent hue families | No single accent identity; reads as default-palette sprawl | Brand genuinely uses a multi-accent system, declared in `DESIGN.md` / `brand-to-tokens` |
|
|
59
|
+
| C7 | Theme inversion — a light island mid-dark surface (or vice versa) not driven by the theme system | Mid-surface inversion breaks the theme rhythm | Intentional inverted callout with a documented reason — **judgment-only**, design-review calls it (deterministic detection is unreliable) |
|
|
60
|
+
| L9 | Section monotony — ≥ 8 sections, < 4 distinct layout families | Uniform section rhythm collapses scannability | Deliberately uniform grid (e.g. a gallery), declared as such |
|
|
61
|
+
| L10 | Zigzag — > 2 consecutive image-left/text-right two-column sections | The alternating-zigzag landing-page tell | A genuinely sequenced walkthrough where alternation aids comprehension |
|
|
62
|
+
|
|
37
63
|
---
|
|
38
64
|
|
|
39
65
|
## Visual
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
# Design Canon — named-systems grounding index
|
|
2
|
+
|
|
3
|
+
> Lazy-loaded reference. Pull this when a brief names (or a project's
|
|
4
|
+
> `components.json`/deps signal) a published design system, type, or colour
|
|
5
|
+
> reference — so the design is **grounded in named canon**, not improvised.
|
|
6
|
+
> Used by `design-intelligence`; cross-linked from `typography-system`,
|
|
7
|
+
> `iconography`, `design-tokens`, `brand-to-tokens`, `icon-consistency`.
|
|
8
|
+
|
|
9
|
+
This is a thin **index** — names, one-line summaries, and a "pull this when"
|
|
10
|
+
trigger. It deliberately does **not** bundle the multi-megabyte specs; fetch
|
|
11
|
+
the live spec only when the trigger fires (the canonical URLs below are the
|
|
12
|
+
fetch targets). Precedence (per `brand-source-of-truth`): **consumer brand
|
|
13
|
+
tokens > confirmed session decisions > named canon (this index) > generated
|
|
14
|
+
corpus.** Canon is a gap-filler, never an override of a registered brand value.
|
|
15
|
+
|
|
16
|
+
## Design systems
|
|
17
|
+
|
|
18
|
+
| System | Signature traits (one-line) | Pull this when |
|
|
19
|
+
|---|---|---|
|
|
20
|
+
| **Material 3** (Google) | Dynamic colour from a seed (HCT), tonal palettes, elevation by tint not shadow, expressive motion (emphasized easing). | brief says "Material" / "Android"; deps `@mui/material`, `@angular/material`, `react-native-paper`. Spec: m3.material.io |
|
|
21
|
+
| **Apple HIG** | Platform-native clarity/deference/depth, SF type + Dynamic Type, system materials/vibrancy, restrained motion, large touch targets. | brief targets iOS/iPadOS/macOS/visionOS, "native Apple feel". Spec: developer.apple.com/design |
|
|
22
|
+
| **Fluent 2** (Microsoft) | Neutral-forward, acrylic/reveal materials, Segoe/Fluent type ramp, depth via subtle shadow, enterprise density. | brief says "Fluent"/"Windows"/"Office"; deps `@fluentui/*`. Spec: fluent2.microsoft.design |
|
|
23
|
+
| **Carbon** (IBM) | 2px grid, IBM Plex type, strong data-viz system, productive vs expressive themes, accessibility-first. | brief says "Carbon"/"IBM"/data-heavy enterprise; deps `@carbon/*`. Spec: carbondesignsystem.com |
|
|
24
|
+
| **Ant Design** | Dense enterprise/admin, comprehensive component set, token-based theming (v5), wireframe-driven. | brief says "Ant"/admin console/CRUD-heavy; deps `antd`. Spec: ant.design |
|
|
25
|
+
| **Atlassian** | Product-suite consistency, ADS tokens, calm neutral + bold primary, strong empty/loading patterns. | brief says "Atlassian"/Jira-like; deps `@atlaskit/*`. Spec: atlassian.design |
|
|
26
|
+
|
|
27
|
+
Grounding rule: surface the matching one-liner, then **offer to pull the live
|
|
28
|
+
spec** before committing to the system's conventions. Never reproduce a brand
|
|
29
|
+
book's marks; ground against the *system's* documented principles.
|
|
30
|
+
|
|
31
|
+
## Typography craft (foundry + theory)
|
|
32
|
+
|
|
33
|
+
A one-line index — point `typography-system` here; do not inline the content.
|
|
34
|
+
|
|
35
|
+
- **Theory:** Butterick's *Practical Typography* (practicaltypography.com — line-length, measure, hierarchy rules), *Thinking with Type*.
|
|
36
|
+
- **Calibration / "in use":** Typewolf, Fonts In Use (real pairings, not defaults).
|
|
37
|
+
- **Quality foundries** (beyond the AI-default fonts the anti-slop catalog T7 flags): Klim, Lineto, Colophon, Commercial Type, Grilli Type; libre: League of Movable Type, Google Fonts (chosen deliberately, not reflexively).
|
|
38
|
+
- Pull when: a brief calls for a distinctive type voice, or to escape the Inter/Roboto/Geist default.
|
|
39
|
+
|
|
40
|
+
## Colour references
|
|
41
|
+
|
|
42
|
+
Names + when-to-use — not the palettes themselves.
|
|
43
|
+
|
|
44
|
+
- **Accessibility / contrast:** WCAG contrast checkers (e.g. APCA, WebAIM) — verify text/non-text contrast (ties to `lint_design_quality` Q1 + anti-slop C4). Pull on any colour decision that must clear AA.
|
|
45
|
+
- **Culturally-specific palettes:** Nippon Colors (traditional Japanese), Chinese Colors — pull when the brand/audience is culturally situated and a Western default palette would read as generic.
|
|
46
|
+
- **Systematic palette tools:** the design systems above ship their own token palettes — prefer the system's palette over an ad-hoc picker when a system is in play.
|
|
47
|
+
|
|
48
|
+
## Icon systems
|
|
49
|
+
|
|
50
|
+
Each design system ships a matching icon set — prefer it when that system is in
|
|
51
|
+
play (consistency over novelty, per `icon-consistency`): **Material Symbols**
|
|
52
|
+
(Material 3, variable weight/fill/grade), **SF Symbols** (Apple HIG, weight-
|
|
53
|
+
matched to SF text), **Fluent UI System Icons** (Fluent), **Carbon icons**
|
|
54
|
+
(Carbon). Outside a named system, the `iconography` skill resolves a single
|
|
55
|
+
Iconify set (Lucide / Phosphor / Heroicons / Tabler) — pick one and stay in it.
|
|
56
|
+
Pull when: choosing or auditing an icon library.
|
|
57
|
+
|
|
58
|
+
## See also
|
|
59
|
+
|
|
60
|
+
- `docs/guidelines/design-antipatterns.md` — the anti-slop catalog (T7 default-font tell, C1/C5 palette tells) this canon helps you escape.
|
|
61
|
+
- `brand-source-of-truth` / `brand-consistency` — the precedence this index obeys.
|
|
62
|
+
- `design-intelligence` — the consumer that surfaces these on a brief signal.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@event4u/agent-config",
|
|
3
|
-
"version": "7.
|
|
3
|
+
"version": "7.5.0",
|
|
4
4
|
"description": "Universal AI Agent OS — audited skills, governance rules, commands, and templates for AI coding tools (Claude Code, Cursor, Windsurf, Copilot).",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"private": false,
|
|
@@ -81,6 +81,7 @@
|
|
|
81
81
|
"@typescript-eslint/parser": "^8.59.4",
|
|
82
82
|
"eslint": "^9.39.4",
|
|
83
83
|
"happy-dom": "^20.9.0",
|
|
84
|
+
"js-tiktoken": "^1.0.21",
|
|
84
85
|
"tsx": "^4.22.3",
|
|
85
86
|
"typescript": "^5.9.3",
|
|
86
87
|
"vite": "^5.4.21",
|
|
@@ -722,6 +722,27 @@ hooks:
|
|
|
722
722
|
injection_scan:
|
|
723
723
|
enabled: false
|
|
724
724
|
|
|
725
|
+
# PreToolUse RTK-wrap nudge (token-saving Phase 3). Default-OFF. When enabled
|
|
726
|
+
# AND `rtk` is on PATH (a live probe — not a self-reported flag), warns (exit
|
|
727
|
+
# 2, never blocks) "re-run wrapped with rtk" before a single verbose CLI
|
|
728
|
+
# command (git/npm/cargo/docker/…), so verbose output is filtered for 60–90%
|
|
729
|
+
# token saving. Skips completeness-critical / piped / compound commands and
|
|
730
|
+
# `git diff`. No-op when rtk is absent. Opt in once you have rtk installed.
|
|
731
|
+
rtk_wrap:
|
|
732
|
+
enabled: false
|
|
733
|
+
|
|
734
|
+
# PreToolUse anti-slop nudge (road-to-anti-slop-detector Phase 3). Default-OFF.
|
|
735
|
+
# When enabled, runs the lint_design_slop registry against about-to-be-written
|
|
736
|
+
# UI content (html/css/jsx/…) and WARNS (exit 2, never blocks) on P0/P1
|
|
737
|
+
# aesthetic tells — the side-stripe signature, gradient-text, magic z-index,
|
|
738
|
+
# etc. Flags are rebuttable: declare intent in DESIGN.md, design-slop-disable,
|
|
739
|
+
# or proceed. Anti-loop: a file::rule signature surfaced 3x goes silent so it
|
|
740
|
+
# never traps the agent. The universal gate is the lint_design_slop linter/CI;
|
|
741
|
+
# this hook is a host-limited convenience layer (pre_tool_use fires on ~2-3
|
|
742
|
+
# hosts). Opt in once you trust the signal-to-noise on your UI.
|
|
743
|
+
design_slop:
|
|
744
|
+
enabled: false
|
|
745
|
+
|
|
725
746
|
# --- Decision engine ---
|
|
726
747
|
#
|
|
727
748
|
# Controllable gates layered over the observability surface. Absent
|
|
@@ -0,0 +1,347 @@
|
|
|
1
|
+
#!/usr/bin/env tsx
|
|
2
|
+
/**
|
|
3
|
+
* `agent-config analyze-session` — post-session report from on-disk runtime
|
|
4
|
+
* state.
|
|
5
|
+
*
|
|
6
|
+
* Read-only diagnostic. No daemon, no network, no model calls. It reads two
|
|
7
|
+
* real data sources the work-engine / context-hygiene hook leave behind and
|
|
8
|
+
* prints a deterministic Markdown report:
|
|
9
|
+
*
|
|
10
|
+
* 1. the work_engine state envelope `.work-state.json` (version 1) — loaded
|
|
11
|
+
* via `load_state` from `explain_last/state_loader.ts`. Fields read:
|
|
12
|
+
* - `changes` — files touched (array of {kind, stack, file, summary}).
|
|
13
|
+
* - `outcomes` — directive → "success" / "blocked" / … .
|
|
14
|
+
* - `halts` — array of recorded halts.
|
|
15
|
+
* 2. `agents/runtime/state/context-hygiene.json` — fields read:
|
|
16
|
+
* - `tool_calls` (int)
|
|
17
|
+
* - `consecutive_same_tool`(int)
|
|
18
|
+
* - `loop_detected` (bool)
|
|
19
|
+
*
|
|
20
|
+
* There is NO general token/cost data source in this package (only
|
|
21
|
+
* video-specific telemetry), so the report deliberately does NOT report
|
|
22
|
+
* tokens or cost — it prints a single honest "not tracked" line instead.
|
|
23
|
+
*
|
|
24
|
+
* Exit codes (mirroring `cmd_explain`'s `last` subject):
|
|
25
|
+
* 0 report rendered (even when a source file is absent — graceful note).
|
|
26
|
+
* 1 the work-state file is missing or unreadable.
|
|
27
|
+
* 2 invocation error (bad project root, bad --state-file path).
|
|
28
|
+
*/
|
|
29
|
+
|
|
30
|
+
import * as fs from 'node:fs';
|
|
31
|
+
import * as path from 'node:path';
|
|
32
|
+
import process from 'node:process';
|
|
33
|
+
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
34
|
+
|
|
35
|
+
import { ProjectRootError, resolve_project_root } from '../_lib/agent_settings.js';
|
|
36
|
+
import { StateLoadError, load_state } from './explain_last/state_loader.js';
|
|
37
|
+
|
|
38
|
+
type Dict = Record<string, unknown>;
|
|
39
|
+
|
|
40
|
+
const _HERE = fileURLToPath(import.meta.url);
|
|
41
|
+
|
|
42
|
+
const CONTEXT_HYGIENE_RELATIVE = path.join(
|
|
43
|
+
'agents',
|
|
44
|
+
'runtime',
|
|
45
|
+
'state',
|
|
46
|
+
'context-hygiene.json',
|
|
47
|
+
);
|
|
48
|
+
|
|
49
|
+
/** Python `raise SystemExit(code)` analogue — propagates an explicit code. */
|
|
50
|
+
class SystemExitError extends Error {
|
|
51
|
+
constructor(public readonly code: number) {
|
|
52
|
+
super(`system-exit-${code}`);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/** argparse-style usage / help exit (code 2 / 0). Caught at the CLI entry. */
|
|
57
|
+
class ArgparseExit extends Error {
|
|
58
|
+
constructor(public readonly code: number) {
|
|
59
|
+
super(`argparse-exit-${code}`);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function eprint(line = ''): void {
|
|
64
|
+
process.stderr.write(line + '\n');
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function isPlainObject(value: unknown): value is Dict {
|
|
68
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function _resolve_root(arg: string | null): string {
|
|
72
|
+
try {
|
|
73
|
+
const [root] = resolve_project_root(arg, { cwd: process.cwd() });
|
|
74
|
+
return root;
|
|
75
|
+
} catch (exc) {
|
|
76
|
+
if (exc instanceof ProjectRootError) {
|
|
77
|
+
eprint(`❌ analyze-session: ${exc.message}`);
|
|
78
|
+
throw new SystemExitError(2);
|
|
79
|
+
}
|
|
80
|
+
throw exc;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/** `Path.relative_to(base)` — returns null when `p` is not under `base`. */
|
|
85
|
+
function relativeToOrNull(p: string, base: string): string | null {
|
|
86
|
+
const rel = path.relative(base, p);
|
|
87
|
+
if (rel === '') return rel;
|
|
88
|
+
if (rel.startsWith('..') || path.isAbsolute(rel)) {
|
|
89
|
+
return null;
|
|
90
|
+
}
|
|
91
|
+
return rel;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Read the context-hygiene snapshot. Returns the parsed object, or `null`
|
|
96
|
+
* when the file is absent / unreadable / not an object — the caller renders
|
|
97
|
+
* a graceful "not available" note rather than failing the whole report.
|
|
98
|
+
*/
|
|
99
|
+
function _load_context_hygiene(project_root: string): Dict | null {
|
|
100
|
+
const p = path.join(project_root, CONTEXT_HYGIENE_RELATIVE);
|
|
101
|
+
if (!fs.existsSync(p)) {
|
|
102
|
+
return null;
|
|
103
|
+
}
|
|
104
|
+
let text: string;
|
|
105
|
+
try {
|
|
106
|
+
text = fs.readFileSync(p, 'utf-8');
|
|
107
|
+
} catch {
|
|
108
|
+
return null;
|
|
109
|
+
}
|
|
110
|
+
let payload: unknown;
|
|
111
|
+
try {
|
|
112
|
+
payload = JSON.parse(text);
|
|
113
|
+
} catch {
|
|
114
|
+
return null;
|
|
115
|
+
}
|
|
116
|
+
return isPlainObject(payload) ? payload : null;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/** Render `value` as an integer string, or `'?'` when not a finite number. */
|
|
120
|
+
function intOrUnknown(value: unknown): string {
|
|
121
|
+
if (typeof value === 'number' && Number.isFinite(value)) {
|
|
122
|
+
return String(Math.trunc(value));
|
|
123
|
+
}
|
|
124
|
+
return '?';
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Build the deterministic Markdown report from already-loaded state.
|
|
129
|
+
*
|
|
130
|
+
* Pure function — no I/O, no clock, no environment. Both inputs come from the
|
|
131
|
+
* caller so the report is unit-testable in isolation.
|
|
132
|
+
*
|
|
133
|
+
* @param state the parsed `.work-state.json` (version 1).
|
|
134
|
+
* @param hygiene the parsed context-hygiene snapshot, or `null` when absent.
|
|
135
|
+
*/
|
|
136
|
+
export function render_report(state: Dict, hygiene: Dict | null): string {
|
|
137
|
+
const lines: string[] = [];
|
|
138
|
+
lines.push('# Session analysis');
|
|
139
|
+
lines.push('');
|
|
140
|
+
|
|
141
|
+
// --- Files touched (from state.changes) ---
|
|
142
|
+
const changes = Array.isArray(state['changes']) ? (state['changes'] as unknown[]) : [];
|
|
143
|
+
lines.push(`## Files touched (${changes.length})`);
|
|
144
|
+
lines.push('');
|
|
145
|
+
if (changes.length === 0) {
|
|
146
|
+
lines.push('_No files recorded as changed._');
|
|
147
|
+
} else {
|
|
148
|
+
for (const change of changes) {
|
|
149
|
+
if (!isPlainObject(change)) continue;
|
|
150
|
+
const file = typeof change['file'] === 'string' ? change['file'] : '(unknown file)';
|
|
151
|
+
const summary =
|
|
152
|
+
typeof change['summary'] === 'string' && change['summary'].length > 0
|
|
153
|
+
? ` — ${change['summary']}`
|
|
154
|
+
: '';
|
|
155
|
+
lines.push(`- \`${file}\`${summary}`);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
lines.push('');
|
|
159
|
+
|
|
160
|
+
// --- Per-directive outcomes (from state.outcomes) ---
|
|
161
|
+
const outcomes = isPlainObject(state['outcomes']) ? (state['outcomes'] as Dict) : {};
|
|
162
|
+
const directives = Object.keys(outcomes).sort(); // deterministic order
|
|
163
|
+
const blocked = directives.filter((d) => outcomes[d] === 'blocked');
|
|
164
|
+
lines.push(`## Outcomes (${directives.length})`);
|
|
165
|
+
lines.push('');
|
|
166
|
+
if (directives.length === 0) {
|
|
167
|
+
lines.push('_No directive outcomes recorded._');
|
|
168
|
+
} else {
|
|
169
|
+
for (const directive of directives) {
|
|
170
|
+
const result = outcomes[directive];
|
|
171
|
+
const marker = result === 'blocked' ? '⚠️ ' : '';
|
|
172
|
+
lines.push(`- ${marker}\`${directive}\`: ${String(result)}`);
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
lines.push('');
|
|
176
|
+
if (blocked.length > 0) {
|
|
177
|
+
lines.push(`**Blocked directives (${blocked.length}):** ${blocked.join(', ')}`);
|
|
178
|
+
lines.push('');
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
// --- Halts (from state.halts) ---
|
|
182
|
+
const halts = Array.isArray(state['halts']) ? (state['halts'] as unknown[]) : [];
|
|
183
|
+
lines.push(`## Halts (${halts.length})`);
|
|
184
|
+
lines.push('');
|
|
185
|
+
if (halts.length === 0) {
|
|
186
|
+
lines.push('_No halts recorded._');
|
|
187
|
+
} else {
|
|
188
|
+
lines.push(`This session recorded ${halts.length} halt(s).`);
|
|
189
|
+
}
|
|
190
|
+
lines.push('');
|
|
191
|
+
|
|
192
|
+
// --- Tool activity (from context-hygiene) ---
|
|
193
|
+
lines.push('## Tool activity');
|
|
194
|
+
lines.push('');
|
|
195
|
+
if (hygiene === null) {
|
|
196
|
+
lines.push('_Context-hygiene snapshot not available._');
|
|
197
|
+
} else {
|
|
198
|
+
const toolCalls = intOrUnknown(hygiene['tool_calls']);
|
|
199
|
+
const consecutive = intOrUnknown(hygiene['consecutive_same_tool']);
|
|
200
|
+
const loop = hygiene['loop_detected'] === true;
|
|
201
|
+
lines.push(`- Tool calls: ${toolCalls}`);
|
|
202
|
+
lines.push(`- Consecutive same-tool calls: ${consecutive}`);
|
|
203
|
+
lines.push(`- Loop detected: ${loop ? 'yes' : 'no'}`);
|
|
204
|
+
}
|
|
205
|
+
lines.push('');
|
|
206
|
+
|
|
207
|
+
// --- Honest token/cost note (no per-session source in this package) ---
|
|
208
|
+
lines.push('## Token / cost');
|
|
209
|
+
lines.push('');
|
|
210
|
+
lines.push('Token/cost: not tracked (no per-session source).');
|
|
211
|
+
lines.push('');
|
|
212
|
+
|
|
213
|
+
return lines.join('\n');
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
function _analyze_session(project_root: string, state_file: string | null): number {
|
|
217
|
+
const target_state = state_file || path.join(project_root, '.work-state.json');
|
|
218
|
+
let state: Dict;
|
|
219
|
+
try {
|
|
220
|
+
state = load_state(target_state) as Dict;
|
|
221
|
+
} catch (exc) {
|
|
222
|
+
if (exc instanceof StateLoadError) {
|
|
223
|
+
// Never print absolute paths in errors (username leakage).
|
|
224
|
+
const rel = relativeToOrNull(target_state, project_root);
|
|
225
|
+
const shown = rel !== null ? rel : path.basename(target_state);
|
|
226
|
+
const msg = exc.message.split(target_state).join(shown);
|
|
227
|
+
eprint(`❌ analyze-session: ${msg}`);
|
|
228
|
+
return exc.exitCode;
|
|
229
|
+
}
|
|
230
|
+
throw exc;
|
|
231
|
+
}
|
|
232
|
+
const hygiene = _load_context_hygiene(project_root);
|
|
233
|
+
process.stdout.write(render_report(state, hygiene));
|
|
234
|
+
return 0;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
interface Opts {
|
|
238
|
+
project: string | null;
|
|
239
|
+
state_file: string | null;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
const PROG = 'agent-config analyze-session';
|
|
243
|
+
const USAGE = `usage: ${PROG} [-h] [--project PROJECT] [--state-file STATE_FILE]\n`;
|
|
244
|
+
|
|
245
|
+
const _HELP = `\
|
|
246
|
+
usage: ${PROG} [--project PATH] [--state-file PATH]
|
|
247
|
+
|
|
248
|
+
Print a post-session report from on-disk runtime state. Read-only;
|
|
249
|
+
no daemon, no network, no model calls.
|
|
250
|
+
|
|
251
|
+
Sources read:
|
|
252
|
+
.work-state.json (version 1)
|
|
253
|
+
· changes — files touched
|
|
254
|
+
· outcomes — per-directive success / blocked
|
|
255
|
+
· halts — recorded halts
|
|
256
|
+
agents/runtime/state/context-hygiene.json
|
|
257
|
+
· tool_calls · consecutive_same_tool · loop_detected
|
|
258
|
+
|
|
259
|
+
Token/cost is NOT reported — this package has no per-session token
|
|
260
|
+
source (only video-specific telemetry).
|
|
261
|
+
|
|
262
|
+
options:
|
|
263
|
+
--project PATH project root (defaults to anchor walk from cwd)
|
|
264
|
+
--state-file PATH .work-state.json path (default <root>/.work-state.json)
|
|
265
|
+
-h, --help show this help and exit
|
|
266
|
+
|
|
267
|
+
exit codes:
|
|
268
|
+
0 report rendered (a missing source file is noted, not fatal)
|
|
269
|
+
1 no recent run found (state file missing or unreadable)
|
|
270
|
+
2 invocation error (bad project root, bad --state-file path)
|
|
271
|
+
`;
|
|
272
|
+
|
|
273
|
+
function _argError(msg: string): never {
|
|
274
|
+
process.stderr.write(USAGE);
|
|
275
|
+
process.stderr.write(`${PROG}: error: ${msg}\n`);
|
|
276
|
+
throw new ArgparseExit(2);
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
function _parse(argv: string[]): Opts {
|
|
280
|
+
const opts: Opts = { project: null, state_file: null };
|
|
281
|
+
let i = 0;
|
|
282
|
+
while (i < argv.length) {
|
|
283
|
+
const a = argv[i] as string;
|
|
284
|
+
if (a === '-h' || a === '--help') {
|
|
285
|
+
process.stdout.write(_HELP);
|
|
286
|
+
throw new ArgparseExit(0);
|
|
287
|
+
}
|
|
288
|
+
const eq = a.startsWith('--') ? a.indexOf('=') : -1;
|
|
289
|
+
const flag = eq >= 0 ? a.slice(0, eq) : a;
|
|
290
|
+
const inlineVal = eq >= 0 ? a.slice(eq + 1) : null;
|
|
291
|
+
if (flag === '--project' || flag === '--state-file') {
|
|
292
|
+
const dest = flag === '--project' ? 'project' : 'state_file';
|
|
293
|
+
let value: string;
|
|
294
|
+
if (inlineVal !== null) {
|
|
295
|
+
value = inlineVal;
|
|
296
|
+
} else {
|
|
297
|
+
if (i + 1 >= argv.length) {
|
|
298
|
+
_argError(`argument ${flag}: expected one argument`);
|
|
299
|
+
}
|
|
300
|
+
value = argv[i + 1] as string;
|
|
301
|
+
i += 1;
|
|
302
|
+
}
|
|
303
|
+
opts[dest] = value;
|
|
304
|
+
i += 1;
|
|
305
|
+
continue;
|
|
306
|
+
}
|
|
307
|
+
_argError(`unrecognized arguments: ${a}`);
|
|
308
|
+
}
|
|
309
|
+
return opts;
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
export function main(argv: string[] | null = null): number {
|
|
313
|
+
const argv_list = argv !== null ? Array.from(argv) : process.argv.slice(2);
|
|
314
|
+
const opts = _parse(argv_list);
|
|
315
|
+
let project_root: string;
|
|
316
|
+
try {
|
|
317
|
+
project_root = _resolve_root(opts.project);
|
|
318
|
+
} catch (exc) {
|
|
319
|
+
if (exc instanceof SystemExitError) {
|
|
320
|
+
return exc.code;
|
|
321
|
+
}
|
|
322
|
+
throw exc;
|
|
323
|
+
}
|
|
324
|
+
return _analyze_session(project_root, opts.state_file);
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
// --- CLI entry ---
|
|
328
|
+
|
|
329
|
+
const _isCliEntry =
|
|
330
|
+
process.argv[1] !== undefined &&
|
|
331
|
+
import.meta.url === pathToFileURL(path.resolve(process.argv[1])).href;
|
|
332
|
+
if (_isCliEntry || process.argv[1] === _HERE) {
|
|
333
|
+
try {
|
|
334
|
+
process.exitCode = main(process.argv.slice(2));
|
|
335
|
+
} catch (e) {
|
|
336
|
+
if (e instanceof ArgparseExit) {
|
|
337
|
+
process.exitCode = e.code;
|
|
338
|
+
} else if (e instanceof SystemExitError) {
|
|
339
|
+
process.exitCode = e.code;
|
|
340
|
+
} else {
|
|
341
|
+
throw e;
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
export { _parse, _resolve_root, _load_context_hygiene, _analyze_session, ArgparseExit, SystemExitError };
|
|
347
|
+
export type { Opts };
|
|
@@ -933,6 +933,13 @@ cmd_explain() {
|
|
|
933
933
|
exec_ts "$PACKAGE_ROOT/src/scripts/_cli/cmd_explain.ts" "$@"
|
|
934
934
|
}
|
|
935
935
|
|
|
936
|
+
# `agent-config analyze-session` — read-only post-session report from on-disk
|
|
937
|
+
# runtime state (.work-state.json + context-hygiene.json). No daemon, no
|
|
938
|
+
# network, no model calls. See scripts/_cli/cmd_analyze_session.ts.
|
|
939
|
+
cmd_analyze_session() {
|
|
940
|
+
exec_ts "$PACKAGE_ROOT/src/scripts/_cli/cmd_analyze_session.ts" "$@"
|
|
941
|
+
}
|
|
942
|
+
|
|
936
943
|
main() {
|
|
937
944
|
local cmd="${1-}"
|
|
938
945
|
[[ $# -gt 0 ]] && shift || true
|
|
@@ -991,6 +998,7 @@ main() {
|
|
|
991
998
|
doctor) cmd_doctor "$@" ;;
|
|
992
999
|
versions) cmd_versions "$@" ;;
|
|
993
1000
|
explain) cmd_explain "$@" ;;
|
|
1001
|
+
analyze-session) cmd_analyze_session "$@" ;;
|
|
994
1002
|
help|--help|-h|"")
|
|
995
1003
|
# Optional `--tier=0|1|all` filter (default 0).
|
|
996
1004
|
local tier_arg="0"
|