@event4u/agent-config 8.13.0 → 9.0.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 +1 -1
- package/CHANGELOG.md +136 -0
- package/README.md +1 -1
- package/dist/agent-src/commands/agent-handoff.md +35 -0
- package/dist/agent-src/commands/agents/init.md +1 -0
- package/dist/agent-src/commands/agents/optimize.md +1 -0
- package/dist/agent-src/commands/agents/user/accept.md +3 -2
- package/dist/agent-src/commands/agents/user/init.md +5 -4
- package/dist/agent-src/commands/agents/user/update.md +3 -2
- package/dist/agent-src/commands/agents/user.md +1 -0
- package/dist/agent-src/commands/agents.md +1 -0
- package/dist/agent-src/commands/analytics/show.md +1 -0
- package/dist/agent-src/commands/analytics.md +1 -0
- package/dist/agent-src/commands/analyze/decision.md +1 -0
- package/dist/agent-src/commands/analyze/incident.md +1 -0
- package/dist/agent-src/commands/analyze/near-miss.md +1 -0
- package/dist/agent-src/commands/analyze/postmortem.md +1 -0
- package/dist/agent-src/commands/analyze/premortem.md +1 -0
- package/dist/agent-src/commands/analyze/reference-repo.md +1 -0
- package/dist/agent-src/commands/analyze.md +3 -2
- package/dist/agent-src/commands/brand/identity.md +6 -5
- package/dist/agent-src/commands/brand/review.md +6 -5
- package/dist/agent-src/commands/brand/strategy.md +6 -4
- package/dist/agent-src/commands/brand/tokens.md +7 -6
- package/dist/agent-src/commands/brand/voice.md +5 -4
- package/dist/agent-src/commands/brand.md +9 -7
- package/dist/agent-src/commands/bug/fix.md +1 -0
- package/dist/agent-src/commands/bug/investigate.md +1 -0
- package/dist/agent-src/commands/bug.md +2 -1
- package/dist/agent-src/commands/challenge-me/vision.md +1 -0
- package/dist/agent-src/commands/challenge-me/with-docs.md +1 -0
- package/dist/agent-src/commands/challenge-me.md +28 -6
- package/dist/agent-src/commands/chat-history.md +1 -0
- package/dist/agent-src/commands/check-current-md.md +1 -0
- package/dist/agent-src/commands/commit.md +1 -0
- package/dist/agent-src/commands/context/create.md +3 -2
- package/dist/agent-src/commands/context.md +1 -0
- package/dist/agent-src/commands/cost/profile.md +1 -0
- package/dist/agent-src/commands/cost.md +2 -1
- package/dist/agent-src/commands/council/analysis.md +1 -0
- package/dist/agent-src/commands/council/debate.md +1 -0
- package/dist/agent-src/commands/council/default.md +21 -20
- package/dist/agent-src/commands/council/design.md +1 -0
- package/dist/agent-src/commands/council/optimize.md +1 -0
- package/dist/agent-src/commands/council/pr.md +1 -0
- package/dist/agent-src/commands/council.md +1 -0
- package/dist/agent-src/commands/estimate-ticket.md +1 -0
- package/dist/agent-src/commands/feature/dev.md +1 -0
- package/dist/agent-src/commands/feature/explore.md +1 -0
- package/dist/agent-src/commands/feature/plan.md +1 -0
- package/dist/agent-src/commands/feature/refactor.md +1 -0
- package/dist/agent-src/commands/feature/roadmap.md +1 -0
- package/dist/agent-src/commands/feature.md +2 -1
- package/dist/agent-src/commands/fix/pr-comments.md +19 -0
- package/dist/agent-src/commands/fix.md +2 -1
- package/dist/agent-src/commands/ghostwriter/delete.md +1 -0
- package/dist/agent-src/commands/ghostwriter/fetch.md +1 -0
- package/dist/agent-src/commands/ghostwriter/show.md +1 -0
- package/dist/agent-src/commands/ghostwriter/write.md +1 -0
- package/dist/agent-src/commands/ghostwriter.md +1 -0
- package/dist/agent-src/commands/grill-me.md +17 -12
- package/dist/agent-src/commands/humanize.md +1 -0
- package/dist/agent-src/commands/image/analyse.md +2 -1
- package/dist/agent-src/commands/image/create.md +1 -0
- package/dist/agent-src/commands/image/verify.md +1 -0
- package/dist/agent-src/commands/image.md +16 -12
- package/dist/agent-src/commands/implement-ticket.md +1 -0
- package/dist/agent-src/commands/judge/on-diff.md +1 -0
- package/dist/agent-src/commands/judge/solo.md +1 -0
- package/dist/agent-src/commands/judge/steps.md +1 -0
- package/dist/agent-src/commands/judge.md +1 -0
- package/dist/agent-src/commands/knowledge/cross-repo.md +1 -0
- package/dist/agent-src/commands/knowledge/forget.md +1 -0
- package/dist/agent-src/commands/knowledge/ingest.md +1 -0
- package/dist/agent-src/commands/knowledge/list.md +10 -0
- package/dist/agent-src/commands/knowledge.md +1 -0
- package/dist/agent-src/commands/memory/learn-low-impact.md +1 -0
- package/dist/agent-src/commands/memory/mine-session.md +1 -0
- package/dist/agent-src/commands/memory/promote.md +3 -2
- package/dist/agent-src/commands/memory.md +1 -0
- package/dist/agent-src/commands/mission/upgrade.md +45 -24
- package/dist/agent-src/commands/mode.md +1 -0
- package/dist/agent-src/commands/module/create.md +1 -0
- package/dist/agent-src/commands/module/explore.md +1 -0
- package/dist/agent-src/commands/module.md +1 -0
- package/dist/agent-src/commands/optimize/agents-dir.md +1 -0
- package/dist/agent-src/commands/optimize/project.md +1 -0
- package/dist/agent-src/commands/optimize/prompt.md +1 -0
- package/dist/agent-src/commands/optimize.md +2 -1
- package/dist/agent-src/commands/orchestrate.md +1 -0
- package/dist/agent-src/commands/override.md +1 -0
- package/dist/agent-src/commands/package.md +2 -1
- package/dist/agent-src/commands/post-as/ghostwriter.md +1 -0
- package/dist/agent-src/commands/post-as/me.md +1 -0
- package/dist/agent-src/commands/post-as.md +1 -0
- package/dist/agent-src/commands/pr/create/description-only.md +1 -0
- package/dist/agent-src/commands/pr/create.md +19 -17
- package/dist/agent-src/commands/prediction-pool.md +7 -6
- package/dist/agent-src/commands/prepare-for-review.md +1 -0
- package/dist/agent-src/commands/profile/activate.md +1 -0
- package/dist/agent-src/commands/profile/deactivate.md +1 -0
- package/dist/agent-src/commands/profile.md +1 -0
- package/dist/agent-src/commands/project.md +2 -1
- package/dist/agent-src/commands/refine-ticket.md +7 -0
- package/dist/agent-src/commands/research/deep.md +1 -0
- package/dist/agent-src/commands/research.md +1 -0
- package/dist/agent-src/commands/review/routing.md +1 -0
- package/dist/agent-src/commands/review.md +2 -1
- package/dist/agent-src/commands/roadmap/ai-council.md +1 -0
- package/dist/agent-src/commands/roadmap/materialize.md +1 -0
- package/dist/agent-src/commands/roadmap/process-full.md +1 -0
- package/dist/agent-src/commands/roadmap/process-phase.md +1 -0
- package/dist/agent-src/commands/roadmap/process-step.md +1 -0
- package/dist/agent-src/commands/roadmap.md +1 -0
- package/dist/agent-src/commands/security-audit-config.md +2 -1
- package/dist/agent-src/commands/skill/preview.md +1 -0
- package/dist/agent-src/commands/skill.md +1 -0
- package/dist/agent-src/commands/skills/discover.md +1 -0
- package/dist/agent-src/commands/skills.md +1 -0
- package/dist/agent-src/commands/sync/agent-settings.md +1 -0
- package/dist/agent-src/commands/sync.md +2 -1
- package/dist/agent-src/commands/team/adversarial.md +3 -2
- package/dist/agent-src/commands/team/delegate.md +3 -2
- package/dist/agent-src/commands/team/review.md +3 -2
- package/dist/agent-src/commands/team/status.md +49 -18
- package/dist/agent-src/commands/team-knowledge.md +8 -6
- package/dist/agent-src/commands/team.md +21 -16
- package/dist/agent-src/commands/tests/e2e-heal.md +1 -0
- package/dist/agent-src/commands/tests/e2e-plan.md +1 -0
- package/dist/agent-src/commands/tests/execute.md +1 -0
- package/dist/agent-src/commands/tests.md +2 -1
- package/dist/agent-src/commands/update-form-request-messages.md +1 -0
- package/dist/agent-src/commands/upstream-contribute.md +1 -0
- package/dist/agent-src/commands/video/from-script.md +3 -2
- package/dist/agent-src/commands/video/from-song.md +22 -21
- package/dist/agent-src/commands/video/scene.md +1 -0
- package/dist/agent-src/commands/video/stitch.md +2 -1
- package/dist/agent-src/commands/video/storyboard.md +1 -0
- package/dist/agent-src/commands/video.md +1 -0
- package/dist/agent-src/commands/work.md +1 -0
- package/dist/agent-src/commands/worktree/cleanup.md +8 -7
- package/dist/agent-src/commands/worktree/create.md +1 -0
- package/dist/agent-src/commands/worktree/verify.md +1 -0
- package/dist/agent-src/commands/worktree.md +1 -0
- package/dist/agent-src/rules/active-remediation.md +12 -36
- package/dist/agent-src/rules/artifact-drafting-protocol.md +7 -64
- package/dist/agent-src/rules/context-hygiene.md +19 -113
- package/dist/agent-src/rules/design-fidelity.md +4 -60
- package/dist/agent-src/rules/domain-adoption-policy.md +10 -106
- package/dist/agent-src/rules/framework-neutrality-in-generic-skills.md +12 -60
- package/dist/agent-src/rules/improve-before-implement.md +4 -0
- package/dist/agent-src/rules/minimal-safe-diff.md +6 -86
- package/dist/agent-src/rules/size-enforcement.md +10 -0
- package/dist/agent-src/rules/tool-safety.md +17 -3
- package/dist/agent-src/skills/agent-security-review/SKILL.md +12 -1
- package/dist/agent-src/skills/ai-council/SKILL.md +11 -0
- package/dist/agent-src/skills/bug-analyzer/SKILL.md +24 -2
- package/dist/agent-src/skills/code-review/SKILL.md +105 -93
- package/dist/agent-src/skills/code-review/checklists/backend.md +50 -0
- package/dist/agent-src/skills/code-review/checklists/database.md +19 -0
- package/dist/agent-src/skills/code-review/checklists/dependency.md +18 -0
- package/dist/agent-src/skills/code-review/checklists/docs.md +14 -0
- package/dist/agent-src/skills/code-review/checklists/frontend.md +19 -0
- package/dist/agent-src/skills/code-review/checklists/infra.md +17 -0
- package/dist/agent-src/skills/code-review/checklists/producing-the-review.md +62 -0
- package/dist/agent-src/skills/code-review/checklists/test-only.md +13 -0
- package/dist/agent-src/skills/code-review/evals/evals.json +53 -15
- package/dist/agent-src/skills/condense-memory/SKILL.md +3 -2
- package/dist/agent-src/skills/conventional-commits-writing/evals/evals.json +15 -0
- package/dist/agent-src/skills/docker/SKILL.md +10 -0
- package/dist/agent-src/skills/docx-authoring/SKILL.md +102 -0
- package/dist/agent-src/skills/github-ci/evals/evals.json +25 -0
- package/dist/agent-src/skills/humanizer/SKILL.md +79 -3
- package/dist/agent-src/skills/humanizer/references/anti-aiisms.md +112 -0
- package/dist/agent-src/skills/humanizer/references/fixtures.md +70 -0
- package/dist/agent-src/skills/judge-bug-hunter/SKILL.md +7 -1
- package/dist/agent-src/skills/laravel/SKILL.md +10 -0
- package/dist/agent-src/skills/launch-readiness/SKILL.md +35 -0
- package/dist/agent-src/skills/merge-conflicts/SKILL.md +30 -0
- package/dist/agent-src/skills/pdf-tools/SKILL.md +101 -0
- package/dist/agent-src/skills/quality-tools/evals/evals.json +15 -0
- package/dist/agent-src/skills/security-audit/SKILL.md +29 -0
- package/dist/agent-src/skills/security-maturity-assessment/SKILL.md +90 -0
- package/dist/agent-src/skills/security-maturity-assessment/evals/triggers.json +52 -0
- package/dist/agent-src/skills/skill-writing/SKILL.md +79 -0
- package/dist/agent-src/skills/spreadsheet-authoring/SKILL.md +11 -3
- package/dist/agent-src/skills/subagent-orchestration/schemas/team-review-status.json +103 -0
- package/dist/agent-src/skills/terraform/SKILL.md +10 -0
- package/dist/agent-src/skills/test-driven-development/SKILL.md +31 -0
- package/dist/agent-src/skills/threat-modeling/SKILL.md +15 -0
- package/dist/agent-src/templates/agents/agent-project-settings.example.yml +1 -1
- package/dist/agent-src/templates/scripts/telemetry/aggregator.ts +0 -0
- package/dist/agent-src/templates/scripts/telemetry/engagement.ts +30 -0
- package/dist/agent-src/templates/scripts/telemetry/report_renderer.ts +22 -9
- package/dist/agent-src/templates/scripts/telemetry_record.ts +5 -0
- package/dist/discovery/deprecation-report.md +1 -1
- package/dist/discovery/discovery-manifest.json +256 -190
- package/dist/discovery/discovery-manifest.json.sha256 +1 -1
- package/dist/discovery/discovery-manifest.summary.md +5 -5
- package/dist/discovery/orphan-report.md +1 -1
- package/dist/discovery/packs.json +20 -17
- package/dist/discovery/trust-report.md +3 -3
- package/dist/discovery/workspaces.json +16 -13
- package/dist/install/install.mjs +10 -2
- package/dist/mcp/registry-manifest.json +3 -3
- package/dist/router.json +1 -1
- package/dist/scripts/condense.js +5 -0
- package/dist/scripts/condense.js.map +1 -1
- package/dist/server/schemas/settings.js +5 -1
- package/dist/server/schemas/settings.js.map +1 -1
- package/docs/CLAIMS.md +8 -1
- package/docs/SKILL_CENSUS.md +8 -0
- package/docs/architecture.md +2 -2
- package/docs/benchmark.md +67 -2
- package/docs/catalog.md +2 -2
- package/docs/contracts/ai-council-config.md +9 -2
- package/docs/contracts/ai-team-config.md +37 -4
- package/docs/contracts/prelaunch-diagnostics.md +151 -0
- package/docs/contracts/release-pr-gating.md +1 -0
- package/docs/decisions/ADR-049-configuration-trust-boundary.md +9 -1
- package/docs/featured-skills.md +2 -2
- package/docs/getting-started-by-role.md +1 -1
- package/docs/getting-started.md +16 -5
- package/docs/governance-advantage.md +1 -1
- package/docs/guidelines/agent-infra/active-remediation-mechanics.md +45 -0
- package/docs/guidelines/agent-infra/agent-interaction-and-decision-quality.md +28 -0
- package/docs/guidelines/agent-infra/artifact-drafting-protocol-mechanics.md +76 -0
- package/docs/guidelines/agent-infra/context-hygiene-mechanics.md +90 -0
- package/docs/guidelines/agent-infra/domain-adoption-gates.md +112 -0
- package/docs/guidelines/agent-infra/framework-neutrality-patterns.md +66 -0
- package/docs/guidelines/agent-infra/minimal-safe-diff-mechanics.md +99 -0
- package/docs/guidelines/agent-infra/skill-quality-checklist.md +13 -0
- package/docs/guidelines/design-fidelity-mechanics.md +76 -0
- package/docs/parity/command-frontmatter.md +60 -0
- package/docs/proof.md +6 -5
- package/package.json +1 -1
- package/src/agent-src/templates/scripts/telemetry/aggregator.ts +0 -0
- package/src/agent-src/templates/scripts/telemetry/engagement.ts +30 -0
- package/src/agent-src/templates/scripts/telemetry/report_renderer.ts +22 -9
- package/src/agent-src/templates/scripts/telemetry_record.ts +5 -0
- package/src/config/agent-settings.template.yml +34 -10
- package/src/config/prelaunch-areas.yml +36 -0
- package/src/scripts/_cli/cmd_doctor.ts +118 -26
- package/src/scripts/_lib/claude_plugin.ts +77 -2
- package/src/scripts/_lib/orchestration_record.ts +5 -0
- package/src/scripts/_lib/tool_adapter_registry.ts +120 -0
- package/src/scripts/ai_team/config.ts +73 -1
- package/src/scripts/ai_team/review_gate.ts +636 -0
- package/src/scripts/ai_team/team_dispatch.ts +612 -0
- package/src/scripts/bench_ab_task_runner.ts +11 -1
- package/src/scripts/bench_parity_count.ts +279 -0
- package/src/scripts/build_linear_digest.ts +3 -1
- package/src/scripts/check_bridge_derivation.ts +172 -0
- package/src/scripts/check_consumer_scope_flip.ts +179 -0
- package/src/scripts/check_council_config_location.ts +115 -15
- package/src/scripts/check_host_loadability.ts +81 -0
- package/src/scripts/condense.ts +5 -0
- package/src/scripts/hook_manifest.yaml +12 -1
- package/src/scripts/lint_handoffs.ts +42 -0
- package/src/scripts/lint_skill_descriptions.ts +228 -0
- package/src/scripts/lint_skill_descriptions_allowlist.json +1 -0
- package/src/scripts/lint_skill_scripts_readonly.ts +145 -0
- package/src/scripts/lint_skill_scripts_readonly_allowlist.json +8 -0
- package/src/scripts/lint_supported_tools_matrix.ts +35 -0
- package/src/scripts/optimize_skill_description.ts +144 -0
- package/src/scripts/orchestration_record.ts +2 -1
- package/src/scripts/prelaunch_diagnostics.ts +248 -0
- package/src/scripts/run_skill_evals.ts +70 -4
- package/src/scripts/schemas/command.schema.json +7 -0
- package/src/scripts/schemas/evals.schema.json +105 -14
- package/src/scripts/schemas/prelaunch-report.schema.json +107 -0
- package/src/scripts/schemas/skill.schema.json +18 -1
- package/src/scripts/schemas/subagent.schema.json +6 -0
- package/src/scripts/skill_linter.ts +43 -3
- package/src/scripts/smoke_host_loadability.sh +73 -0
- package/src/scripts/team_review_gate_hook.ts +115 -0
- package/src/scripts/trigger_eval_grandfather.json +1 -2
- package/src/scripts/utilization_report.ts +225 -0
- package/src/server/schemas/settings.ts +9 -1
|
@@ -63,6 +63,7 @@ interface RecordArgs {
|
|
|
63
63
|
boundary: string;
|
|
64
64
|
consulted: string[] | null;
|
|
65
65
|
applied: string[] | null;
|
|
66
|
+
loaded: string[] | null;
|
|
66
67
|
outcome: string[] | null;
|
|
67
68
|
ts: string;
|
|
68
69
|
payload_file: string | null;
|
|
@@ -78,6 +79,7 @@ function _build_event_from_args(args: RecordArgs): EngagementEvent {
|
|
|
78
79
|
boundary_kind: args.boundary,
|
|
79
80
|
consulted: _parse_kv_list(args.consulted ?? []),
|
|
80
81
|
applied: _parse_kv_list(args.applied ?? []),
|
|
82
|
+
loaded: args.loaded && args.loaded.length > 0 ? _parse_kv_list(args.loaded) : null,
|
|
81
83
|
outcomes: args.outcome && args.outcome.length > 0 ? [...args.outcome] : null,
|
|
82
84
|
});
|
|
83
85
|
}
|
|
@@ -101,6 +103,7 @@ function _build_event_from_payload(raw: string): EngagementEvent {
|
|
|
101
103
|
boundary_kind: (d['boundary_kind'] as string) ?? '',
|
|
102
104
|
consulted: _orEmpty(d['consulted']),
|
|
103
105
|
applied: _orEmpty(d['applied']),
|
|
106
|
+
loaded: d['loaded'] === undefined || d['loaded'] === null ? null : _orEmpty(d['loaded']),
|
|
104
107
|
outcomes: (d['outcomes'] as string[] | null | undefined) ?? null,
|
|
105
108
|
tokens_estimate: (d['tokens_estimate'] as Record<string, number> | null | undefined) ?? null,
|
|
106
109
|
});
|
|
@@ -121,6 +124,7 @@ function _parseArgs(argv: string[]): RecordArgs {
|
|
|
121
124
|
boundary: 'task',
|
|
122
125
|
consulted: null,
|
|
123
126
|
applied: null,
|
|
127
|
+
loaded: null,
|
|
124
128
|
outcome: null,
|
|
125
129
|
ts: '',
|
|
126
130
|
payload_file: null,
|
|
@@ -155,6 +159,7 @@ function _parseArgs(argv: string[]): RecordArgs {
|
|
|
155
159
|
}
|
|
156
160
|
a.boundary = v;
|
|
157
161
|
} else if (tok === '--consulted') (a.consulted ??= []).push(next());
|
|
162
|
+
else if (tok === '--loaded') (a.loaded ??= []).push(next());
|
|
158
163
|
else if (tok === '--applied') (a.applied ??= []).push(next());
|
|
159
164
|
else if (tok === '--outcome') {
|
|
160
165
|
const v = next();
|
|
@@ -62,10 +62,15 @@ projection:
|
|
|
62
62
|
# rules ALWAYS project (unconditional by definition); untagged rules fail
|
|
63
63
|
# safe and ship. Example consumer scope:
|
|
64
64
|
# rule_workspaces: [engineering]
|
|
65
|
-
#
|
|
66
|
-
#
|
|
65
|
+
# DEFAULT FLIPPED 2026-07-13 (the Phase-1 human gate, maintainer-approved,
|
|
66
|
+
# evidence-gated): the default below lists EVERY consumer workspace, so
|
|
67
|
+
# exactly the exclusively-maintainer specification rules drop from consumer
|
|
68
|
+
# installs (the audited 16-rule lever — see
|
|
69
|
+
# agents/settings/contexts/consumer-scoping-audit-2026-07-07.md) while all
|
|
70
|
+
# domain safety floors and domain rules keep shipping. Narrowing further
|
|
71
|
+
# (e.g. [engineering] only) stays a per-install user choice.
|
|
67
72
|
# An EMPTY list is inactive (= legacy-all); only a non-empty list scopes.
|
|
68
|
-
rule_workspaces: []
|
|
73
|
+
rule_workspaces: [engineering, product, finance, founder, gtm, ops, small-business, construction, legal-review-prep]
|
|
69
74
|
#
|
|
70
75
|
# Optional second axis, same semantics per pack ids (packs.yml). When set,
|
|
71
76
|
# a non-kernel rule also needs a `packs:` intersection to project — e.g.
|
|
@@ -622,10 +627,14 @@ worktrees:
|
|
|
622
627
|
|
|
623
628
|
# --- AI Council (external second-opinion network) ---
|
|
624
629
|
#
|
|
625
|
-
#
|
|
626
|
-
#
|
|
627
|
-
#
|
|
628
|
-
#
|
|
630
|
+
# The council config is NOT here and is NOT project-local. It lives ONLY
|
|
631
|
+
# user-global at `~/.event4u/agent-config/settings/.ai-council.yml` — one
|
|
632
|
+
# file, configured once, serving every project / worktree / CWD (ADR-104,
|
|
633
|
+
# superseding ADR-093). The project tree is NEVER searched; a
|
|
634
|
+
# `.ai-council.yml` under any `agents/` tree is ignored. To opt in, copy
|
|
635
|
+
# the shape from `agents/templates/.ai-council.yml.example` to that
|
|
636
|
+
# user-global path and flip `enabled: true` per provider — do NOT drop it
|
|
637
|
+
# into a project's `agents/` tree.
|
|
629
638
|
#
|
|
630
639
|
# See `docs/contracts/ai-council-config.md` for the schema.
|
|
631
640
|
|
|
@@ -665,9 +674,24 @@ ai_team:
|
|
|
665
674
|
# plugin on Claude-Code hosts. Cosmetic only — never changes behavior.
|
|
666
675
|
suppress_setup_hint: false
|
|
667
676
|
|
|
668
|
-
#
|
|
669
|
-
#
|
|
670
|
-
#
|
|
677
|
+
# Managed governance of the codex plugin's Stop-hook Review Gate
|
|
678
|
+
# (road-to-team-mode Phase 4). The gate itself stays upstream's; these
|
|
679
|
+
# keys only govern it. Upstream's own warning: "The review gate can
|
|
680
|
+
# create a long-running Claude/Codex loop and may drain usage limits
|
|
681
|
+
# quickly."
|
|
682
|
+
review_gate:
|
|
683
|
+
# false (default) = byte-identical pre-Phase-4 behavior: no counting,
|
|
684
|
+
# no circuit breaker, our Stop path never touches the gate. true =
|
|
685
|
+
# count consecutive BLOCK verdicts per session and trip the circuit
|
|
686
|
+
# breaker at the bound below.
|
|
687
|
+
managed: false
|
|
688
|
+
|
|
689
|
+
# Circuit-breaker bound: after this many CONSECUTIVE BLOCK verdicts
|
|
690
|
+
# in one session, a visible notice is injected exactly once and the
|
|
691
|
+
# managed layer stops re-blocking — the user decides, never an
|
|
692
|
+
# infinite Claude↔Codex loop. An ALLOW verdict resets the counter.
|
|
693
|
+
# Positive integer.
|
|
694
|
+
max_consecutive_blocks: 3
|
|
671
695
|
|
|
672
696
|
# --- Onboarding ---
|
|
673
697
|
#
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# Pre-launch diagnostic — fixed coverage-area vocabulary
|
|
2
|
+
# (road-to-ecosystem-harvest-prelaunch-diagnostics Phase 1, Source L).
|
|
3
|
+
#
|
|
4
|
+
# The AREA code is the middle segment of the immutable finding-ID grammar
|
|
5
|
+
# `AC-<AREA>-NNN` (docs/contracts/prelaunch-diagnostics.md). Codes are
|
|
6
|
+
# append-only: renaming or re-using a code would re-assign IDs and break
|
|
7
|
+
# every committed baseline. Add new areas at the end; never delete.
|
|
8
|
+
#
|
|
9
|
+
# `launch_gate: true` areas participate in the regression gate's
|
|
10
|
+
# Pass→Finding CI trigger and block a "ready" verdict while Unknown.
|
|
11
|
+
schema_version: prelaunch-areas-v1
|
|
12
|
+
areas:
|
|
13
|
+
auth:
|
|
14
|
+
code: AUTH
|
|
15
|
+
launch_gate: true
|
|
16
|
+
description: Authentication, authorization, tenancy boundaries on the launch surface.
|
|
17
|
+
migrations:
|
|
18
|
+
code: MIG
|
|
19
|
+
launch_gate: true
|
|
20
|
+
description: Schema/data migrations shipped with the launch — reversibility, expand-contract, backfill safety.
|
|
21
|
+
secrets:
|
|
22
|
+
code: SEC
|
|
23
|
+
launch_gate: true
|
|
24
|
+
description: Secrets, tokens, key handling touched by the launch — storage, rotation, exposure paths.
|
|
25
|
+
observability:
|
|
26
|
+
code: OBS
|
|
27
|
+
launch_gate: false
|
|
28
|
+
description: Telemetry proving rollout health — the metric, alert, and pager path for the new surface.
|
|
29
|
+
rollback:
|
|
30
|
+
code: RBK
|
|
31
|
+
launch_gate: true
|
|
32
|
+
description: Rollback contract — trip wires, decision windows, abort paths, named irreversible steps.
|
|
33
|
+
agent-governance:
|
|
34
|
+
code: AGOV
|
|
35
|
+
launch_gate: false
|
|
36
|
+
description: AI/agent surfaces shipped with the launch — tool grants, egress paths, injection exposure.
|
|
@@ -99,6 +99,7 @@ import {
|
|
|
99
99
|
} from '../_lib/agent_settings.js';
|
|
100
100
|
import * as ai_council_clients from '../ai_council/clients.js';
|
|
101
101
|
import * as ai_council_config from '../ai_council/config.js';
|
|
102
|
+
import { review_gate_doctor_signal } from '../ai_team/review_gate.js';
|
|
102
103
|
|
|
103
104
|
type Dict = Record<string, unknown>;
|
|
104
105
|
|
|
@@ -2028,6 +2029,79 @@ function _settings_flag(val: unknown): boolean {
|
|
|
2028
2029
|
return false;
|
|
2029
2030
|
}
|
|
2030
2031
|
|
|
2032
|
+
/**
|
|
2033
|
+
* Decode a JWT payload's `exp` claim (epoch seconds) — base64 decode only,
|
|
2034
|
+
* NO signature verification, no network. Non-JWT / undecodable → null.
|
|
2035
|
+
*/
|
|
2036
|
+
function _jwt_exp(token: unknown): number | null {
|
|
2037
|
+
if (typeof token !== 'string') return null;
|
|
2038
|
+
const parts = token.split('.');
|
|
2039
|
+
if (parts.length !== 3) return null;
|
|
2040
|
+
try {
|
|
2041
|
+
const payload: unknown = JSON.parse(
|
|
2042
|
+
Buffer.from(parts[1] as string, 'base64url').toString('utf-8'),
|
|
2043
|
+
);
|
|
2044
|
+
if (typeof payload !== 'object' || payload === null) return null;
|
|
2045
|
+
const exp = (payload as Record<string, unknown>)['exp'];
|
|
2046
|
+
return typeof exp === 'number' && Number.isFinite(exp) && exp > 0 ? exp : null;
|
|
2047
|
+
} catch {
|
|
2048
|
+
return null;
|
|
2049
|
+
}
|
|
2050
|
+
}
|
|
2051
|
+
|
|
2052
|
+
/** Coerce an explicit expiry field to epoch seconds (number or ISO string). */
|
|
2053
|
+
function _epoch_seconds(value: unknown): number | null {
|
|
2054
|
+
if (typeof value === 'number' && Number.isFinite(value) && value > 0) {
|
|
2055
|
+
// Heuristic: values past ~2286 in seconds are epoch milliseconds.
|
|
2056
|
+
return value > 1e12 ? value / 1000 : value;
|
|
2057
|
+
}
|
|
2058
|
+
if (typeof value === 'string' && value.trim() !== '') {
|
|
2059
|
+
const ms = Date.parse(value);
|
|
2060
|
+
return Number.isNaN(ms) ? null : ms / 1000;
|
|
2061
|
+
}
|
|
2062
|
+
return null;
|
|
2063
|
+
}
|
|
2064
|
+
|
|
2065
|
+
/**
|
|
2066
|
+
* Latest locally-derivable expiry (epoch seconds) from a codex `auth.json`,
|
|
2067
|
+
* or null when none is derivable. Sources, all offline:
|
|
2068
|
+
*
|
|
2069
|
+
* - explicit `expires_at` / `expiry` / `exp` fields (top level; numbers in
|
|
2070
|
+
* seconds or ms, or ISO date strings), and
|
|
2071
|
+
* - the `exp` claim of JWT tokens under `tokens.{access_token,id_token}`
|
|
2072
|
+
* (or the same keys top-level) — base64 payload decode only, never
|
|
2073
|
+
* signature verification.
|
|
2074
|
+
*
|
|
2075
|
+
* LATEST wins: the real codex auth.json holds a refresh_token, so an
|
|
2076
|
+
* expired id_token sitting next to a live access_token is not an auth
|
|
2077
|
+
* failure — WARN only when everything derivable is past.
|
|
2078
|
+
*/
|
|
2079
|
+
function _codex_auth_expiry(auth_path: string): number | null {
|
|
2080
|
+
let data: unknown;
|
|
2081
|
+
try {
|
|
2082
|
+
data = JSON.parse(fs.readFileSync(auth_path, 'utf-8'));
|
|
2083
|
+
} catch {
|
|
2084
|
+
return null;
|
|
2085
|
+
}
|
|
2086
|
+
if (typeof data !== 'object' || data === null) return null;
|
|
2087
|
+
const d = data as Record<string, unknown>;
|
|
2088
|
+
const candidates: number[] = [];
|
|
2089
|
+
for (const key of ['expires_at', 'expiry', 'exp']) {
|
|
2090
|
+
const v = _epoch_seconds(d[key]);
|
|
2091
|
+
if (v !== null) candidates.push(v);
|
|
2092
|
+
}
|
|
2093
|
+
const tokens_raw = d['tokens'];
|
|
2094
|
+
const tokens =
|
|
2095
|
+
typeof tokens_raw === 'object' && tokens_raw !== null && !Array.isArray(tokens_raw)
|
|
2096
|
+
? (tokens_raw as Record<string, unknown>)
|
|
2097
|
+
: {};
|
|
2098
|
+
for (const key of ['access_token', 'id_token']) {
|
|
2099
|
+
const v = _jwt_exp(tokens[key] ?? d[key]);
|
|
2100
|
+
if (v !== null) candidates.push(v);
|
|
2101
|
+
}
|
|
2102
|
+
return candidates.length > 0 ? Math.max(...candidates) : null;
|
|
2103
|
+
}
|
|
2104
|
+
|
|
2031
2105
|
/** The `ai_team` block from `.agent-settings.yml` — lenient, `{}` when absent. */
|
|
2032
2106
|
function _read_ai_team_block(project_root: string): Dict {
|
|
2033
2107
|
const settings_file = project_settings_path(project_root);
|
|
@@ -2049,14 +2123,21 @@ function _read_ai_team_block(project_root: string): Dict {
|
|
|
2049
2123
|
* `team` health check — three sub-signals folded into one result:
|
|
2050
2124
|
*
|
|
2051
2125
|
* (a) codex binary present (council-probe binary discovery reused) AND
|
|
2052
|
-
* auth present. Auth is a READ-ONLY
|
|
2053
|
-
*
|
|
2054
|
-
* expiry
|
|
2055
|
-
*
|
|
2126
|
+
* auth present. Auth is a READ-ONLY probe of `$CODEX_HOME/auth.json`
|
|
2127
|
+
* (default `~/.codex/auth.json`): presence, plus locally-derivable
|
|
2128
|
+
* expiry evidence (`_codex_auth_expiry` — explicit expiry fields or
|
|
2129
|
+
* JWT `exp` payloads; no network, no signature verification). Past
|
|
2130
|
+
* expiry → WARN; no derivable expiry → presence-only note in the
|
|
2131
|
+
* detail. Runtime expiry is still caught authoritatively by
|
|
2132
|
+
* `OpenAICliClient._AUTH_FAILURE_PATTERNS`.
|
|
2056
2133
|
* (b) on Claude-Code hosts (claude binary on PATH or the Claude config dir
|
|
2057
2134
|
* present): the official codex plugin recorded in
|
|
2058
|
-
* `plugins/installed_plugins.json
|
|
2059
|
-
*
|
|
2135
|
+
* `plugins/installed_plugins.json`, with namespace-resistant identity
|
|
2136
|
+
* via `codex_plugin_identity()` — a prefix match whose marketplace
|
|
2137
|
+
* source repo cannot be verified against `openai/codex-plugin-cc`
|
|
2138
|
+
* keeps ✅ but is annotated "identity not fully verified (prefix
|
|
2139
|
+
* match)". Detection is read-only — doctor never writes under
|
|
2140
|
+
* `~/.claude/`.
|
|
2060
2141
|
* (c) Review-Gate consistency: `ai_team.review_gate.managed` on while the
|
|
2061
2142
|
* loop bound (`max_consecutive_blocks`, Phase 4 — unbuilt) is absent
|
|
2062
2143
|
* → WARN.
|
|
@@ -2074,8 +2155,6 @@ function _check_team(project_root: string): Dict {
|
|
|
2074
2155
|
? (gateRaw as Dict)
|
|
2075
2156
|
: {};
|
|
2076
2157
|
const gate_managed = _settings_flag(gate['managed']);
|
|
2077
|
-
const bound = gate['max_consecutive_blocks'];
|
|
2078
|
-
const bound_present = typeof bound === 'number' && Number.isInteger(bound) && bound > 0;
|
|
2079
2158
|
|
|
2080
2159
|
if (!enabled) {
|
|
2081
2160
|
if (gate_managed) {
|
|
@@ -2110,12 +2189,26 @@ function _check_team(project_root: string): Dict {
|
|
|
2110
2189
|
remedies.push('install the codex CLI: `npm install -g @openai/codex`');
|
|
2111
2190
|
}
|
|
2112
2191
|
|
|
2113
|
-
// (a, cont.) codex auth —
|
|
2192
|
+
// (a, cont.) codex auth — READ-ONLY probe of $CODEX_HOME/auth.json:
|
|
2193
|
+
// presence + locally-derivable expiry evidence (no network).
|
|
2114
2194
|
const auth_path = path.join(_codex_home(), 'auth.json');
|
|
2115
2195
|
const authed = pathExists(auth_path);
|
|
2116
|
-
|
|
2196
|
+
let auth_str: string;
|
|
2117
2197
|
if (!authed) {
|
|
2198
|
+
auth_str = `codex auth ❌ (${auth_path})`;
|
|
2118
2199
|
remedies.push('run `codex login`');
|
|
2200
|
+
} else {
|
|
2201
|
+
const expiry = _codex_auth_expiry(auth_path);
|
|
2202
|
+
if (expiry !== null && expiry * 1000 < Date.now()) {
|
|
2203
|
+
auth_str = `codex auth ⚠️ expired (${auth_path})`;
|
|
2204
|
+
remedies.push('auth token appears expired — run `codex login`');
|
|
2205
|
+
} else if (expiry === null) {
|
|
2206
|
+
auth_str =
|
|
2207
|
+
`codex auth ✅ (${auth_path}; ` +
|
|
2208
|
+
'presence-only check — expiry not verifiable locally)';
|
|
2209
|
+
} else {
|
|
2210
|
+
auth_str = `codex auth ✅ (${auth_path})`;
|
|
2211
|
+
}
|
|
2119
2212
|
}
|
|
2120
2213
|
|
|
2121
2214
|
// (b) official codex plugin — Claude-Code hosts only.
|
|
@@ -2123,34 +2216,33 @@ function _check_team(project_root: string): Dict {
|
|
|
2123
2216
|
shutilWhich('claude') !== null || pathExists(claude_plugin.claude_config_dir());
|
|
2124
2217
|
let plugin_str: string;
|
|
2125
2218
|
if (is_claude_host) {
|
|
2126
|
-
const
|
|
2127
|
-
|
|
2128
|
-
|
|
2219
|
+
const identity = claude_plugin.codex_plugin_identity();
|
|
2220
|
+
if (!identity.installed) {
|
|
2221
|
+
plugin_str = 'codex plugin ❌';
|
|
2129
2222
|
remedies.push(
|
|
2130
2223
|
'install the official codex plugin: ' +
|
|
2131
2224
|
claude_plugin.CODEX_PLUGIN_INSTALL_HINT.map((c) => `\`${c}\``).join(' then '),
|
|
2132
2225
|
);
|
|
2226
|
+
} else if (!identity.identity_verified) {
|
|
2227
|
+
plugin_str = 'codex plugin ✅ — identity not fully verified (prefix match)';
|
|
2228
|
+
} else {
|
|
2229
|
+
plugin_str = 'codex plugin ✅';
|
|
2133
2230
|
}
|
|
2134
2231
|
} else {
|
|
2135
2232
|
plugin_str = 'codex plugin — (not a Claude Code host; fallback path applies)';
|
|
2136
2233
|
}
|
|
2137
2234
|
|
|
2138
|
-
// (c) Review-Gate
|
|
2139
|
-
|
|
2140
|
-
|
|
2141
|
-
|
|
2142
|
-
|
|
2143
|
-
|
|
2144
|
-
|
|
2145
|
-
'`ai_team.review_gate.managed: false`',
|
|
2146
|
-
);
|
|
2147
|
-
} else {
|
|
2148
|
-
gate_str = `review-gate ${gate_managed ? 'on' : 'off'}`;
|
|
2149
|
-
}
|
|
2235
|
+
// (c) Review-Gate governance (Phase 4) — logic lives in
|
|
2236
|
+
// src/scripts/ai_team/review_gate.ts (upstream gate probe + WARN
|
|
2237
|
+
// states, incl. plugin-gate-on-while-unmanaged with the quoted
|
|
2238
|
+
// upstream cost warning).
|
|
2239
|
+
const gate_signal = review_gate_doctor_signal(project_root, gate);
|
|
2240
|
+
const gate_str = gate_signal.gate_str;
|
|
2241
|
+
remedies.push(...gate_signal.remedies);
|
|
2150
2242
|
|
|
2151
2243
|
const detail =
|
|
2152
2244
|
`codex binary ${binary_glyph} (${binary_name}) · ` +
|
|
2153
|
-
|
|
2245
|
+
`${auth_str} · ${plugin_str} · ${gate_str}`;
|
|
2154
2246
|
if (remedies.length > 0) {
|
|
2155
2247
|
return {
|
|
2156
2248
|
id: 'team',
|
|
@@ -79,14 +79,89 @@ export function claude_plugin_installed(): boolean {
|
|
|
79
79
|
return _plugin_id_recorded(CLAUDE_PLUGIN_ID);
|
|
80
80
|
}
|
|
81
81
|
|
|
82
|
+
/**
|
|
83
|
+
* Codex plugin detection richness — presence plus namespace-resistant
|
|
84
|
+
* identity verification.
|
|
85
|
+
*
|
|
86
|
+
* `installed` — a `codex@<marketplace>` key exists in
|
|
87
|
+
* `plugins/installed_plugins.json` (prefix match, any marketplace).
|
|
88
|
+
* `identity_verified` — at least one such marketplace resolves, via the
|
|
89
|
+
* sibling `plugins/known_marketplaces.json` registry
|
|
90
|
+
* (`{ "<marketplace>": { source: { source: "github", repo: "owner/repo" } } }`
|
|
91
|
+
* — the real on-disk shape; `installed_plugins.json` entries themselves
|
|
92
|
+
* carry no repo/owner field), to the official upstream repo
|
|
93
|
+
* (`CODEX_MARKETPLACE_REPO`). When the registry file is absent, unreadable,
|
|
94
|
+
* or its entry has no `source.repo`, identity stays UNVERIFIED (honest
|
|
95
|
+
* fallback: prefix match only) — never inferred from the marketplace
|
|
96
|
+
* name alone, which any squatter can reuse.
|
|
97
|
+
*/
|
|
98
|
+
export interface CodexPluginIdentity {
|
|
99
|
+
readonly installed: boolean;
|
|
100
|
+
readonly identity_verified: boolean;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/** Marketplace names (the `@<marketplace>` suffix) a plugin id is installed from. */
|
|
104
|
+
function _plugin_marketplaces(plugin_id: string): string[] {
|
|
105
|
+
const p = path.join(claude_config_dir(), 'plugins', 'installed_plugins.json');
|
|
106
|
+
let data: unknown;
|
|
107
|
+
try {
|
|
108
|
+
data = JSON.parse(fs.readFileSync(p, 'utf-8'));
|
|
109
|
+
} catch {
|
|
110
|
+
return [];
|
|
111
|
+
}
|
|
112
|
+
if (data === null || typeof data !== 'object') return [];
|
|
113
|
+
const plugins = (data as { plugins?: unknown }).plugins;
|
|
114
|
+
if (plugins === null || plugins === undefined || typeof plugins !== 'object') return [];
|
|
115
|
+
const prefix = `${plugin_id}@`;
|
|
116
|
+
return Object.keys(plugins as Record<string, unknown>)
|
|
117
|
+
.filter((k) => k.startsWith(prefix))
|
|
118
|
+
.map((k) => k.slice(prefix.length));
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/** `source.repo` recorded for a marketplace in `known_marketplaces.json`, or null. */
|
|
122
|
+
function _marketplace_source_repo(marketplace: string): string | null {
|
|
123
|
+
const p = path.join(claude_config_dir(), 'plugins', 'known_marketplaces.json');
|
|
124
|
+
let data: unknown;
|
|
125
|
+
try {
|
|
126
|
+
data = JSON.parse(fs.readFileSync(p, 'utf-8'));
|
|
127
|
+
} catch {
|
|
128
|
+
return null;
|
|
129
|
+
}
|
|
130
|
+
if (data === null || typeof data !== 'object') return null;
|
|
131
|
+
const entry = (data as Record<string, unknown>)[marketplace];
|
|
132
|
+
if (entry === null || entry === undefined || typeof entry !== 'object') return null;
|
|
133
|
+
const source = (entry as { source?: unknown }).source;
|
|
134
|
+
if (source === null || source === undefined || typeof source !== 'object') return null;
|
|
135
|
+
const repo = (source as { repo?: unknown }).repo;
|
|
136
|
+
return typeof repo === 'string' && repo.trim() !== '' ? repo.trim() : null;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Presence + identity of the official OpenAI codex plugin. Read-only:
|
|
141
|
+
* detection never writes under `~/.claude/`. See `CodexPluginIdentity`
|
|
142
|
+
* for the verification contract.
|
|
143
|
+
*/
|
|
144
|
+
export function codex_plugin_identity(): CodexPluginIdentity {
|
|
145
|
+
const marketplaces = _plugin_marketplaces(CODEX_PLUGIN_ID);
|
|
146
|
+
if (marketplaces.length === 0) {
|
|
147
|
+
return { installed: false, identity_verified: false };
|
|
148
|
+
}
|
|
149
|
+
const want = CODEX_MARKETPLACE_REPO.toLowerCase();
|
|
150
|
+
const identity_verified = marketplaces.some(
|
|
151
|
+
(m) => (_marketplace_source_repo(m) ?? '').toLowerCase() === want,
|
|
152
|
+
);
|
|
153
|
+
return { installed: true, identity_verified };
|
|
154
|
+
}
|
|
155
|
+
|
|
82
156
|
/**
|
|
83
157
|
* True when the official OpenAI codex plugin is recorded in Claude Code's
|
|
84
158
|
* `plugins/installed_plugins.json` (any marketplace — matched by the
|
|
85
159
|
* `codex@` id prefix). Unreadable / absent file → false. Read-only:
|
|
86
|
-
* detection never writes under `~/.claude/`.
|
|
160
|
+
* detection never writes under `~/.claude/`. Presence only — for
|
|
161
|
+
* namespace-resistant identity use `codex_plugin_identity()`.
|
|
87
162
|
*/
|
|
88
163
|
export function codex_plugin_installed(): boolean {
|
|
89
|
-
return
|
|
164
|
+
return codex_plugin_identity().installed;
|
|
90
165
|
}
|
|
91
166
|
|
|
92
167
|
/**
|
|
@@ -62,6 +62,10 @@ export interface RecordInput {
|
|
|
62
62
|
first_pass_success?: boolean | undefined;
|
|
63
63
|
/** QUALITY: slice retried on a higher tier after a verification failure. Optional boolean (null = not measured). */
|
|
64
64
|
escalated?: boolean | undefined;
|
|
65
|
+
/** U5 (reliability-measurement): the subagent-type combination this dispatch
|
|
66
|
+
* ran (ordered agent-type ids, e.g. ['implementer','implementer','judge']) —
|
|
67
|
+
* with `outcome`, lets the orchestrator prefer combos that worked. Ids only. */
|
|
68
|
+
agent_combo?: string[] | undefined;
|
|
65
69
|
// Audit-log envelope (sensible defaults for a dispatch record)
|
|
66
70
|
phase?: LinePhase | undefined;
|
|
67
71
|
outcome?: LineOutcome | undefined;
|
|
@@ -173,6 +177,7 @@ export function buildOrchestrationLine(input: RecordInput): BuiltLine {
|
|
|
173
177
|
session_tier: input.session_tier ?? null,
|
|
174
178
|
first_pass_success: input.first_pass_success ?? null,
|
|
175
179
|
escalated: input.escalated ?? null,
|
|
180
|
+
agent_combo: input.agent_combo ?? [],
|
|
176
181
|
};
|
|
177
182
|
|
|
178
183
|
const line: Record<string, unknown> = {
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* tool_adapter_registry.ts — canonical supported-tools adapter set.
|
|
3
|
+
*
|
|
4
|
+
* Single source of truth binding three surfaces that previously drifted
|
|
5
|
+
* independently (road-to-ecosystem-harvest-skill-quality-gates Phase 4,
|
|
6
|
+
* Source AA):
|
|
7
|
+
*
|
|
8
|
+
* 1. the README `## Supported tools` § "Project-installed" matrix,
|
|
9
|
+
* 2. this registry (reviewed, canonical),
|
|
10
|
+
* 3. the generator output roots in `check_generator_output_coverage.ts`.
|
|
11
|
+
*
|
|
12
|
+
* `lint_supported_tools_matrix.ts` fails CI when (1) ≠ (2) in either
|
|
13
|
+
* direction, and when a generator tool tag in (3) has no registry entry
|
|
14
|
+
* covering its root. Adding/removing a supported tool therefore requires
|
|
15
|
+
* touching README + registry (+ generator roots when native) in one commit.
|
|
16
|
+
*/
|
|
17
|
+
import { GENERATOR_OUTPUT_ROOTS } from '../check_generator_output_coverage.js';
|
|
18
|
+
|
|
19
|
+
export interface AdapterEntry {
|
|
20
|
+
/** Tool name exactly as the README matrix first column renders it (without bold markers / parenthetical). */
|
|
21
|
+
readonly tool: string;
|
|
22
|
+
/** How the tool consumes the package. */
|
|
23
|
+
readonly kind: 'native' | 'agents-md' | 'marker';
|
|
24
|
+
/** Generated / written roots this tool reads (repo-relative). Empty for agents-md / marker tools. */
|
|
25
|
+
readonly roots: readonly string[];
|
|
26
|
+
/** Tool tag used by GENERATOR_OUTPUT_ROOTS, when the condense pipeline owns a root for it. */
|
|
27
|
+
readonly generator_tag?: string;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export const ADAPTER_REGISTRY: readonly AdapterEntry[] = [
|
|
31
|
+
{
|
|
32
|
+
tool: 'Claude Code',
|
|
33
|
+
kind: 'native',
|
|
34
|
+
roots: ['.claude/rules', '.claude/skills', '.claude/agents', '.claude/personas', '.claude/user-types'],
|
|
35
|
+
generator_tag: 'claude-code',
|
|
36
|
+
},
|
|
37
|
+
{ tool: 'Cursor', kind: 'native', roots: ['.cursor/rules', '.cursor/commands'], generator_tag: 'cursor' },
|
|
38
|
+
{ tool: 'Cline', kind: 'native', roots: ['.clinerules'] },
|
|
39
|
+
{
|
|
40
|
+
tool: 'Windsurf',
|
|
41
|
+
kind: 'native',
|
|
42
|
+
roots: ['.windsurfrules', '.windsurf/rules', '.windsurf/workflows'],
|
|
43
|
+
generator_tag: 'windsurf',
|
|
44
|
+
},
|
|
45
|
+
{ tool: 'Gemini CLI', kind: 'native', roots: ['GEMINI.md'], generator_tag: 'gemini' },
|
|
46
|
+
{ tool: 'GitHub Copilot', kind: 'native', roots: ['.github/copilot-instructions.md'] },
|
|
47
|
+
{ tool: 'Roo Code', kind: 'agents-md', roots: [] },
|
|
48
|
+
{ tool: 'Codex CLI', kind: 'agents-md', roots: [] },
|
|
49
|
+
{ tool: 'Continue.dev', kind: 'agents-md', roots: [] },
|
|
50
|
+
{ tool: 'Aider', kind: 'marker', roots: [] },
|
|
51
|
+
{ tool: 'Augment', kind: 'marker', roots: ['.augment'], generator_tag: 'augment' },
|
|
52
|
+
{ tool: 'Claude Desktop', kind: 'marker', roots: [] },
|
|
53
|
+
];
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Extract the tool names from the README's `## Supported tools` §
|
|
57
|
+
* "Project-installed" table. Returns names with bold markers and trailing
|
|
58
|
+
* parentheticals stripped, in table order.
|
|
59
|
+
*/
|
|
60
|
+
export function parse_supported_tools(readme: string): string[] {
|
|
61
|
+
const section = readme.split(/^## Supported tools\s*$/m)[1];
|
|
62
|
+
if (section === undefined) return [];
|
|
63
|
+
// Stop at the next H2/H3 AFTER the first table (the "Plugin-installed" H3).
|
|
64
|
+
const lines = section.split('\n');
|
|
65
|
+
const tools: string[] = [];
|
|
66
|
+
let inTable = false;
|
|
67
|
+
for (const line of lines) {
|
|
68
|
+
const t = line.trim();
|
|
69
|
+
if (/^###\s/.test(t) && inTable) break; // next sub-section after the table
|
|
70
|
+
if (/^\|/.test(t)) {
|
|
71
|
+
inTable = true;
|
|
72
|
+
const cell = t.split('|')[1]?.trim() ?? '';
|
|
73
|
+
if (cell === '' || cell === 'Tool' || /^:?-+:?$/.test(cell)) continue;
|
|
74
|
+
const name = cell
|
|
75
|
+
.replace(/\*\*/g, '')
|
|
76
|
+
.replace(/\s*\(.*\)\s*$/, '')
|
|
77
|
+
.trim();
|
|
78
|
+
if (name && name !== '---') tools.push(name);
|
|
79
|
+
} else if (inTable && t !== '' && !/^\|/.test(t)) {
|
|
80
|
+
// Table ended (legend line etc.).
|
|
81
|
+
break;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
return tools;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/** Compare README matrix ↔ registry ↔ generator roots. Returns human-readable errors; empty = green. */
|
|
88
|
+
export function lint_matrix(readme: string): string[] {
|
|
89
|
+
const errors: string[] = [];
|
|
90
|
+
const readmeTools = parse_supported_tools(readme);
|
|
91
|
+
const readmeSet = new Set(readmeTools);
|
|
92
|
+
const registrySet = new Set(ADAPTER_REGISTRY.map((e) => e.tool));
|
|
93
|
+
|
|
94
|
+
for (const t of registrySet) {
|
|
95
|
+
if (!readmeSet.has(t)) {
|
|
96
|
+
errors.push(`registry tool '${t}' missing from the README Supported-tools matrix`);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
for (const t of readmeSet) {
|
|
100
|
+
if (!registrySet.has(t)) {
|
|
101
|
+
errors.push(`README Supported-tools row '${t}' has no ADAPTER_REGISTRY entry`);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// Generator leg: every tool-tagged generator output root must be claimed
|
|
106
|
+
// by the registry entry carrying that tag.
|
|
107
|
+
const byTag = new Map(ADAPTER_REGISTRY.filter((e) => e.generator_tag).map((e) => [e.generator_tag, e]));
|
|
108
|
+
for (const { root, tool } of GENERATOR_OUTPUT_ROOTS) {
|
|
109
|
+
if (tool === null) continue;
|
|
110
|
+
const entry = byTag.get(tool);
|
|
111
|
+
if (!entry) {
|
|
112
|
+
errors.push(`generator output root '${root}' (tool '${tool}') has no ADAPTER_REGISTRY entry with that generator_tag`);
|
|
113
|
+
continue;
|
|
114
|
+
}
|
|
115
|
+
if (!entry.roots.includes(root)) {
|
|
116
|
+
errors.push(`generator output root '${root}' (tool '${tool}') is not listed in ADAPTER_REGISTRY entry '${entry.tool}'`);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
return errors;
|
|
120
|
+
}
|