@event4u/agent-config 14.20.0 → 14.21.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 +243 -0
- package/README.md +1 -1
- package/agents/templates/.ai-council.yml.example +10 -0
- package/dist/agent-src/commands/analyze/inbox.md +1 -1
- package/dist/agent-src/commands/analyze/{reference-repo.md → repo.md} +247 -32
- package/dist/agent-src/commands/analyze/roadmap-repos.md +129 -0
- package/dist/agent-src/commands/analyze.md +12 -7
- package/dist/agent-src/commands/feature/plan.md +55 -16
- package/dist/agent-src/commands/feature/refactor.md +8 -3
- package/dist/agent-src/commands/humanize.md +48 -4
- package/dist/agent-src/contexts/execution/contract-decision-sheet.md +22 -1
- package/dist/agent-src/contexts/execution/host-capability-manifest.md +4 -1
- package/dist/agent-src/contexts/execution/roadmap-process-loop.md +35 -2
- package/dist/agent-src/contexts/execution/terminal-states.md +4 -1
- package/dist/agent-src/rules/evaluator-independence.md +4 -4
- package/dist/agent-src/rules/external-reference-deep-dive.md +2 -2
- package/dist/agent-src/skills/competitive-positioning/SKILL.md +2 -2
- package/dist/agent-src/skills/humanizer/references/anti-aiisms.md +27 -11
- package/dist/agent-src/skills/learning-to-rule-or-skill/SKILL.md +1 -1
- package/dist/agent-src/skills/markitdown/SKILL.md +1 -1
- package/dist/agent-src/skills/repomix-packer/SKILL.md +1 -1
- package/dist/agent-src/skills/technical-specification/SKILL.md +4 -0
- package/dist/agent-src/templates/agents/agent-project-settings.example.yml +1 -1
- package/dist/agent-src/templates/features.md +5 -0
- package/dist/agent-src/templates/roadmaps.md +15 -0
- package/dist/cli-delegate/{chunk-KPBWFOPZ.js → chunk-2FTMENJL.js} +194 -37
- package/dist/cli-delegate/{chunk-PI2GU3DP.js → chunk-2TAQ2K4V.js} +4 -1
- package/dist/cli-delegate/{chunk-C6IPZRDA.js → chunk-643Z7FIC.js} +1 -1
- package/dist/cli-delegate/{chunk-NUMQHLS5.js → chunk-J3RFNKG4.js} +21 -2
- package/dist/cli-delegate/cmd_conformance.js +17 -3
- package/dist/cli-delegate/cmd_doctor.js +3 -3
- package/dist/cli-delegate/cmd_export.js +2 -2
- package/dist/cli-delegate/cmd_handoff.js +1 -1
- package/dist/cli-delegate/cmd_prune.js +2 -2
- package/dist/cli-delegate/cmd_settings_migrate.js +2 -2
- package/dist/cli-delegate/cmd_sync.js +2 -2
- package/dist/cli-delegate/cmd_uninstall.js +2 -2
- package/dist/cli-delegate/cmd_validate.js +2 -2
- package/dist/discovery/deprecation-report.md +1 -1
- package/dist/discovery/discovery-manifest.json +56 -25
- 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 +7 -6
- package/dist/discovery/trust-report.md +2 -2
- package/dist/discovery/workspaces.json +6 -5
- package/dist/hooks/dispatch.js +479 -241
- package/dist/install/install.mjs +4 -1
- package/dist/mcp/registry-manifest.json +2 -2
- package/dist/mcp/server.json +2 -2
- package/dist/mcp/server.mjs +313 -63
- package/dist/scripts/ai_council/content_ceiling.js +63 -0
- package/dist/scripts/ai_council/seat_policy.js +106 -0
- package/dist/scripts/ai_council/transport_resolver.js +21 -0
- package/llms.txt +14 -14
- package/package.json +1 -1
- package/src/config/gate-violation-baselines.json +3 -3
- package/src/config/release-gate-locality.yml +4 -3
- package/src/scripts/_lib/blocked_by_marker.ts +189 -0
- package/src/scripts/_lib/host_capability.ts +38 -1
- package/src/scripts/_lib/release_highlights.ts +19 -211
- package/src/scripts/_lib/release_material.ts +27 -77
- package/src/scripts/_lib/structured_ask.ts +117 -0
- package/src/scripts/_lib/tty_prompt.ts +76 -0
- package/src/scripts/ai_council/api_transport.ts +163 -0
- package/src/scripts/ai_council/clients.ts +58 -63
- package/src/scripts/ai_council/config.ts +13 -0
- package/src/scripts/ai_council/content_ceiling.ts +79 -0
- package/src/scripts/ai_council/jury_aggregate.ts +178 -0
- package/src/scripts/ai_council/seat_policy.ts +137 -0
- package/src/scripts/ai_council/status_surface.ts +79 -0
- package/src/scripts/ai_council/transport_resolver.ts +59 -1
- package/src/scripts/ai_tells_rules.ts +446 -5
- package/src/scripts/ask_block_census.ts +499 -0
- package/src/scripts/bench_humanizer_eval.ts +264 -40
- package/src/scripts/bench_prose_tells_fp.ts +198 -0
- package/src/scripts/check_branch_work_committed.ts +234 -0
- package/src/scripts/check_pack_size.ts +1 -1
- package/src/scripts/check_release_highlights.ts +0 -157
- package/src/scripts/council_cli.ts +18 -27
- package/src/scripts/detect_ai_tells.ts +142 -20
- package/src/scripts/harvest_reference_tokens.ts +698 -0
- package/src/scripts/hook_manifest.json +1 -1
- package/src/scripts/hook_manifest.yaml +31 -1
- package/src/scripts/hooks/concern_registry.ts +2 -0
- package/src/scripts/hooks/delegation_nudge_hook.ts +45 -1
- package/src/scripts/hooks/one_question_per_ask_hook.ts +191 -0
- package/src/scripts/install-hooks.sh +52 -0
- package/src/scripts/lint_roadmap_blockers.ts +51 -1
- package/src/scripts/migrate_command_suggestions.ts +1 -1
- package/src/scripts/probe_unblocked_ask.ts +135 -2
- package/src/scripts/release.ts +26 -71
- package/src/scripts/release_drill.ts +6 -10
- package/src/scripts/release_env.ts +1 -0
- package/src/scripts/release_publication.ts +34 -29
- package/src/scripts/self_review_gate.ts +409 -15
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* One binary content ceiling per seat — road-to-admissible-council-seats 2.2.
|
|
3
|
+
*
|
|
4
|
+
* Redaction today is a SECRETS floor and says so
|
|
5
|
+
* (`src/skills/ai-council/references/cost-and-redaction.md`). Nothing above it
|
|
6
|
+
* could express "this project's diff may not go to that seat", which is a
|
|
7
|
+
* different question with a different answer: a diff carrying no secret at all
|
|
8
|
+
* is still the consumer's private source.
|
|
9
|
+
*
|
|
10
|
+
* Deliberately BINARY. A graded scale invites a middle value nobody can define
|
|
11
|
+
* and every caller reads differently; two values force the decision at
|
|
12
|
+
* authoring time, where it belongs.
|
|
13
|
+
*
|
|
14
|
+
* public-artifact — repo-owned artefacts: skill descriptions, the trigger
|
|
15
|
+
* corpus, rule text, bench fixtures. Already public.
|
|
16
|
+
* project-content — everything a consumer supplies. The DEFAULT, so an
|
|
17
|
+
* unclassified payload is treated as private rather than
|
|
18
|
+
* as publishable.
|
|
19
|
+
*
|
|
20
|
+
* A seat's ceiling is the most sensitive class it may receive. `public-artifact`
|
|
21
|
+
* is therefore the NARROWER seat: it admits public artefacts only.
|
|
22
|
+
*/
|
|
23
|
+
export const CONTENT_CLASSES = ['public-artifact', 'project-content'];
|
|
24
|
+
/** The default for anything not explicitly classified: private, not public. */
|
|
25
|
+
export const DEFAULT_CONTENT_CLASS = 'project-content';
|
|
26
|
+
/** The default seat ceiling: unchanged behaviour for every existing seat. */
|
|
27
|
+
export const DEFAULT_SEAT_CEILING = 'project-content';
|
|
28
|
+
const _SET = new Set(CONTENT_CLASSES);
|
|
29
|
+
/**
|
|
30
|
+
* Any configured value → a content class. Unrecognised input becomes
|
|
31
|
+
* `project-content`, the RESTRICTIVE value: a typo must narrow what a seat may
|
|
32
|
+
* receive, never widen it.
|
|
33
|
+
*/
|
|
34
|
+
export function classifyContentClass(raw, fallback = DEFAULT_CONTENT_CLASS) {
|
|
35
|
+
if (typeof raw === 'string' && _SET.has(raw)) {
|
|
36
|
+
return raw;
|
|
37
|
+
}
|
|
38
|
+
return fallback;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Does a seat whose ceiling is `seatCeiling` admit a payload of `contentClass`?
|
|
42
|
+
*
|
|
43
|
+
* The one refusal is `project-content` into a `public-artifact` seat. Every
|
|
44
|
+
* other pairing is admitted, including public artefacts into a
|
|
45
|
+
* `project-content` seat — a seat cleared for private content is trivially
|
|
46
|
+
* cleared for public content, and refusing that would be a nonsense that
|
|
47
|
+
* disables the default seat for its own repo's fixtures.
|
|
48
|
+
*
|
|
49
|
+
* Refusal is a GREEN path (this roadmap's 2.2 verify): the caller drops the
|
|
50
|
+
* seat and the run continues. It is not an error, because a policy working as
|
|
51
|
+
* designed is not a failure.
|
|
52
|
+
*/
|
|
53
|
+
export function admitsContent(seatCeiling, contentClass) {
|
|
54
|
+
if (seatCeiling === 'public-artifact' && contentClass === 'project-content') {
|
|
55
|
+
return {
|
|
56
|
+
admitted: false,
|
|
57
|
+
reason: "seat ceiling is 'public-artifact'; the payload is classified " +
|
|
58
|
+
"'project-content' (consumer-supplied)",
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
return { admitted: true, reason: null };
|
|
62
|
+
}
|
|
63
|
+
//# sourceMappingURL=content_ceiling.js.map
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import { classifyContentClass as _classifyContentClass, DEFAULT_SEAT_CEILING as _DEFAULT_SEAT_CEILING, } from './content_ceiling.js';
|
|
2
|
+
/**
|
|
3
|
+
* Seat policy — the vocabulary for refusing a route that IS reachable.
|
|
4
|
+
*
|
|
5
|
+
* road-to-admissible-council-seats 2.1. `AbsentReason` was exactly four values
|
|
6
|
+
* — `no_binary | no_auth | timeout | quota` — and every one of them means "the
|
|
7
|
+
* route is not reachable". A route that is reachable and **must not be used**
|
|
8
|
+
* had no name at all, so the resolver could not fail closed on terms, privacy,
|
|
9
|
+
* unproven cost, or a served model that is not the requested one. The absence
|
|
10
|
+
* of a word was the absence of the refusal: `resolveTransport`'s `api`/`cli`
|
|
11
|
+
* branches return `available: true` unconditionally, so anything the four
|
|
12
|
+
* reachability reasons could not express resolved OPEN.
|
|
13
|
+
*
|
|
14
|
+
* Fail closed on the unknown, which is the whole point.
|
|
15
|
+
* `classifyPolicyExclusion` maps anything it does not recognise to
|
|
16
|
+
* `policy_unknown`, and `policy_unknown` is an ABSENT reason like every other
|
|
17
|
+
* member of this union. An unrecognised policy token is the case where the
|
|
18
|
+
* config is trying to say something the code cannot understand; resolving it
|
|
19
|
+
* to "available" would make a typo an authorisation. This is the roadmap's own
|
|
20
|
+
* Risk 3 — "a new refusal vocabulary defaults open instead of closed".
|
|
21
|
+
*
|
|
22
|
+
* What this is NOT. It is not a secrets floor. Redaction (`skills/ai-council/references/
|
|
23
|
+
* cost-and-redaction.md`) still runs underneath and is not replaced. It is not
|
|
24
|
+
* an authorisation either: nothing here ever makes a seat available that was
|
|
25
|
+
* not already available — every value in the union only ever subtracts.
|
|
26
|
+
*/
|
|
27
|
+
/** Reachable-but-refused reasons. Every value means ABSENT. */
|
|
28
|
+
export const POLICY_EXCLUSIONS = [
|
|
29
|
+
/** The provider's terms forbid this use of the route. */
|
|
30
|
+
'policy_terms',
|
|
31
|
+
/** A privacy constraint forbids sending to this route. */
|
|
32
|
+
'policy_privacy',
|
|
33
|
+
/** The route's cost property is asserted rather than proven. */
|
|
34
|
+
'policy_unproven_cost',
|
|
35
|
+
/** The route serves a model other than the one requested. */
|
|
36
|
+
'policy_model_mismatch',
|
|
37
|
+
/** 2.2 — the payload's content class exceeds the seat's ceiling. */
|
|
38
|
+
'policy_content_ceiling',
|
|
39
|
+
/** The config named an exclusion this build does not understand. */
|
|
40
|
+
'policy_unknown',
|
|
41
|
+
];
|
|
42
|
+
const _SET = new Set(POLICY_EXCLUSIONS);
|
|
43
|
+
/** True for a value this build can act on. `policy_unknown` included: it is a
|
|
44
|
+
* real, actionable verdict (refuse), not a parse failure. */
|
|
45
|
+
export function isPolicyExclusion(v) {
|
|
46
|
+
return typeof v === 'string' && _SET.has(v);
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Any configured value → a verdict. Unrecognised input, including `null` used
|
|
50
|
+
* where a string was meant, becomes `policy_unknown` — never `null`, because a
|
|
51
|
+
* `null` return would put the caller back in the position of deciding what an
|
|
52
|
+
* unreadable policy means, which is where the open default came from.
|
|
53
|
+
*
|
|
54
|
+
* `undefined` and `null` are the two ways of saying "no exclusion configured"
|
|
55
|
+
* and are the ONLY inputs that return `null`. Everything else is a refusal.
|
|
56
|
+
*/
|
|
57
|
+
export function classifyPolicyExclusion(raw) {
|
|
58
|
+
if (raw === undefined || raw === null) {
|
|
59
|
+
return null;
|
|
60
|
+
}
|
|
61
|
+
return isPolicyExclusion(raw) ? raw : 'policy_unknown';
|
|
62
|
+
}
|
|
63
|
+
/** One-line human reason for a refusal. `council:status` prints this. */
|
|
64
|
+
export function policyExclusionReason(p, provider) {
|
|
65
|
+
const who = provider === undefined ? 'this seat' : provider;
|
|
66
|
+
switch (p) {
|
|
67
|
+
case 'policy_terms':
|
|
68
|
+
return `${who} is excluded by policy: the route's terms forbid this use`;
|
|
69
|
+
case 'policy_privacy':
|
|
70
|
+
return `${who} is excluded by policy: a privacy constraint forbids this route`;
|
|
71
|
+
case 'policy_unproven_cost':
|
|
72
|
+
return `${who} is excluded by policy: the route's cost property is asserted, not proven`;
|
|
73
|
+
case 'policy_model_mismatch':
|
|
74
|
+
return `${who} is excluded by policy: the route serves a model other than the one requested`;
|
|
75
|
+
case 'policy_content_ceiling':
|
|
76
|
+
return `${who} is excluded by policy: the payload exceeds the seat's content ceiling`;
|
|
77
|
+
case 'policy_unknown':
|
|
78
|
+
return `${who} is excluded by policy: an exclusion this build does not understand (failing closed)`;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
export function parseSeatFields(name, raw) {
|
|
82
|
+
const dr = raw['disabled_reason'];
|
|
83
|
+
let disabled_reason = null;
|
|
84
|
+
if (dr !== undefined && dr !== null) {
|
|
85
|
+
if (typeof dr !== 'string' || dr.trim() === '') {
|
|
86
|
+
return {
|
|
87
|
+
error: `members.${name}.disabled_reason must be a non-empty string when set ` +
|
|
88
|
+
`(got ${JSON.stringify(dr)}).`,
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
disabled_reason = dr.trim();
|
|
92
|
+
}
|
|
93
|
+
// Both of the next two fail CLOSED on an unreadable value: an unknown
|
|
94
|
+
// exclusion token becomes `policy_unknown` (which resolves ABSENT) and an
|
|
95
|
+
// unknown ceiling falls back to the RESTRICTIVE one. Throwing instead would
|
|
96
|
+
// be defensible, but a config typo would then take the whole council down
|
|
97
|
+
// rather than the one seat it names.
|
|
98
|
+
return {
|
|
99
|
+
fields: {
|
|
100
|
+
disabled_reason,
|
|
101
|
+
policy_exclusion: classifyPolicyExclusion(raw['policy_exclusion']),
|
|
102
|
+
content_ceiling: _classifyContentClass(raw['content_ceiling'], _DEFAULT_SEAT_CEILING),
|
|
103
|
+
},
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
//# sourceMappingURL=seat_policy.js.map
|
|
@@ -40,6 +40,8 @@
|
|
|
40
40
|
*/
|
|
41
41
|
import { classifyBilling, isCommunityCli, strongestAuth, PROVIDER_CLI_META, } from '../_lib/environment_detector.js';
|
|
42
42
|
import { resolve_mode } from './modes.js';
|
|
43
|
+
import { admitsContent, DEFAULT_SEAT_CEILING, } from './content_ceiling.js';
|
|
44
|
+
import { policyExclusionReason } from './seat_policy.js';
|
|
43
45
|
/** The accepted values of the `mode` key, including the resolver-only `auto`. */
|
|
44
46
|
export const VALID_TRANSPORT_MODES = new Set([
|
|
45
47
|
'api',
|
|
@@ -84,6 +86,22 @@ export function resolveTransport(opts) {
|
|
|
84
86
|
const auth = strongestAuth(report, provider);
|
|
85
87
|
const authSource = auth?.source ?? null;
|
|
86
88
|
const billing = classifyBilling(provider, authSource);
|
|
89
|
+
// Policy first (2.1). Before the mode dispatch, because `api` and `cli`
|
|
90
|
+
// below return `available: true` UNCONDITIONALLY — a refusal placed after
|
|
91
|
+
// them would be unreachable for exactly the seats it exists to stop.
|
|
92
|
+
const exclusion = opts.policyExclusion ?? null;
|
|
93
|
+
if (exclusion !== null) {
|
|
94
|
+
return unavailable(provider, billing, policyExclusionReason(exclusion, provider), exclusion);
|
|
95
|
+
}
|
|
96
|
+
// Content ceiling (2.2). Only runs when the caller declared what it would
|
|
97
|
+
// send; an undeclared pass is unchanged, not silently refused.
|
|
98
|
+
const contentClass = opts.contentClass ?? null;
|
|
99
|
+
if (contentClass !== null) {
|
|
100
|
+
const verdict = admitsContent(opts.seatCeiling ?? DEFAULT_SEAT_CEILING, contentClass);
|
|
101
|
+
if (!verdict.admitted) {
|
|
102
|
+
return unavailable(provider, billing, `${provider}: ${verdict.reason ?? 'content ceiling refused the payload'}`, 'policy_content_ceiling');
|
|
103
|
+
}
|
|
104
|
+
}
|
|
87
105
|
if (mode === 'manual') {
|
|
88
106
|
return {
|
|
89
107
|
available: true,
|
|
@@ -173,6 +191,9 @@ export function resolveMemberTransport(opts) {
|
|
|
173
191
|
// optional-boolean field, so the key is omitted entirely rather than
|
|
174
192
|
// forwarded as `apiKeyPresent: undefined`.
|
|
175
193
|
...(opts.apiKeyPresent !== undefined ? { apiKeyPresent: opts.apiKeyPresent } : {}),
|
|
194
|
+
policyExclusion: opts.policyExclusion ?? null,
|
|
195
|
+
seatCeiling: opts.seatCeiling ?? null,
|
|
196
|
+
contentClass: opts.contentClass ?? null,
|
|
176
197
|
});
|
|
177
198
|
return { ...resolved, configuredMode };
|
|
178
199
|
}
|
package/llms.txt
CHANGED
|
@@ -37,11 +37,11 @@ brand-strategy: Ground a brand strategy from the corpus — archetype, opposable
|
|
|
37
37
|
brand-to-tokens: Turn a confirmed brand identity into a DTCG .tokens.json source of truth — emit CSS vars + Tailwind via design-tokens, export locked brand deck templates.
|
|
38
38
|
bug-analyzer: Use when the user shares a Sentry error, Jira bug ticket, or error description and wants root cause analysis. Also for proactive bug hunting and code audits for hidden bugs.
|
|
39
39
|
build-buy-partner: Use when deciding insource vs outsource vs acquire — integration-cost analysis, dependency-risk, optionality preservation. Triggers on 'should we build', 'buy vs partner'.
|
|
40
|
-
canvas-design:
|
|
40
|
+
canvas-design: Creating static visual art — posters, marketing visuals, brand assets, PDF/PNG design pieces — on 'design a poster' or 'mach uns ein Visual'. Grounded by design-intelligence.
|
|
41
41
|
character-consistency: Use when a character must stay visually identical across AI video scenes — locks identity tokens (silhouette, palette, wardrobe, prop) in JSON. Triggers 'character lock', 'same character'.
|
|
42
42
|
check-refs: Use when verifying cross-references between skills, rules, commands, guidelines, and context documents are not broken after edits, renames, or deletions.
|
|
43
43
|
churn-prevention: Use when designing churn defence — health-score signals, churn-cause split (involuntary / value / relationship / fit), early-warning loop. Triggers on 'why are accounts leaving'.
|
|
44
|
-
code-intelligence: Route codebase-structure questions (who calls X, where used, what imports, change-impact) to
|
|
44
|
+
code-intelligence: Route codebase-structure questions (who calls X, where is this used, what imports, change-impact) to an existing code-graph first: cheaper, never more precise; grep stays routine. Also 'call graph'.
|
|
45
45
|
code-refactoring: When the user says 'refactor this', 'rename class', or 'move method'. Safely refactors code in any language — finds all callers, updates downstream dependencies, verifies via quality tools.
|
|
46
46
|
code-review: Use when the user says \"review this\", \"check my code\", or wants feedback on changes. Reviews for correctness, quality, security, and coding standards.
|
|
47
47
|
command-routing: Use when the user invokes a slash command like /create-pr, /commit, /fix-ci, or pastes command file content — routes to the right command with context inference and GitHub API patterns.
|
|
@@ -57,7 +57,7 @@ context-authoring: Use when filling knowledge-layer context files — auth-model
|
|
|
57
57
|
context-document: Use when the user says \"create context\", \"document this area\", or wants a structured snapshot of a codebase area for agent orientation.
|
|
58
58
|
contract-review: Use when reviewing a contract clause-by-clause from your party's side — buyer/seller/vendor/licensee. Triggers on 'review this contract', 'redline this MSA', 'is this clause a problem'.
|
|
59
59
|
contracts-cognition: Use when reading a contract for risk and constraint — clause shape, redline priority, what the contract actually binds. Triggers on 'review this contract', 'what does this MSA constrain'.
|
|
60
|
-
conventional-commits-writing: When writing commit
|
|
60
|
+
conventional-commits-writing: When writing a commit message, branch name or squash title — measure the repo's own convention first, Conventional Commits as fallback — even on a bare 'commit this'.
|
|
61
61
|
copilot-agents-optimization: Use when optimizing AGENTS.md or copilot-instructions.md — deduplicates against .augment/ content, enforces line budgets, and focuses each file on its audience.
|
|
62
62
|
copilot-config: Tune the GitHub Copilot AI — `copilot-instructions.md`, PR-review patterns, suggestion behavior, output verbosity. NOT for dev-environment setup (use `devcontainer`).
|
|
63
63
|
corpus-grounding: Shared corpus-grounding engine — BM25 + structured filters + decision rules over CSV corpora via a domain manifest. Use when a skill needs grounded pre-action option-space constraints.
|
|
@@ -74,11 +74,11 @@ deep-reading-analyst: Deep analysis of articles/long-form via thinking framework
|
|
|
74
74
|
defense-in-depth: Use when validation needs entry, business-logic, environment, and instrumentation guards so a bad value cannot reach the failure point — turns a local bug fix into a structural one.
|
|
75
75
|
dependency-upgrade: Use when upgrading dependencies — 'update framework X', 'bump runtime version', or 'upgrade packages'. Covers changelog review, breaking-change detection, and verification. Stack-agnostic.
|
|
76
76
|
description-assist: Use when polishing a skill/rule/command/guideline frontmatter description — pushier phrasing, trigger coverage, undertrigger audit — even if the user just says 'make this pushier'.
|
|
77
|
-
design-intelligence: Grounded design brief from the
|
|
78
|
-
design-review: When the user says \"review the design\", \"check the UI\", or wants a comprehensive UI/UX review.
|
|
77
|
+
design-intelligence: Grounded design brief from the corpus — style, WCAG color tokens, typography, layout, anti-patterns. Use on ui-design-brief or any which-style/palette/font/chart call; feeds fe-design.
|
|
78
|
+
design-review: When the user says \"review the design\", \"check the UI\", or wants a comprehensive UI/UX review. 7 phases: interaction, responsiveness, accessibility. Inventory via existing-ui-audit.
|
|
79
79
|
design-system-capture: Write and maintain DESIGN.md + PRODUCT.md — captures visual decisions and interaction patterns so design tasks stay consistent across sessions without re-scanning past work.
|
|
80
80
|
design-tokens: Author a 3-layer DTCG token system (primitive → semantic → component) with light/dark theming; generate CSS vars + Tailwind colors and lint hardcoded values. Use on design tokens / CSS variables.
|
|
81
|
-
design-variations: Produce 3+
|
|
81
|
+
design-variations: Produce 3+ distinct hi-fi design variations — basic to bold, one file with tweak controls — when the user asks for options, alternatives, \"show me a few takes\". Extends fe-design.
|
|
82
82
|
devcontainer: Wire up DevContainers / GitHub Codespaces — `devcontainer.json`, container images, secrets, VS Code features, port forwarding. NOT for tuning Copilot itself (use `copilot-config`).
|
|
83
83
|
developer-like-execution: Use when implementing, debugging, refactoring, or reviewing code — enforces the think → analyze → verify → execute workflow — even when the user just says 'implement X' without naming it.
|
|
84
84
|
discovery-interview: When running discovery interviews — question-bank build, bias audit, insight extraction. Triggers on 'audit my guide', 'extract insights from transcript', 'is my hypothesis falsifiable'.
|
|
@@ -92,10 +92,10 @@ emit-tickets: Use when materialising a roadmap into a ticket bundle — 'turn th
|
|
|
92
92
|
error-handling-patterns: Use when picking a failure-reporting strategy — exceptions vs Result types, recoverable vs not, retry / circuit-breaker / graceful degradation — decision framework only, catalogues externalized.
|
|
93
93
|
"estimate-ticket": Estimate a Jira/Linear ticket — 'estimate PROJ-123', 'wie groß ist das?', 'should we split this?' — size + risk + split + uncertainty, sibling of /refine-ticket, close-prompt.
|
|
94
94
|
evaluate-llm-feature: Black-box evaluation of a shipped LLM feature — adversarial probes for hallucination, prompt-injection, and cost-runaway vs stated expectations. Not RAG/embedding. Triggers 'review my chatbot'.
|
|
95
|
-
existing-ui-audit: Use BEFORE writing or editing any non-trivial UI — inventories components, design tokens, shadcn primitives, and reusable patterns into state.ui_audit. Hard gate for
|
|
95
|
+
existing-ui-audit: Use BEFORE writing or editing any non-trivial UI — inventories components, design tokens, shadcn primitives, and reusable patterns into state.ui_audit. Hard gate for fe-design.
|
|
96
96
|
expansion-playbook: Use when designing account-expansion mechanics — upsell vs cross-sell, expansion-trigger signals, NRR cognition. Triggers on 'lift NRR', 'when do we upsell vs cross-sell'.
|
|
97
97
|
experiment-loop: Use to drive a scalar metric down or up across bounded iterations — keep on strict improvement, revert otherwise, state on disk. Triggers 'minimize X', 'optimize until it stops improving'.
|
|
98
|
-
fe-design: Frontend design heuristics
|
|
98
|
+
fe-design: Frontend design heuristics and the loop applying them: existing-ui-audit, brief, inventory, build, review. Use when building or changing any UI, not only when planning one.
|
|
99
99
|
feature-planning: Use when the user says \"plan a feature\", \"brainstorm\", \"explore this idea\", or wants to go from idea to structured plan and roadmap.
|
|
100
100
|
file-editor: Use when opening edited files in the user's IDE. Reads settings from .agent-settings.yml to determine IDE and whether auto-open is enabled.
|
|
101
101
|
finishing-a-development-branch: Use when the feature is implementation-complete and the next step is 'ship it' — verifies, cleans up, routes to merge/PR/park/discard; never destroys work without explicit confirmation.
|
|
@@ -225,7 +225,7 @@ prompt-optimizer: When the user wants a prompt optimized for ChatGPT, Claude, Ge
|
|
|
225
225
|
prompt-validator: Pre-spend contradiction gate for AI-video runs: checks every prompt in the batch, blocks on style / character / physics mismatch. Triggers 'validate the prompts', 'check the storyboard'.
|
|
226
226
|
quality-tools: Use when PHPStan, Rector, or ECS output appears — \"phpstan says mixed\", type errors, \"fix code style\", \"run rector\" — even when Eloquent/Laravel/model code is also mentioned.
|
|
227
227
|
react-native-setup: Use when setting up React Native or Expo dev environments — Xcode, Android Studio, CocoaPods, EAS, Metro, New Architecture — even when the user just says 'my RN build won't start'.
|
|
228
|
-
react-shadcn-ui:
|
|
228
|
+
react-shadcn-ui: Building React UI on shadcn/ui primitives + Tailwind — the apply/review/polish skill dispatched by `directives/ui/*` for the `react-shadcn` stack; idiom from tailwind-engineer.
|
|
229
229
|
readme-reviewer: Use when reviewing a README for accuracy, usability, and alignment with the actual repository. Detects invented content, broken setup steps, and structural issues.
|
|
230
230
|
readme-writing: Use when creating, rewriting, or significantly improving a README based on the actual repository structure, commands, and intended audience.
|
|
231
231
|
readme-writing-package: Use when creating or rewriting a README for a reusable package or library. Focus on installability, minimal usage example, compatibility, and developer onboarding.
|
|
@@ -275,22 +275,22 @@ subagent-orchestration: Use when orchestrating implementer/judge subagents — f
|
|
|
275
275
|
supply-chain-intake: Before adding/installing any dependency the agent named — verify the package exists (slopsquatting: ~1 in 5 AI suggestions are hallucinated), isn't typo-adjacent, is pinned + locked, and CVE-scanned
|
|
276
276
|
symfony-workflow: Writes Symfony PHP — DI container, bundles, Doctrine, Messenger, Security voters, console commands. For Laravel / Eloquent / Artisan use `laravel`. For framework-free PHP use `php-coder`.
|
|
277
277
|
systematic-debugging: Use on a bug, test failure, crash, or unexpected behavior — enforce reproduce → isolate → hypothesize → verify before any fix; fires even on 'this is broken' / 'quick fix'.
|
|
278
|
-
tailwind-engineer:
|
|
278
|
+
tailwind-engineer: Writing or reviewing Tailwind CSS — utility-first, design tokens, no inline-style drift, responsive variants, dark mode — 'style this' or 'mach das hübsch'. Pairs with react-shadcn-ui.
|
|
279
279
|
tech-debt-tracker: Use when surfacing tech debt as trackable items — interest-vs-principal framing, prioritisation by carrying cost, repayment plan — even if the user just says 'this codebase is a mess'.
|
|
280
280
|
technical-specification: Use when the user says \"write a spec\", \"create RFC\", \"write a PRD\", or \"document this decision\". Writes technical specifications, PRDs, RFCs, and ADRs with clear structure.
|
|
281
281
|
terraform: Use when writing Terraform — AWS modules, resources, variables, outputs, remote state — even when the user just says 'provision this infra' or 'add an S3 bucket' without naming Terraform.
|
|
282
282
|
terragrunt: Use when working with Terragrunt — DRY multi-env configs, module dependencies, remote state orchestration — even when the user just says 'deploy this to staging and prod' without naming Terragrunt.
|
|
283
283
|
test-case-discovery: Use BEFORE writing any test — enumerate cases per behavior (happy / boundary / error / abuse), prioritize by likelihood × impact, cross-check via subagent — even if the user just says 'add tests'.
|
|
284
|
-
test-driven-development:
|
|
284
|
+
test-driven-development: Implementing a feature, fixing a bug, refactoring — failing test first, then the code. For a WRONG test, `testing-anti-patterns` wins.
|
|
285
285
|
test-performance: Use when optimizing test suite performance — database setup, seeder optimization, parallel testing, CI pipeline efficiency, or RefreshDatabase alternatives.
|
|
286
|
-
testing-anti-patterns:
|
|
286
|
+
testing-anti-patterns: BEFORE writing/changing tests, mocks, or test-only methods on production classes — mocking-the-mock, production pollution, overfit assertions; for order, `test-driven-development` wins.
|
|
287
287
|
threat-modeling: Use when adding auth, webhooks, uploads, queues, secrets, tenant boundaries, or public endpoints — produces trust boundaries + abuse cases mapped to files, BEFORE implementation.
|
|
288
288
|
throughput-vs-morale-tradeoff: Use when balancing eng-team velocity vs quality vs burnout — on-call load, focus fragmentation, reorg shock. Triggers on 'team is burning out', 'why is velocity dropping'.
|
|
289
289
|
token-optimizer: Use BEFORE any verbose CLI run, large file read, doc conversion, or near-context handoff — decision tree keyed by intent citing the canonical token-saving asset.
|
|
290
290
|
traefik: Use when setting up Traefik as a local reverse proxy — real domains on 127.0.0.1, trusted HTTPS via mkcert, automatic service discovery, and multi-project routing.
|
|
291
291
|
typography-system: Derive a type system from a style constraint — font pairings, scale/line-height/weights, DTCG tokens via design-tokens. Use to choose fonts or build a typographic scale.
|
|
292
|
-
ui-apply-generic: Use when implementing a UI brief on a stack with no framework executor — Svelte, Astro, Angular, plain HTML.
|
|
293
|
-
ui-component-architect:
|
|
292
|
+
ui-apply-generic: Use when implementing a UI brief on a stack with no framework executor — Svelte, Astro, Angular, plain HTML. Stack-independent sibling of react-shadcn-ui; idiom from the stack corpus.
|
|
293
|
+
ui-component-architect: Shaping a UI component tree — composition vs inheritance, slot patterns, prop API design, controlled vs uncontrolled, polymorphic — on 'split this component'. Feeds react-shadcn-ui.
|
|
294
294
|
unit-economics-modeling: Use when modeling CAC, LTV, payback, contribution margin, or burn-multiple per customer — SaaS, marketplace, or transactional. Triggers on 'are we unit-economic', 'what is our LTV/CAC'.
|
|
295
295
|
universal-project-analysis: Deep multi-pass codebase audit — orchestrates project-analysis-core plus the framework-specific project-analysis-*; explicit request only. Single-pass scan → project-analyzer.
|
|
296
296
|
upstream-contribute: Use when a learning, new skill, rule improvement, or bug fix from a consumer project should be contributed back to the shared agent-config package.
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@event4u/agent-config",
|
|
3
3
|
"mcpName": "io.github.event4u-app/agent-config",
|
|
4
|
-
"version": "14.
|
|
4
|
+
"version": "14.21.0",
|
|
5
5
|
"description": "Governed skills, rules and commands for AI coding tools (Claude Code, Cursor, Windsurf, Copilot) — every claim machine-checked, including the counts in these badges.",
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"private": false,
|
|
@@ -34,9 +34,9 @@
|
|
|
34
34
|
"note": "The nine active non-draft roadmaps that predate the `relates:` requirement (road-to-agentic-engineering-assurance, road-to-component-library-lifecycle, road-to-council-evidence-integrity, road-to-frontend-fidelity-calibration, road-to-monorepo-scope-and-detection, road-to-observability-plate, road-to-role-scoped-spawn-profiles, road-to-skill-delivery-over-mcp, road-to-standing-payload-diet). This is a NEW obligation rather than a repaired scan root, and the ratchet is the point: `depends:` shipped in the template with nothing requiring it and measured 0 adoption, so requiring `relates:` retroactively across the estate would be an owner-reserved write with no analysis behind the edges it would have to invent (held as step 4.3, deferred). Clearing an entry here is one roadmap at a time: run `agent-config roadmap:context` on it, write the rows the probe surfaces, and lower the count."
|
|
35
35
|
},
|
|
36
36
|
"check_source_size_budget": {
|
|
37
|
-
"count":
|
|
38
|
-
"landed": "2026-09-
|
|
39
|
-
"note": "Lowered 18,068 -> 18,062 on 2026-09-06 by drain/host-enforcement-truth. Collapsing the five per-host binding constants in install.ts into src/scripts/hooks/host_lowering.yaml removed 29 lines from a file ~3,900 past the 1500 cap, where every line costs one excess; the journal's surface column and its extracted migration helper gave 23 back. Lowered 18,245 -> 18,230 on 2026-09-02 by drain/publication-integrity-option-a. Phase 2 Option A moved the authoring instruction out of the generator: release.ts, which sits ~530 lines PAST the 1500 cap so every line there costs one excess, loses the comment emission, a 16-line HEAD_DEFAULT doc block and its alias const, and gains a shorter writer docstring. The doc block moved to _lib/release_material.ts, which is 289 lines and therefore UNDER the cap, so the same prose now costs nothing. Net -15. The branch first came in at +8 and paid its own way down rather than raising the baseline.",
|
|
37
|
+
"count": 18016,
|
|
38
|
+
"landed": "2026-09-07",
|
|
39
|
+
"note": "MERGE RECONCILIATION 2026-09-07, 18,061/18,017 -> 18,016. drain/admissible-council-seats and fix/release-obligation-answerable each lowered from the SAME 18,062 base in parallel — main to 18,061 (net -1: curlJsonPost, the seat-status helpers and the per-seat config parsing moved out of the three over-ceiling council files) and the branch to 18,017 (net -45: the /dev/tty prompt helpers moved out of release.ts into _lib/tty_prompt.ts, 76 lines and UNDER the cap, taking release.ts 1871 -> 1826). Taking either side alone is wrong in a different direction, which is the reconciliation doctrine this entry already carried from 2026-09-03: main's value silently discards the branch's earned -45, and the branch's value RAISES the baseline against main and weakens the gate. The committed number is neither — it is the gate's own output on the MERGED tree, where both reductions apply, and it equals the arithmetic (18,062 - 45 - 1 = 18,016) rather than being reconciled by it. LOCAL READING, ADMISSIBLE ON THIS ENTRY'S OWN DISCRIMINATOR: the gate walks only SOURCE_ROOTS=['src'] counting excess over the 1500-line ceiling, and `git status --porcelain -uall -- src/` reports ZERO untracked files, so a clean CI checkout of this commit measures the same 18,016. If CI reads anything else, CI's number is the correct one and this note is the record of why. Carried in the merge commit because tests/scripts/check_source_size_budget.test.ts asserts baseline == live and therefore BLOCKS where the gate only advises. PRIOR ENTRY: Lowered 18,062 -> 18,061 on 2026-09-07 by drain/admissible-council-seats. The branch's own additions to the three over-ceiling files (ai_council/config.ts, council_cli.ts, ai_council/clients.ts) came in at +96 excess and were paid down to -1 rather than baselined, in the four ways this entry's doctrine prescribes: `curlJsonPost` moved out of clients.ts into ai_council/api_transport.ts (UNDER the cap, so the prose and the three new provider shims cost nothing); the seat-status helpers moved out of council_cli.ts into ai_council/status_surface.ts, taking an orphaned doc comment with them; the three new per-seat config fields' parsing moved out of config.ts into ai_council/seat_policy.ts::parseSeatFields; and three prose blocks that the branch's own transport repair made FACTUALLY FALSE (the `openai` / `google-genai` Python-SDK claims, and a `_curlJsonPost` pointer to a function that had moved) were corrected and shortened in the same change. LOCAL READING, ADMISSIBLE ON THIS ENTRY'S OWN DISCRIMINATOR: the gate walks only SOURCE_ROOTS=['src'] counting excess over the 1500-line ceiling, and `git status --porcelain -uall -- src/` reports zero untracked files with the five new src/ files staged, so a clean CI checkout of this commit measures the same 18,061. If CI reads anything else, CI's number is the correct one and this note is the record of why. Carried here because tests/scripts/check_source_size_budget.test.ts:166 asserts baseline == live and therefore blocks where the gate only advises -- the lowering commit must carry its own baseline update. Lowered 18,068 -> 18,062 on 2026-09-06 by drain/host-enforcement-truth. Collapsing the five per-host binding constants in install.ts into src/scripts/hooks/host_lowering.yaml removed 29 lines from a file ~3,900 past the 1500 cap, where every line costs one excess; the journal's surface column and its extracted migration helper gave 23 back. Lowered 18,245 -> 18,230 on 2026-09-02 by drain/publication-integrity-option-a. Phase 2 Option A moved the authoring instruction out of the generator: release.ts, which sits ~530 lines PAST the 1500 cap so every line there costs one excess, loses the comment emission, a 16-line HEAD_DEFAULT doc block and its alias const, and gains a shorter writer docstring. The doc block moved to _lib/release_material.ts, which is 289 lines and therefore UNDER the cap, so the same prose now costs nothing. Net -15. The branch first came in at +8 and paid its own way down rather than raising the baseline.",
|
|
40
40
|
"reason": "Lowered 18,230 -> 18,209 on 2026-09-03 by drain/artifact-location. `doctor --strict` needed 16 lines of integration wiring in cmd_doctor.ts, which sits ~2,100 lines PAST the 1500 cap so every line there costs one excess. The branch first came in at +137 and paid its own way down rather than raising the baseline: the strict semantics, threshold, kill switch, payload helper and argv fragments moved to _cli/_doctor_strict.ts (~160 lines, under the cap, costs nothing), taking it to +15; then the 37-line drift renderer moved to _cli/_doctor_emit.ts. Net -21. The renderer was chosen deliberately: pure rendering, two dependencies, no argparse involvement and therefore none of the byte-parity risk the parser surface carries against the retired Python implementation; nothing outside cmd_doctor.ts imported it. An AI council on the residual split (a: raise by 15 / d: descope the feature); neither was taken, because a parallel branch demonstrated the same day that paying down is achievable in this codebase, which is the evidence the split lacked. This gate's own test comment prescribes it: a commit that splits a god-file lowers the excess and must carry the lowered baseline. MERGE RECONCILIATION 2026-09-03, 18,209 -> 18,205: drain/wired-instruments and drain/artifact-location each lowered from the SAME 18,230 base in parallel -- the branch to 18,226 (net -4, run_continuation_hook.ts's termination ladder moved to _lib/continuation_ladder.ts and runtime_journal.ts's prose to _lib/outcome_vocabularies.ts, both under the cap) and main to 18,209 (net -21, the doctor --strict split above). Taking either side alone is wrong in a different direction: main's value silently discards the branch's earned -4, and the branch's value RAISES the baseline against main and weakens the gate. The committed number is neither -- it is the gate's own output on the merged tree, where both reductions apply. THIS LOCAL READING IS ADMISSIBLE HERE, and the discriminator matters because this file's ci-parity:local-only entry records a 165 -> 164 lowering that was reverted for exactly the opposite case: that count depends on how the environment resolves the projection surface, whereas this gate walks only SOURCE_ROOTS = ['src'] (check_source_size_budget.ts:88) counting excess lines over the 1500 ceiling, and `git status --porcelain -uall -- src/` reported ZERO untracked files, so a clean CI checkout of this commit measures the same 18,205. If CI reads anything else, CI's number is the correct one and this note is the record of why. LOWERED 18,205 -> 18,193 on 2026-09-03 by fix/release-curation-ask. The change adds a fifth publication guard, and its own additions landed at +10 excess: release.ts 2,008 -> 2,018, every line of which is past the 1500 cap. Paid down rather than baselined, in the two ways this gate's doctrine prescribes. The guard's rationale (~13 lines of docstring, including why prMerged is a parameter rather than a call-site if) lives in release_publication.ts, 915 lines and therefore UNDER the cap, so the prose costs nothing; and the two pure text helpers _splitlines and _rstrip moved out of release.ts into release_env.ts (333 lines, also under the cap), taking release.ts to 1,996 -- 12 BELOW where it started. The branch came in at +10 and leaves at -12. Recorded while moving them: _splitlines is duplicated a third and fourth time at adoption_report.ts:106 and readme_linter.ts:418; collapsing all three onto one definition touches three modules and was left out of this change rather than done silently. LOWERED 18,193 -> 18,180 on 2026-09-03 by fix/release-branch-reuse. The step-1 branch decision -- twenty lines of if/else in release.ts -- became one call to checkout_release_branch in release_publication.ts, which is 1,019 lines and therefore UNDER the 1500 cap, so the twenty lines plus a ~35-line rationale now cost nothing. release.ts 1,996 -> 1,983, every one of those lines being past the cap. The drill grew 765 -> 918 for two new scenarios and the snapshot/restore that keeps them from writing the real CHANGELOG, and it is under the cap too. A change that adds a feature and lowers the excess is the shape this gate's doctrine asks for; the alternative was a +13 raise for logic that had no business in the god-file. LOWERED 18,180 -> 18,068 on 2026-09-05 by fix/release-gate-producer-parity. The change wires the governance-mix obligation into the writer and the two local guards and adds a release-gate locality registry; its own additions to release.ts landed at +19 excess, every one of those lines past the 1500 cap. Paid down rather than baselined, in the way this entry's doctrine prescribes: the 132-line test-count-trend unit (_TEST_COUNT_LINE_RE, _TEST_LIST_MAX_BUFFER, _count_from_list_result, _count_tests_current, _previous_test_count_from_changelog, _render_test_trend_line) moved from release.ts into release_publication.ts, which is under the cap and where it now belongs on merit rather than only on arithmetic -- the same diff makes the missing `Tests: N` footer a section-level publication blocker, so the writer that emits the footer and the guard that refuses its absence sit in one module. release.ts 1,983 -> 1,871. All four names are re-exported from release.ts, so tests/scripts/release.test.ts is unchanged. LOCAL READING, ADMISSIBLE ON THIS ENTRY'S OWN DISCRIMINATOR: the gate walks only SOURCE_ROOTS=['src'] counting excess over the ceiling, and `git status --porcelain -uall -- src/` reports ZERO untracked files with the two new src/ files staged, so a clean CI checkout of this commit measures the same 18,068. If CI reads anything else, CI's number is the correct one and this note is the record of why."
|
|
41
41
|
},
|
|
42
42
|
"ci-parity:local-only": {
|
|
@@ -90,9 +90,10 @@ jobs:
|
|
|
90
90
|
guard: ~
|
|
91
91
|
|
|
92
92
|
highlight-plausibility:
|
|
93
|
-
# The job that failed 14.17.0. All
|
|
94
|
-
# unrewritten derived marker, `_none_` contradiction
|
|
95
|
-
#
|
|
93
|
+
# The job that failed 14.17.0. All three sub-checks (authoring instruction,
|
|
94
|
+
# unrewritten derived marker, `_none_` contradiction) run locally from local
|
|
95
|
+
# tags alone — no origin, no HEAD_REF. It was four until ADR-261 deleted the
|
|
96
|
+
# governance-mix written answer and its promise read-back.
|
|
96
97
|
local: './scripts-run src/scripts/check_release_highlights --version ${VERSION}'
|
|
97
98
|
verify: true
|
|
98
99
|
script: src/scripts/check_release_highlights.ts
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Ask before park — the inline `blocked-by:` marker, and the decision that must
|
|
3
|
+
* precede writing one.
|
|
4
|
+
*
|
|
5
|
+
* road-to-asked-not-parked Phase 3. `terminal-states.md` defines `blocked` to
|
|
6
|
+
* include "a decision only the user can make", and `roadmap-process-loop.md`
|
|
7
|
+
* writes and reads the inline marker with NO ask in between. The result is a
|
|
8
|
+
* decision the user never sees: it is filed, correctly labelled, in a file
|
|
9
|
+
* nobody is watching.
|
|
10
|
+
*
|
|
11
|
+
* This module is the executable half of the fix. It answers two questions and
|
|
12
|
+
* nothing else:
|
|
13
|
+
*
|
|
14
|
+
* 1. Given a context — is the host interactive, was the question put, was it
|
|
15
|
+
* answered, did it time out — what does the run DO? ({@link decideAsk})
|
|
16
|
+
* 2. What does the marker then look like? ({@link renderBlockedByMarker})
|
|
17
|
+
*
|
|
18
|
+
* The obligations it encodes:
|
|
19
|
+
*
|
|
20
|
+
* - On an interactive host a user-decision block is PUT TO THE USER FIRST. Only
|
|
21
|
+
* a decline, a timeout, or a non-interactive context writes a marker.
|
|
22
|
+
* - A TIMEOUT IS NOT CONSENT, and neither is a non-interactive context. Where
|
|
23
|
+
* the ask cannot be completed the run ends `approval-required` — a state
|
|
24
|
+
* `terminal-states.md` already defines — carrying the unanswered question
|
|
25
|
+
* verbatim and its conservative default UNADOPTED. Nothing here ever returns
|
|
26
|
+
* the default as an answer.
|
|
27
|
+
* - The marker records WHETHER the question was put, and when it was not, WHY.
|
|
28
|
+
* `asked: no` with no reason is not a legal marker: the whole point is that a
|
|
29
|
+
* later reader can tell a decision that was declined from one that was never
|
|
30
|
+
* offered.
|
|
31
|
+
*/
|
|
32
|
+
import type { RunTerminalState } from './outcome_vocabularies.js';
|
|
33
|
+
|
|
34
|
+
/** A parsed inline `<!-- blocked-by: … -->` marker. */
|
|
35
|
+
export interface BlockedByMarker {
|
|
36
|
+
/** The blocker id the marker points at. */
|
|
37
|
+
readonly id: string;
|
|
38
|
+
/** Whether the question was put to the user. `null` when the marker predates the field. */
|
|
39
|
+
readonly asked: boolean | null;
|
|
40
|
+
/** Why it was not put. Required whenever `asked` is `false`. */
|
|
41
|
+
readonly reason: string | null;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* The marker grammar, extended with an optional `| asked: …` tail.
|
|
46
|
+
*
|
|
47
|
+
* The tail is OPTIONAL in the grammar and required by the linter for the
|
|
48
|
+
* user-decision class only. Parsing and policy are separated on purpose: a
|
|
49
|
+
* parser that rejects the old shape would make every archived roadmap
|
|
50
|
+
* unreadable, which is a migration, not a fix.
|
|
51
|
+
*/
|
|
52
|
+
export const BLOCKED_BY_MARKER_RE =
|
|
53
|
+
/<!--[ \t]*blocked-by:[ \t]*([a-z0-9-]+)[ \t]*(?:\|[ \t]*asked:[ \t]*(yes|no)[ \t]*(?:[—-][ \t]*([^>]*?))?[ \t]*)?-->/i;
|
|
54
|
+
|
|
55
|
+
/** Parse one marker, or `null` when the text carries none. */
|
|
56
|
+
export function parseBlockedByMarker(text: string): BlockedByMarker | null {
|
|
57
|
+
const m = BLOCKED_BY_MARKER_RE.exec(text);
|
|
58
|
+
if (m === null) {
|
|
59
|
+
return null;
|
|
60
|
+
}
|
|
61
|
+
const asked = m[2] === undefined ? null : m[2].toLowerCase() === 'yes';
|
|
62
|
+
const reason = m[3] === undefined || m[3].trim() === '' ? null : m[3].trim();
|
|
63
|
+
return { id: (m[1] as string).toLowerCase(), asked, reason };
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/** Render a marker. Throws on `asked: no` with no reason — the state the field exists to prevent. */
|
|
67
|
+
export function renderBlockedByMarker(marker: BlockedByMarker): string {
|
|
68
|
+
if (marker.asked === null) {
|
|
69
|
+
return `<!-- blocked-by: ${marker.id} -->`;
|
|
70
|
+
}
|
|
71
|
+
if (marker.asked) {
|
|
72
|
+
return `<!-- blocked-by: ${marker.id} | asked: yes -->`;
|
|
73
|
+
}
|
|
74
|
+
const reason = (marker.reason ?? '').trim();
|
|
75
|
+
if (reason === '') {
|
|
76
|
+
throw new Error(
|
|
77
|
+
`blocked-by marker for '${marker.id}': asked: no needs a reason — ` +
|
|
78
|
+
'a marker that does not say why the question was not put is the parking ' +
|
|
79
|
+
'lot this field exists to end',
|
|
80
|
+
);
|
|
81
|
+
}
|
|
82
|
+
return `<!-- blocked-by: ${marker.id} | asked: no — ${reason} -->`;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/** What the run knows when it reaches a step blocked on a user decision. */
|
|
86
|
+
export interface AskContext {
|
|
87
|
+
/** The blocker id. */
|
|
88
|
+
readonly id: string;
|
|
89
|
+
/** The question, in the words it would be put in. */
|
|
90
|
+
readonly question: string;
|
|
91
|
+
/** The conservative option, named so a reader can see what was NOT adopted. */
|
|
92
|
+
readonly conservativeDefault: string;
|
|
93
|
+
/** Can this run reach the user at all? */
|
|
94
|
+
readonly interactive: boolean;
|
|
95
|
+
/** Why not, when it cannot. Required whenever `interactive` is `false`. */
|
|
96
|
+
readonly nonInteractiveReason?: string | undefined;
|
|
97
|
+
/** The user's answer, when one arrived. */
|
|
98
|
+
readonly answer?: string | undefined;
|
|
99
|
+
/** The ask was put and no answer came back. */
|
|
100
|
+
readonly timedOut?: boolean | undefined;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/** What the run does next. */
|
|
104
|
+
export interface AskDecision {
|
|
105
|
+
/**
|
|
106
|
+
* `ask` — put the question and come back with an answer or a timeout.
|
|
107
|
+
* `answered` — an answer exists; nothing is parked.
|
|
108
|
+
* `park` — write the marker; the run may continue with other steps.
|
|
109
|
+
* `approval-required` — the run stops and hands the unanswered question back.
|
|
110
|
+
*/
|
|
111
|
+
readonly action: 'ask' | 'answered' | 'park' | 'approval-required';
|
|
112
|
+
/** The terminal state to report, when this decision ends the run. */
|
|
113
|
+
readonly terminal: RunTerminalState | null;
|
|
114
|
+
/** The marker to write, when one is written. */
|
|
115
|
+
readonly marker: string | null;
|
|
116
|
+
/** The text the run puts in front of the user. Never a silent default. */
|
|
117
|
+
readonly surfaced: string | null;
|
|
118
|
+
/** Whether the conservative default was adopted. ALWAYS false — see the module header. */
|
|
119
|
+
readonly defaultAdopted: false;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Decide, for one user-decision block, whether to ask, park, or stop.
|
|
124
|
+
*
|
|
125
|
+
* Order matters: an ANSWER wins over everything, then a timeout, then a
|
|
126
|
+
* non-interactive context, then the interactive path. A timeout is checked
|
|
127
|
+
* before interactivity so a run that asked and got nothing is never quietly
|
|
128
|
+
* re-classified as "could not ask".
|
|
129
|
+
*/
|
|
130
|
+
export function decideAsk(ctx: AskContext): AskDecision {
|
|
131
|
+
const unanswered =
|
|
132
|
+
`${ctx.question}\n\nConservative option (NOT adopted): ${ctx.conservativeDefault}`;
|
|
133
|
+
|
|
134
|
+
if (ctx.answer !== undefined && ctx.answer.trim() !== '') {
|
|
135
|
+
return {
|
|
136
|
+
action: 'answered',
|
|
137
|
+
terminal: null,
|
|
138
|
+
marker: null,
|
|
139
|
+
surfaced: null,
|
|
140
|
+
defaultAdopted: false,
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
if (ctx.timedOut === true) {
|
|
145
|
+
return {
|
|
146
|
+
action: 'approval-required',
|
|
147
|
+
terminal: 'approval-required',
|
|
148
|
+
marker: renderBlockedByMarker({
|
|
149
|
+
id: ctx.id,
|
|
150
|
+
asked: true,
|
|
151
|
+
reason: null,
|
|
152
|
+
}),
|
|
153
|
+
surfaced: unanswered,
|
|
154
|
+
defaultAdopted: false,
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
if (!ctx.interactive) {
|
|
159
|
+
const why = (ctx.nonInteractiveReason ?? '').trim();
|
|
160
|
+
return {
|
|
161
|
+
action: 'park',
|
|
162
|
+
terminal: null,
|
|
163
|
+
marker: renderBlockedByMarker({
|
|
164
|
+
id: ctx.id,
|
|
165
|
+
asked: false,
|
|
166
|
+
reason: why === '' ? 'non-interactive context, reason not recorded' : why,
|
|
167
|
+
}),
|
|
168
|
+
surfaced: unanswered,
|
|
169
|
+
defaultAdopted: false,
|
|
170
|
+
};
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
return {
|
|
174
|
+
action: 'ask',
|
|
175
|
+
terminal: null,
|
|
176
|
+
marker: null,
|
|
177
|
+
surfaced: ctx.question,
|
|
178
|
+
defaultAdopted: false,
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* The user declined to decide. That IS an answer about the ask — the question
|
|
184
|
+
* was put — so the marker records `asked: yes` and the decline is the reason
|
|
185
|
+
* the step stays blocked.
|
|
186
|
+
*/
|
|
187
|
+
export function parkAfterDecline(id: string): string {
|
|
188
|
+
return renderBlockedByMarker({ id, asked: true, reason: null });
|
|
189
|
+
}
|
|
@@ -67,6 +67,23 @@ export interface HostCapabilityManifest {
|
|
|
67
67
|
* stop-loss behaviour — loudly, never silently.
|
|
68
68
|
*/
|
|
69
69
|
worker_respawn: boolean;
|
|
70
|
+
/**
|
|
71
|
+
* The host exposes its own structured-ask tool — a native question picker
|
|
72
|
+
* the agent can call instead of rendering a numbered-options block as prose.
|
|
73
|
+
*
|
|
74
|
+
* A STRICT BOOLEAN, and the per-host detail deliberately lives elsewhere.
|
|
75
|
+
* The source draft for road-to-asked-not-parked proposed a nested object
|
|
76
|
+
* carrying the tool name and the question/option ceilings; `asBool` below
|
|
77
|
+
* coerces every non-`true` value to `false`, so such an object would
|
|
78
|
+
* normalize silently to nothing. The shape lives in
|
|
79
|
+
* `_lib/structured_ask.ts` (`STRUCTURED_ASK_SHAPES`), which a caller reads
|
|
80
|
+
* explicitly; this field answers only whether there is anything to read.
|
|
81
|
+
*
|
|
82
|
+
* `false` is the safe default on purpose: an unknown host degrades to a
|
|
83
|
+
* text ask, and the agent never fires a tool call into a host that has no
|
|
84
|
+
* such tool.
|
|
85
|
+
*/
|
|
86
|
+
structured_ask: boolean;
|
|
70
87
|
/**
|
|
71
88
|
* The host reads a PROJECT-scope MCP config file (`.mcp.json` or its
|
|
72
89
|
* per-host equivalent) and starts the servers it names.
|
|
@@ -105,6 +122,7 @@ const SAFE_DEFAULT: HostCapabilityManifest = {
|
|
|
105
122
|
separate_quota_pool: false,
|
|
106
123
|
agent_teams: false,
|
|
107
124
|
worker_respawn: false,
|
|
125
|
+
structured_ask: false,
|
|
108
126
|
reads_project_mcp_config: false,
|
|
109
127
|
// `true` because the safe answer here is "there is still a step", not
|
|
110
128
|
// "there is nothing to do" — see the field's own note.
|
|
@@ -137,6 +155,7 @@ export function normalizeHostManifest(input: unknown): HostCapabilityManifest {
|
|
|
137
155
|
separate_quota_pool: asBool(src.separate_quota_pool),
|
|
138
156
|
agent_teams: asBool(src.agent_teams),
|
|
139
157
|
worker_respawn: asBool(src.worker_respawn),
|
|
158
|
+
structured_ask: asBool(src.structured_ask),
|
|
140
159
|
reads_project_mcp_config: asBool(src.reads_project_mcp_config),
|
|
141
160
|
// Inverted coercion, matching the inverted default: only an explicit
|
|
142
161
|
// `false` clears the residual, so an absent or malformed value keeps
|
|
@@ -215,9 +234,26 @@ export function normalizeHostManifest(input: unknown): HostCapabilityManifest {
|
|
|
215
234
|
*
|
|
216
235
|
* `agent_teams` — out of scope for a row by construction; see its own field
|
|
217
236
|
* doc comment and the protocol section.
|
|
237
|
+
*
|
|
238
|
+
* `structured_ask: false` — OBSERVED ABSENT, not never-looked, and the
|
|
239
|
+
* difference is why it is written rather than omitted. claude (Claude Code
|
|
240
|
+
* 2.1.263, Opus 5 1M session, 2026-09-07). The host delivers the session's
|
|
241
|
+
* tool surface at session start; that surface carried no structured-ask tool
|
|
242
|
+
* under any name `_lib/structured_ask.ts` matches, and no per-host shape is
|
|
243
|
+
* recorded for it in `STRUCTURED_ASK_SHAPES`. Artefact:
|
|
244
|
+
* `agents/evidence/analysis/structured-ask-host-observation-2026-09.md`. code-comment-allow provenance-comment -- the observation protocol declares a row inadmissible without its artefact citation, so this pointer is part of the contract the row satisfies, not evidence duplicated from a roadmap
|
|
245
|
+
* The observation is about the delivered surface of one session on one
|
|
246
|
+
* version; it is not a claim that the vendor ships no such tool anywhere,
|
|
247
|
+
* and a later session observing one writes the `true` row over it.
|
|
248
|
+
*
|
|
249
|
+
* NO ROW ANYWHERE SETS THE STRUCTURED-ASK FIELD TRUE, and the observation
|
|
250
|
+
* protocol above is the only path to the first one that does. A vendor's
|
|
251
|
+
* documentation is not that path — and the phrasing here avoids the literal
|
|
252
|
+
* key-value pair on purpose, because the roadmap step that ships this field
|
|
253
|
+
* verifies the absence with a grep for exactly that string.
|
|
218
254
|
*/
|
|
219
255
|
const HOST_CAPABILITY_REGISTRY: Readonly<Record<string, Partial<HostCapabilityManifest>>> = {
|
|
220
|
-
claude: { subagent_spawn: true, parallel_spawn: true },
|
|
256
|
+
claude: { subagent_spawn: true, parallel_spawn: true, structured_ask: false },
|
|
221
257
|
};
|
|
222
258
|
|
|
223
259
|
/**
|
|
@@ -323,6 +359,7 @@ const CAPABILITY_FIELDS: readonly CapabilityField[] = [
|
|
|
323
359
|
'separate_quota_pool',
|
|
324
360
|
'agent_teams',
|
|
325
361
|
'worker_respawn',
|
|
362
|
+
'structured_ask',
|
|
326
363
|
'reads_project_mcp_config',
|
|
327
364
|
'mcp_needs_manual_activation',
|
|
328
365
|
];
|