@event4u/agent-config 14.20.0 → 14.22.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 +441 -0
- package/CREDITS.md +37 -0
- package/README.md +1 -1
- package/agents/templates/.ai-council.yml.example +10 -0
- package/dist/agent-src/commands/analyze/inbox.md +381 -6
- package/dist/agent-src/commands/analyze/{reference-repo.md → repo.md} +251 -34
- 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/communication/rules-auto/token-efficiency-mechanics.md +69 -0
- package/dist/agent-src/contexts/execution/auto-dispatch-classification.md +5 -5
- 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-code-graph-interop.md +19 -14
- package/dist/agent-src/rules/external-reference-deep-dive.md +2 -2
- package/dist/agent-src/rules/token-efficiency.md +26 -3
- package/dist/agent-src/scripts/_cli_entry.ts +51 -0
- package/dist/agent-src/scripts/archive_completed_roadmaps.ts +14 -23
- package/dist/agent-src/scripts/roadmap_gates.ts +4 -20
- package/dist/agent-src/scripts/roadmap_set_step.ts +5 -2
- package/dist/agent-src/scripts/stubs_due.ts +4 -18
- package/dist/agent-src/scripts/update_roadmap_progress.ts +7 -24
- package/dist/agent-src/skills/code-intelligence/SKILL.md +25 -0
- 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/roadmap-writing/SKILL.md +17 -0
- package/dist/agent-src/skills/technical-specification/SKILL.md +4 -0
- package/dist/agent-src/templates/agent-settings.md +0 -1
- 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/_cli_entry.js +8 -0
- package/dist/cli-delegate/_lib/pin_resolver.js +170 -0
- package/dist/cli-delegate/archival_sweep.js +8 -0
- package/dist/cli-delegate/archive_completed_roadmaps.js +494 -0
- package/dist/cli-delegate/check_update_banner.js +923 -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-5EK5WQN6.js +28 -0
- package/dist/cli-delegate/chunk-7XGD2OEU.js +96 -0
- package/dist/cli-delegate/{chunk-C6IPZRDA.js → chunk-BIXEDQPZ.js} +7 -1
- package/dist/cli-delegate/chunk-DBH4AHDK.js +155 -0
- package/dist/cli-delegate/{chunk-NUMQHLS5.js → chunk-J3RFNKG4.js} +21 -2
- package/dist/cli-delegate/chunk-JO25G5H7.js +20 -0
- package/dist/cli-delegate/chunk-LTYVIY76.js +7961 -0
- package/dist/cli-delegate/chunk-QAJS5YWY.js +999 -0
- package/dist/cli-delegate/{chunk-ZVBOHSVF.js → chunk-TGIPZBCW.js} +1 -1
- package/dist/cli-delegate/{chunk-YS6XTLEK.js → chunk-TYSY33MD.js} +56 -6
- package/dist/cli-delegate/chunk-ULET4BYS.js +328 -0
- package/dist/cli-delegate/chunk-YDQSHTD7.js +187 -0
- package/dist/cli-delegate/chunk-YWVXLR44.js +48 -0
- package/dist/cli-delegate/chunk-ZZQBUHFK.js +273 -0
- package/dist/cli-delegate/cmd_conformance.js +19 -5
- 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 +27 -3
- package/dist/cli-delegate/cmd_prune.js +2 -2
- package/dist/cli-delegate/cmd_session_recycle.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/cli-delegate/dashboard_mode.js +16 -0
- package/dist/cli-delegate/gate_budget.js +24 -0
- package/dist/cli-delegate/gate_execute.js +25 -0
- package/dist/cli-delegate/guarded_baseline.js +18 -0
- package/dist/cli-delegate/resume_probe.js +22 -0
- package/dist/cli-delegate/roadmap_gates.js +638 -0
- package/dist/cli-delegate/roadmap_set_step.js +173 -0
- package/dist/cli-delegate/stub_queue.js +47 -0
- package/dist/cli-delegate/stubs_due.js +38 -0
- package/dist/cli-delegate/ticket_bundles.js +59 -0
- package/dist/cli-delegate/update_roadmap_progress.js +60 -0
- package/dist/discovery/deprecation-report.md +1 -1
- package/dist/discovery/discovery-manifest.json +60 -29
- 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 +1522 -1008
- package/dist/install/claudeLayerCarriage.js +237 -0
- package/dist/install/claudePathsPlan.js +79 -0
- package/dist/install/hostLayerFingerprint.js +1 -1
- package/dist/install/install.mjs +10 -1
- package/dist/install/partitionEligibility.js +231 -117
- package/dist/install/ruleLayerPartition.js +10 -7
- package/dist/mcp/registry-manifest.json +3 -3
- package/dist/mcp/server.json +2 -2
- package/dist/mcp/server.mjs +39376 -38902
- 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/dist/scripts/condense.js +32 -25
- package/docs/MIGRATION.md +49 -1
- package/docs/contracts/settings-classes.md +1 -1
- package/docs/guidelines/agent-infra/minimal-safe-diff-mechanics.md +73 -0
- package/llms.txt +14 -14
- package/package.json +7 -4
- package/src/agent-src/scripts/_cli_entry.ts +51 -0
- package/src/agent-src/scripts/archive_completed_roadmaps.ts +14 -23
- package/src/agent-src/scripts/roadmap_gates.ts +4 -20
- package/src/agent-src/scripts/roadmap_set_step.ts +5 -2
- package/src/agent-src/scripts/stubs_due.ts +4 -18
- package/src/agent-src/scripts/update_roadmap_progress.ts +7 -24
- package/src/config/continuity-surface.json +509 -0
- package/src/config/estate-count-budget.json +1 -1
- package/src/config/gate-coverage.yml +29 -8
- package/src/config/gate-violation-baselines.json +8 -8
- package/src/config/gitignore-block.txt +19 -0
- package/src/config/host-injection-effect.json +73 -0
- package/src/config/packed-binary-manifest.json +33 -0
- package/src/config/preamble-payload-budget.json +112 -105
- package/src/config/publish-surface.json +4 -2
- package/src/config/release-gate-locality.yml +4 -3
- package/src/install/claudeLayerCarriage.ts +273 -0
- package/src/install/claudePathsPlan.ts +76 -0
- package/src/install/hostLayerFingerprint.ts +1 -1
- package/src/install/partitionEligibility.ts +261 -127
- package/src/install/ruleLayerPartition.ts +19 -23
- package/src/scripts/_dispatch.bash +77 -27
- package/src/scripts/_lib/auto_dispatch.ts +2 -2
- package/src/scripts/_lib/blocked_by_marker.ts +189 -0
- package/src/scripts/_lib/catalogue_layer_parity.ts +146 -0
- package/src/scripts/_lib/continuity_surface.ts +296 -0
- package/src/scripts/_lib/corpus_manifest.ts +26 -9
- package/src/scripts/_lib/hook_settings.ts +1 -1
- package/src/scripts/_lib/host_capability.ts +38 -1
- package/src/scripts/_lib/injection_effect.ts +151 -0
- package/src/scripts/_lib/layer_overlap_notice.ts +50 -6
- package/src/scripts/_lib/packed_binary_predicate.ts +225 -0
- package/src/scripts/_lib/pin_resolver.ts +13 -0
- package/src/scripts/_lib/regression_neighbourhood.ts +1 -1
- package/src/scripts/_lib/release_highlights.ts +19 -211
- package/src/scripts/_lib/release_material.ts +27 -77
- package/src/scripts/_lib/retired_status.ts +74 -0
- package/src/scripts/_lib/skill_catalogue.ts +156 -21
- package/src/scripts/_lib/structured_ask.ts +117 -0
- package/src/scripts/_lib/subagent_capsule.ts +117 -8
- package/src/scripts/_lib/tty_prompt.ts +76 -0
- package/src/scripts/ai_council/api_transport.ts +168 -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/deliberation_metrics.ts +353 -0
- package/src/scripts/ai_council/jury_aggregate.ts +178 -0
- package/src/scripts/ai_council/prompts.ts +9 -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/synthesis_disagreement.ts +206 -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/capture_skill_catalogue.ts +24 -11
- package/src/scripts/check_branch_work_committed.ts +234 -0
- package/src/scripts/check_candidate_lines.ts +499 -0
- package/src/scripts/check_continuity_surface.ts +475 -0
- package/src/scripts/check_dependency_floors.ts +13 -6
- package/src/scripts/check_estate_count.ts +40 -79
- package/src/scripts/check_pack_size.ts +263 -24
- package/src/scripts/check_release_highlights.ts +0 -157
- package/src/scripts/check_roadmap_trackable.ts +19 -16
- package/src/scripts/check_rule_layer_partition.ts +54 -34
- package/src/scripts/check_single_delivery.ts +97 -31
- package/src/scripts/check_update_banner.ts +13 -0
- package/src/scripts/code_graph/build.ts +138 -15
- package/src/scripts/code_graph/cli.ts +76 -8
- package/src/scripts/code_graph/extract.ts +11 -0
- package/src/scripts/code_graph/grammar_probe.ts +43 -0
- package/src/scripts/code_graph/loader.ts +88 -14
- package/src/scripts/code_graph/query.ts +121 -11
- package/src/scripts/code_graph/resolution_tiers.ts +238 -0
- package/src/scripts/code_graph/sqlite_store.ts +247 -6
- package/src/scripts/code_graph/types.ts +66 -1
- package/src/scripts/code_graph/validate.ts +23 -0
- package/src/scripts/condense.ts +34 -22
- package/src/scripts/corpus_manifest.ts +5 -5
- package/src/scripts/council_cli.ts +29 -39
- package/src/scripts/count_intent_disagreement.ts +426 -0
- package/src/scripts/detect_ai_tells.ts +142 -20
- package/src/scripts/handoff_context_hook.ts +79 -0
- package/src/scripts/harvest_reference_tokens.ts +698 -0
- package/src/scripts/hook_effect_doctor.ts +82 -1
- package/src/scripts/hook_manifest.json +1 -1
- package/src/scripts/hook_manifest.yaml +94 -12
- package/src/scripts/hooks/chain_nudge_hook.ts +282 -0
- package/src/scripts/hooks/code_graph_context_hook.ts +212 -0
- package/src/scripts/hooks/concern_registry.ts +6 -2
- package/src/scripts/hooks/delegation_nudge_hook.ts +45 -1
- package/src/scripts/hooks/host_semantics.ts +1 -1
- package/src/scripts/hooks/one_question_per_ask_hook.ts +191 -0
- package/src/scripts/hooks/orchestration_record_hook.ts +2 -2
- package/src/scripts/hooks/rule_inject_hook.ts +1 -1
- package/src/scripts/hooks/skill_route_hook.ts +11 -4
- package/src/scripts/inbox_source_census.ts +322 -0
- package/src/scripts/install-hooks.sh +134 -0
- package/src/scripts/{lint_carrier_integrity.ts → lint_deferral_integrity.ts} +60 -214
- package/src/scripts/lint_plan_risk_register.ts +13 -17
- package/src/scripts/lint_roadmap_blockers.ts +51 -1
- package/src/scripts/mcp_server/suggest_skill.ts +72 -0
- package/src/scripts/mcp_server/tools.ts +2 -56
- package/src/scripts/media/lib/adapter-contract.md +7 -2
- package/src/scripts/media/lib/fixtures/flux/asset-0001.png +0 -0
- package/src/scripts/media/lib/fixtures/gemini-image/asset-0001.png +0 -0
- package/src/scripts/media/lib/fixtures/ideogram/asset-0001.png +0 -0
- package/src/scripts/migrate_command_suggestions.ts +1 -1
- package/src/scripts/probe_unblocked_ask.ts +135 -2
- package/src/scripts/rdp_candidates_delta.ts +276 -0
- package/src/scripts/rdp_quality_eval.ts +217 -19
- 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_mix_taxonomy.json +3 -2
- package/src/scripts/release_publication.ts +34 -29
- package/src/scripts/report_host_injection_effect.ts +189 -0
- package/src/scripts/report_layer_overlap.ts +41 -0
- package/src/scripts/report_skill_menu_census.ts +341 -0
- package/src/scripts/self_review_gate.ts +409 -15
- package/src/scripts/skill_tools/score_skill_relevance.ts +70 -6
- package/src/scripts/skill_tools/suggest_skill_for_task.ts +37 -3
- package/src/vendor/grammars/README.md +61 -0
- package/src/vendor/grammars/tree-sitter-javascript.wasm +0 -0
- package/src/vendor/grammars/tree-sitter-php.wasm +0 -0
- package/src/vendor/grammars/tree-sitter-typescript.wasm +0 -0
- package/src/scripts/_lib/carrier_status.ts +0 -118
- package/src/scripts/_lib/estate_carrier_cases.ts +0 -72
- package/src/scripts/hooks/code_graph_nudge_hook.ts +0 -191
|
@@ -9,10 +9,10 @@
|
|
|
9
9
|
*
|
|
10
10
|
* Corpus: ready (non-draft) roadmap files directly under `agents/roadmaps/` code-comment-allow provenance-comment -- the path is this script's operand, not where the code came from
|
|
11
11
|
* (top level only — `archive/`, `skipped/`, `later/`, `stubs/` excluded, as
|
|
12
|
-
* are `template.md` and `dashboard*`). `status: draft`
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
12
|
+
* are `template.md` and `dashboard*`). `status: draft` frontmatter exempts a
|
|
13
|
+
* file, reported as `draft-exempt` and still counted as scanned. A second
|
|
14
|
+
* exempting status, `carrier`, was deleted by ADR-262: a receiver of deferred
|
|
15
|
+
* work is an ordinary plan and carries a real risk register like any other.
|
|
16
16
|
*
|
|
17
17
|
* Checks per ready file:
|
|
18
18
|
* 1. `## Risk Register` section exists — missing = fail unless the
|
|
@@ -329,20 +329,16 @@ export function validateTable(
|
|
|
329
329
|
}
|
|
330
330
|
|
|
331
331
|
/** A frontmatter status that exempts the file, or `null` when none does. */
|
|
332
|
-
export type ExemptingStatus = 'draft'
|
|
332
|
+
export type ExemptingStatus = 'draft';
|
|
333
333
|
|
|
334
334
|
/**
|
|
335
335
|
* Which frontmatter status exempts this file, per contract § 1.
|
|
336
336
|
*
|
|
337
|
-
*
|
|
338
|
-
*
|
|
339
|
-
*
|
|
340
|
-
*
|
|
341
|
-
*
|
|
342
|
-
* A carrier is not a plan. It holds obligations deferred out of an archived
|
|
343
|
-
* parent, each with an unmet resumption trigger, so a plan risk register for it
|
|
344
|
-
* would be manufactured rather than reported. Same category reason that exempts
|
|
345
|
-
* it from `check_roadmap_trackable`'s `## Phase` requirement.
|
|
337
|
+
* The status is returned rather than a bare boolean so the gate's own output
|
|
338
|
+
* names the state it acted on. That mattered while `carrier` was a second
|
|
339
|
+
* exempting value and the predicate printed `draft-exempt` for it; ADR-262
|
|
340
|
+
* deleted that value, and the shape is kept so a future exemption is one enum
|
|
341
|
+
* member rather than a re-argued boolean.
|
|
346
342
|
*/
|
|
347
343
|
export function exemptingStatus(text: string): ExemptingStatus | null {
|
|
348
344
|
const lines = _splitLines(text);
|
|
@@ -350,7 +346,7 @@ export function exemptingStatus(text: string): ExemptingStatus | null {
|
|
|
350
346
|
for (let i = 1; i < lines.length; i++) {
|
|
351
347
|
const line = lines[i] as string;
|
|
352
348
|
if (line.trim() === '---') return null;
|
|
353
|
-
const m = /^status:\s*(draft
|
|
349
|
+
const m = /^status:\s*(draft)\s*$/.exec(line);
|
|
354
350
|
if (m !== null) return m[1] as ExemptingStatus;
|
|
355
351
|
}
|
|
356
352
|
return null;
|
|
@@ -592,7 +588,7 @@ function _blobAt(absPath: string, sha: string): string | null {
|
|
|
592
588
|
return _git(root, ['show', `${sha}:${rel}`]);
|
|
593
589
|
}
|
|
594
590
|
|
|
595
|
-
export type FileStatus = 'ok' | 'draft-exempt' | '
|
|
591
|
+
export type FileStatus = 'ok' | 'draft-exempt' | 'grandfathered' | 'fail';
|
|
596
592
|
|
|
597
593
|
export interface FileResult {
|
|
598
594
|
file: string;
|
|
@@ -879,7 +875,7 @@ export function main(argv?: readonly string[]): number {
|
|
|
879
875
|
for (const p of targets) {
|
|
880
876
|
const result = checkFile(p);
|
|
881
877
|
scanned += 1;
|
|
882
|
-
if (result.status === 'draft-exempt'
|
|
878
|
+
if (result.status === 'draft-exempt') {
|
|
883
879
|
statusLines.push(` ${result.status}: ${p}`);
|
|
884
880
|
} else if (result.status === 'grandfathered') {
|
|
885
881
|
statusLines.push(` grandfathered: ${p}`);
|
|
@@ -14,6 +14,19 @@
|
|
|
14
14
|
* (`- **Run:**`). A gate that claims to be executable without naming the
|
|
15
15
|
* command is the same defect as a decision with no option set: it reads
|
|
16
16
|
* as actionable and is not.
|
|
17
|
+
* 4. An annotation pointing at a USER-DECISION blocker (one whose `Owner:`
|
|
18
|
+
* is the maintainer, the user, or the owner) carries an `asked:` field
|
|
19
|
+
* recording whether the question was put and, when it was not, why —
|
|
20
|
+
* `<!-- blocked-by: <id> | asked: no — <reason> -->`. A decision only the
|
|
21
|
+
* user can make, filed in a roadmap without ever being put, is the defect
|
|
22
|
+
* road-to-asked-not-parked exists to stop; without this field a marker
|
|
23
|
+
* cannot distinguish a declined decision from an unoffered one.
|
|
24
|
+
*
|
|
25
|
+
* HARD, not ratcheted, and it can be: no active roadmap carries a real
|
|
26
|
+
* checkbox annotation today, so on the day it ships the rule fires on
|
|
27
|
+
* nothing — the same "no backlog to grandfather" argument the `Class:`
|
|
28
|
+
* contract above makes. `later/` and `archive/` are outside this gate's
|
|
29
|
+
* glob and are untouched by it.
|
|
17
30
|
*
|
|
18
31
|
* Fenced code blocks are stripped before scanning so a roadmap that shows
|
|
19
32
|
* the `## Blockers` shape as a documentation example is not flagged.
|
|
@@ -42,7 +55,19 @@ const BLOCKER_HEADING_RE = /^###[ \t]+blocker:[ \t]*(.+?)[ \t]*$/gim;
|
|
|
42
55
|
// wrapped continuation line or inline-code documentation example of the
|
|
43
56
|
// syntax (as this very lint script's own roadmap step describes it) must
|
|
44
57
|
// not be mistaken for a live cross-reference.
|
|
45
|
-
const BLOCKED_BY_LINE_RE =
|
|
58
|
+
const BLOCKED_BY_LINE_RE =
|
|
59
|
+
/^-[ \t]*\[[ xX~-]\].*<!--[ \t]*blocked-by:[ \t]*([a-z0-9-]+)[ \t]*(?:\|[ \t]*asked:[ \t]*(yes|no)[ \t]*(?:[\u2014-][ \t]*([^>]*?))?[ \t]*)?-->/i;
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* The owners whose blockers are the USER-DECISION class.
|
|
63
|
+
*
|
|
64
|
+
* Read off `Owner:` rather than guessed from prose: `terminal-states.md`
|
|
65
|
+
* defines `blocked` to include "a decision only the user can make", and the
|
|
66
|
+
* owner field is the only place a roadmap says which blockers those are.
|
|
67
|
+
* `council`, `implementer` and `agent` are deliberately outside the class —
|
|
68
|
+
* those are not decisions the user is waiting to be asked.
|
|
69
|
+
*/
|
|
70
|
+
const USER_DECISION_OWNER_RE = /^-[ \t]*\*\*Owner:\*\*[ \t]*(maintainer|user|owner)\b/im;
|
|
46
71
|
|
|
47
72
|
const REQUIRED_FIELDS: ReadonlyArray<readonly [string, RegExp]> = [
|
|
48
73
|
['Status', /^-[ \t]*\*\*Status:\*\*/im],
|
|
@@ -154,6 +179,7 @@ function _scanBoth(rawText: string): ScanResult {
|
|
|
154
179
|
const decidability: Violation[] = [];
|
|
155
180
|
const text = _stripFencedCode(rawText);
|
|
156
181
|
const declaredIds = new Set<string>();
|
|
182
|
+
const userDecisionIds = new Set<string>();
|
|
157
183
|
|
|
158
184
|
const sectionMatch = BLOCKERS_SECTION_RE.exec(text);
|
|
159
185
|
if (sectionMatch) {
|
|
@@ -181,6 +207,9 @@ function _scanBoth(rawText: string): ScanResult {
|
|
|
181
207
|
declaredIds.add(cur.id);
|
|
182
208
|
const bodyEnd = i + 1 < heads.length ? (heads[i + 1] as { start: number }).start : section.length;
|
|
183
209
|
const body = section.slice(cur.end, bodyEnd);
|
|
210
|
+
if (USER_DECISION_OWNER_RE.test(body)) {
|
|
211
|
+
userDecisionIds.add(cur.id);
|
|
212
|
+
}
|
|
184
213
|
const missing = REQUIRED_FIELDS.filter(([, re]) => !re.test(body)).map(([name]) => name);
|
|
185
214
|
if (missing.length) {
|
|
186
215
|
violations.push({
|
|
@@ -256,6 +285,27 @@ function _scanBoth(rawText: string): ScanResult {
|
|
|
256
285
|
`(no matching '### blocker: ${id}' in this file)`,
|
|
257
286
|
});
|
|
258
287
|
}
|
|
288
|
+
if (userDecisionIds.has(id)) {
|
|
289
|
+
const asked = m[2] === undefined ? null : (m[2] as string).toLowerCase();
|
|
290
|
+
const reason = ((m[3] as string | undefined) ?? '').trim();
|
|
291
|
+
if (asked === null) {
|
|
292
|
+
violations.push({
|
|
293
|
+
line: i + 1,
|
|
294
|
+
message:
|
|
295
|
+
`blocked-by '${id}' is a user-decision blocker and carries no ` +
|
|
296
|
+
'asked: field — write `| asked: yes` when the question was put, ' +
|
|
297
|
+
'or `| asked: no — <reason>` when it was not',
|
|
298
|
+
});
|
|
299
|
+
} else if (asked === 'no' && reason === '') {
|
|
300
|
+
violations.push({
|
|
301
|
+
line: i + 1,
|
|
302
|
+
message:
|
|
303
|
+
`blocked-by '${id}' says asked: no with no reason — a marker that ` +
|
|
304
|
+
'does not say why the question was never put is the parking lot ' +
|
|
305
|
+
'this field exists to end',
|
|
306
|
+
});
|
|
307
|
+
}
|
|
308
|
+
}
|
|
259
309
|
}
|
|
260
310
|
violations.sort((a, b) => a.line - b.line);
|
|
261
311
|
decidability.sort((a, b) => a.line - b.line);
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The `suggest_skill_for_task` MCP handler, extracted from `tools.ts`.
|
|
3
|
+
*
|
|
4
|
+
* `tools.ts` sits ~580 lines past the 1,500-line ceiling `check_source_size_budget`
|
|
5
|
+
* enforces as a shrink-only ratchet, so every line there costs one unit of excess
|
|
6
|
+
* and the gate names re-pinning the baseline a defect rather than a fix. Adding a
|
|
7
|
+
* `skills_roots` field to this handler's answer cost four; extracting the handler
|
|
8
|
+
* pays that back many times over and puts it in a file where its prose is free.
|
|
9
|
+
*
|
|
10
|
+
* Cohesive on its own terms too: this is the one handler in the allowlist that
|
|
11
|
+
* owns a ranking policy rather than reading a file, and it is the only consumer
|
|
12
|
+
* of the skill-catalogue resolver on the server side.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* `suggest_skill_for_task` — the recovery path for a truncated catalogue.
|
|
17
|
+
*
|
|
18
|
+
* Promoted from a discovery stub because the defect it answers is measured, not
|
|
19
|
+
* hypothetical: on the one host that publishes its own truncation, a default
|
|
20
|
+
* install had 402 entries dropped from the model-visible skills list — the
|
|
21
|
+
* host's own count against its own denominator, not a subtraction over ours.
|
|
22
|
+
* An agent
|
|
23
|
+
* that cannot see a skill also cannot ask for it by name, so the only reachable
|
|
24
|
+
* question is "what fits this task" — which is what the deterministic ranker
|
|
25
|
+
* answers, over the TREE rather than over the host's catalogue.
|
|
26
|
+
*
|
|
27
|
+
* Read-only and shell-free by construction: it reads SKILL.md frontmatter and
|
|
28
|
+
* returns names, scores and declared personas. No skill BODY is returned — the
|
|
29
|
+
* caller opens what it picked, so a wrong rank costs one read rather than a
|
|
30
|
+
* context payload.
|
|
31
|
+
*/
|
|
32
|
+
export async function suggestSkillForTask(
|
|
33
|
+
args: Record<string, unknown>,
|
|
34
|
+
consumerRoot: string,
|
|
35
|
+
): Promise<Record<string, unknown>> {
|
|
36
|
+
const task = typeof args['task'] === 'string' ? args['task'] : '';
|
|
37
|
+
if (task.trim() === '') {
|
|
38
|
+
return { status: 'error', error: 'task must be a non-empty string', suggestions: [] };
|
|
39
|
+
}
|
|
40
|
+
// `limit`, not `top` — the stub published `limit` and a consumer may have
|
|
41
|
+
// read that contract already. Renaming it on promotion would break a
|
|
42
|
+
// caller for cosmetic reasons.
|
|
43
|
+
const limitRaw = args['limit'];
|
|
44
|
+
const limit =
|
|
45
|
+
typeof limitRaw === 'number' && Number.isFinite(limitRaw) ? Math.floor(limitRaw) : 5;
|
|
46
|
+
|
|
47
|
+
const catalogue = await import('../_lib/skill_catalogue.js');
|
|
48
|
+
// EVERY readable root — see `resolveSkillCatalogueRoots` on why not the first.
|
|
49
|
+
const roots = catalogue.resolveSkillCatalogueRoots(consumerRoot);
|
|
50
|
+
if (roots.length === 0) {
|
|
51
|
+
// A missing catalogue is UNKNOWN, never "no skill fits this task". The
|
|
52
|
+
// second reads as a ranked answer and would be a confident empty list —
|
|
53
|
+
// the same zero-from-silence failure `capture_skill_catalogue` refuses.
|
|
54
|
+
return {
|
|
55
|
+
status: 'no_catalogue',
|
|
56
|
+
searched: [...catalogue.DEFAULT_CATALOGUE_ROOTS],
|
|
57
|
+
consumer_root: consumerRoot,
|
|
58
|
+
suggestions: [],
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const { rank } = await import('../skill_tools/score_skill_relevance.js');
|
|
63
|
+
const rows = rank(task, roots).slice(0, Math.max(1, limit));
|
|
64
|
+
return {
|
|
65
|
+
status: 'ok',
|
|
66
|
+
// `skills_root` kept verbatim (a consumer may read it); `skills_roots` is
|
|
67
|
+
// the additive truth about what was actually ranked.
|
|
68
|
+
skills_root: roots[0] as string,
|
|
69
|
+
skills_roots: roots,
|
|
70
|
+
suggestions: rows.map(([name, score, personas]) => ({ skill: name, score, personas })),
|
|
71
|
+
};
|
|
72
|
+
}
|
|
@@ -47,6 +47,7 @@ import {
|
|
|
47
47
|
slice_around as _chSliceAround,
|
|
48
48
|
read_header as _chReadHeaderImpl,
|
|
49
49
|
} from '../chat_history.js';
|
|
50
|
+
import { suggestSkillForTask } from './suggest_skill.js';
|
|
50
51
|
import {
|
|
51
52
|
type CatalogEntry,
|
|
52
53
|
install_hint as _catalog_install_hint,
|
|
@@ -1210,61 +1211,6 @@ async function _readResourceBodyHandler(
|
|
|
1210
1211
|
// unlisted tool. Boot-time stderr log enumerates the registered set.
|
|
1211
1212
|
// ---------------------------------------------------------------------
|
|
1212
1213
|
|
|
1213
|
-
/**
|
|
1214
|
-
* `suggest_skill_for_task` — the recovery path for a truncated catalogue.
|
|
1215
|
-
*
|
|
1216
|
-
* Promoted from a discovery stub because the defect it answers is measured, not
|
|
1217
|
-
* hypothetical: on the one host that publishes its own truncation, a default
|
|
1218
|
-
* install had 402 entries dropped from the model-visible skills list — the
|
|
1219
|
-
* host's own count against its own denominator, not a subtraction over ours
|
|
1220
|
-
* (`agents/evidence/analysis/scoped-projection-host-delivery.md`). An agent
|
|
1221
|
-
* that cannot see a skill also cannot ask for it by name, so the only reachable
|
|
1222
|
-
* question is "what fits this task" — which is what the deterministic ranker
|
|
1223
|
-
* answers, over the TREE rather than over the host's catalogue.
|
|
1224
|
-
*
|
|
1225
|
-
* Read-only and shell-free by construction: it reads SKILL.md frontmatter and
|
|
1226
|
-
* returns names, scores and declared personas. No skill BODY is returned — the
|
|
1227
|
-
* caller opens what it picked, so a wrong rank costs one read rather than a
|
|
1228
|
-
* context payload.
|
|
1229
|
-
*/
|
|
1230
|
-
async function _suggestSkillForTaskHandler(
|
|
1231
|
-
args: Record<string, unknown>,
|
|
1232
|
-
consumerRoot: string,
|
|
1233
|
-
): Promise<Record<string, unknown>> {
|
|
1234
|
-
const task = typeof args['task'] === 'string' ? args['task'] : '';
|
|
1235
|
-
if (task.trim() === '') {
|
|
1236
|
-
return { status: 'error', error: 'task must be a non-empty string', suggestions: [] };
|
|
1237
|
-
}
|
|
1238
|
-
// `limit`, not `top` — the stub published `limit` and a consumer may have
|
|
1239
|
-
// read that contract already. Renaming it on promotion would break a
|
|
1240
|
-
// caller for cosmetic reasons.
|
|
1241
|
-
const limitRaw = args['limit'];
|
|
1242
|
-
const limit =
|
|
1243
|
-
typeof limitRaw === 'number' && Number.isFinite(limitRaw) ? Math.floor(limitRaw) : 5;
|
|
1244
|
-
|
|
1245
|
-
const catalogue = await import('../_lib/skill_catalogue.js');
|
|
1246
|
-
const skillsDir = catalogue.resolveSkillsRoot(consumerRoot);
|
|
1247
|
-
if (skillsDir === null) {
|
|
1248
|
-
// A missing catalogue is UNKNOWN, never "no skill fits this task". The
|
|
1249
|
-
// second reads as a ranked answer and would be a confident empty list —
|
|
1250
|
-
// the same zero-from-silence failure `capture_skill_catalogue` refuses.
|
|
1251
|
-
return {
|
|
1252
|
-
status: 'no_catalogue',
|
|
1253
|
-
searched: [...catalogue.DEFAULT_CATALOGUE_ROOTS],
|
|
1254
|
-
consumer_root: consumerRoot,
|
|
1255
|
-
suggestions: [],
|
|
1256
|
-
};
|
|
1257
|
-
}
|
|
1258
|
-
|
|
1259
|
-
const { rank } = await import('../skill_tools/score_skill_relevance.js');
|
|
1260
|
-
const rows = rank(task, skillsDir).slice(0, Math.max(1, limit));
|
|
1261
|
-
return {
|
|
1262
|
-
status: 'ok',
|
|
1263
|
-
skills_root: skillsDir,
|
|
1264
|
-
suggestions: rows.map(([name, score, personas]) => ({ skill: name, score, personas })),
|
|
1265
|
-
};
|
|
1266
|
-
}
|
|
1267
|
-
|
|
1268
1214
|
export const ALLOWLIST: Record<string, BuiltinTool> = {
|
|
1269
1215
|
suggest_skill_for_task: {
|
|
1270
1216
|
name: 'suggest_skill_for_task',
|
|
@@ -1296,7 +1242,7 @@ export const ALLOWLIST: Record<string, BuiltinTool> = {
|
|
|
1296
1242
|
},
|
|
1297
1243
|
additionalProperties: false,
|
|
1298
1244
|
},
|
|
1299
|
-
handler:
|
|
1245
|
+
handler: suggestSkillForTask,
|
|
1300
1246
|
},
|
|
1301
1247
|
lint_skills: {
|
|
1302
1248
|
name: 'lint_skills',
|
|
@@ -355,8 +355,13 @@ the explicit `dry-run` subcommand:
|
|
|
355
355
|
`scripts/media/lib/fixtures/<adapter-id>/`.
|
|
356
356
|
- Exit code 0.
|
|
357
357
|
|
|
358
|
-
Fixtures are committed and cover one happy path per adapter
|
|
359
|
-
|
|
358
|
+
Fixtures are committed and cover one happy path per adapter;
|
|
359
|
+
committed media assets are **non-decodable ASCII placeholders**
|
|
360
|
+
whose documented paths and presence, rather than encoded-media
|
|
361
|
+
validity, form the fixture contract. The placeholder literal is
|
|
362
|
+
`FIXTURE-<adapter-id>-<ext>`. A test that needs decodable media
|
|
363
|
+
generates it at runtime — it must not read a fixture expecting
|
|
364
|
+
one. Phase 6 golden runs assert byte-identical stdout under
|
|
360
365
|
`AIV_DRYRUN=true`.
|
|
361
366
|
|
|
362
367
|
## Logging & redaction
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -73,7 +73,7 @@ export const ELIGIBLE: Record<string, [string, string]> = {
|
|
|
73
73
|
'audit my agent docs, check the state of the agents/ directory',
|
|
74
74
|
'stale files under agents/ or recent edits to .augment/ without doc updates',
|
|
75
75
|
],
|
|
76
|
-
'analyze-
|
|
76
|
+
'analyze-repo': [
|
|
77
77
|
"look at how X does this, compare with that other repo, study this competitor's approach",
|
|
78
78
|
'external repo URL or path mentioned in the prompt',
|
|
79
79
|
],
|
|
@@ -47,18 +47,40 @@
|
|
|
47
47
|
* Both edges are printed. A reader who cannot see the bracket cannot judge the
|
|
48
48
|
* number.
|
|
49
49
|
*
|
|
50
|
+
* The `form` dimension (road-to-asked-not-parked 1.1)
|
|
51
|
+
*
|
|
52
|
+
* Every unblocked ask is additionally partitioned by the FORM the ask took:
|
|
53
|
+
*
|
|
54
|
+
* - `native` — the same assistant turn carries a tool call whose name is a
|
|
55
|
+
* structured-ask tool (`_lib/structured_ask.ts`, `isStructuredAskTool`).
|
|
56
|
+
* - `text` — everything else, i.e. the ask was prose.
|
|
57
|
+
*
|
|
58
|
+
* The two are exhaustive and disjoint by construction, so `text + native`
|
|
59
|
+
* always equals `unblockedAsks`. That identity is asserted in `--self-test`
|
|
60
|
+
* rather than left to a reader to check.
|
|
61
|
+
*
|
|
62
|
+
* Expect `native` to be **0** on every corpus this repo can scan today: no host
|
|
63
|
+
* in the capability registry carries an observed structured-ask tool, and
|
|
64
|
+
* `structured_ask.ts` states why matching on a name-shape pattern is not a claim
|
|
65
|
+
* that any host ships one. A measured zero and an uninstrumented zero look the
|
|
66
|
+
* same in a report and are not the same fact — this axis is what makes them
|
|
67
|
+
* distinguishable later.
|
|
68
|
+
*
|
|
50
69
|
* Usage:
|
|
51
70
|
* ./scripts-run src/scripts/probe_unblocked_ask [--limit N] [--store PATH] [--json]
|
|
52
71
|
* ./scripts-run src/scripts/probe_unblocked_ask --self-test
|
|
72
|
+
* ./scripts-run src/scripts/probe_unblocked_ask --help
|
|
53
73
|
*
|
|
54
74
|
* Exit codes: 0 always — a measurement, not a gate. `--self-test` exits 1 when
|
|
55
75
|
* the detector fails to fire on its fixtures.
|
|
56
76
|
*/
|
|
57
77
|
import fs from 'node:fs';
|
|
78
|
+
import os from 'node:os';
|
|
58
79
|
import path from 'node:path';
|
|
59
80
|
import { pathToFileURL } from 'node:url';
|
|
60
81
|
|
|
61
82
|
import { defaultStore, HARNESS_TEXT, isInjectedBody } from './conformance_scan.js';
|
|
83
|
+
import { isStructuredAskTool } from './_lib/structured_ask.js';
|
|
62
84
|
import { isSyntheticPrompt } from './_lib/prompt_shape.js';
|
|
63
85
|
|
|
64
86
|
/** A numbered-options block — the surface `check_reply_consistency` already reads. */
|
|
@@ -88,6 +110,8 @@ interface Turn {
|
|
|
88
110
|
role: 'user' | 'assistant';
|
|
89
111
|
text: string;
|
|
90
112
|
at: string;
|
|
113
|
+
/** Tool names invoked in this turn — empty for a user turn. */
|
|
114
|
+
tools: string[];
|
|
91
115
|
}
|
|
92
116
|
|
|
93
117
|
function turns(lines: string[]): Turn[] {
|
|
@@ -116,10 +140,16 @@ function turns(lines: string[]): Turn[] {
|
|
|
116
140
|
.map((b: { text?: string }) => b.text ?? '')
|
|
117
141
|
.join('\n')
|
|
118
142
|
: '';
|
|
143
|
+
const tools = Array.isArray(c)
|
|
144
|
+
? c
|
|
145
|
+
.filter((b: { type?: string }) => b?.type === 'tool_use')
|
|
146
|
+
.map((b: { name?: string }) => String(b?.name ?? ''))
|
|
147
|
+
.filter((n: string) => n !== '')
|
|
148
|
+
: [];
|
|
119
149
|
if (!text.trim()) continue;
|
|
120
150
|
if (role === 'assistant' && HARNESS_TEXT.test(text.trim())) continue;
|
|
121
151
|
if (role === 'user' && (isSyntheticPrompt(text) || isInjectedBody(text))) continue;
|
|
122
|
-
out.push({ role, text, at: String(e['timestamp'] ?? '') });
|
|
152
|
+
out.push({ role, text, at: String(e['timestamp'] ?? ''), tools });
|
|
123
153
|
}
|
|
124
154
|
return out;
|
|
125
155
|
}
|
|
@@ -139,16 +169,39 @@ export function classifyTail(tail: string): AskVerdict {
|
|
|
139
169
|
return { unblockedAsk: true, malformed: !RECOMMENDATION.test(tail) };
|
|
140
170
|
}
|
|
141
171
|
|
|
172
|
+
/** The ask FORM partition. Exhaustive and disjoint: `text + native === unblockedAsks`. */
|
|
173
|
+
export interface FormBreakdown {
|
|
174
|
+
text: number;
|
|
175
|
+
native: number;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* Which FORM an ask took. `native` iff the turn carried a structured-ask tool
|
|
180
|
+
* call; `text` otherwise. Exported so the partition is one function rather than
|
|
181
|
+
* an inline condition a later edit can quietly make non-exhaustive.
|
|
182
|
+
*/
|
|
183
|
+
export function askForm(tools: readonly string[]): 'text' | 'native' {
|
|
184
|
+
return tools.some((n) => isStructuredAskTool(n)) ? 'native' : 'text';
|
|
185
|
+
}
|
|
186
|
+
|
|
142
187
|
export interface Result {
|
|
143
188
|
handbacks: number;
|
|
144
189
|
withBlock: number;
|
|
145
190
|
unblockedAsks: number;
|
|
146
191
|
malformed: number;
|
|
192
|
+
form: FormBreakdown;
|
|
147
193
|
samples: { session: string; at: string; span: string }[];
|
|
148
194
|
}
|
|
149
195
|
|
|
150
196
|
export function measure(store: string, limit: number): Result {
|
|
151
|
-
const r: Result = {
|
|
197
|
+
const r: Result = {
|
|
198
|
+
handbacks: 0,
|
|
199
|
+
withBlock: 0,
|
|
200
|
+
unblockedAsks: 0,
|
|
201
|
+
malformed: 0,
|
|
202
|
+
form: { text: 0, native: 0 },
|
|
203
|
+
samples: [],
|
|
204
|
+
};
|
|
152
205
|
let files: string[];
|
|
153
206
|
try {
|
|
154
207
|
files = fs.readdirSync(store).filter((f) => f.endsWith('.jsonl'));
|
|
@@ -179,6 +232,7 @@ export function measure(store: string, limit: number): Result {
|
|
|
179
232
|
const v = classifyTail(tail);
|
|
180
233
|
if (!v.unblockedAsk) continue;
|
|
181
234
|
r.unblockedAsks += 1;
|
|
235
|
+
r.form[askForm(t.tools)] += 1;
|
|
182
236
|
if (v.malformed) {
|
|
183
237
|
r.malformed += 1;
|
|
184
238
|
if (r.samples.length < 5) {
|
|
@@ -221,6 +275,52 @@ export function selfTest(): number {
|
|
|
221
275
|
);
|
|
222
276
|
if (!ok) failed += 1;
|
|
223
277
|
}
|
|
278
|
+
for (const [tools, want] of [
|
|
279
|
+
[[], 'text'],
|
|
280
|
+
[['Bash', 'Read'], 'text'],
|
|
281
|
+
[['AskUserQuestion'], 'native'],
|
|
282
|
+
[['ask_user_question'], 'native'],
|
|
283
|
+
] as Array<[string[], 'text' | 'native']>) {
|
|
284
|
+
const got = askForm(tools);
|
|
285
|
+
const ok = got === want;
|
|
286
|
+
process.stdout.write(
|
|
287
|
+
`${ok ? '✅' : '❌'} form of [${tools.join(', ')}] — ${got}` +
|
|
288
|
+
(ok ? '\n' : ` (wanted ${want})\n`),
|
|
289
|
+
);
|
|
290
|
+
if (!ok) failed += 1;
|
|
291
|
+
}
|
|
292
|
+
const fx = fs.mkdtempSync(path.join(os.tmpdir(), 'probe-ask-form-'));
|
|
293
|
+
try {
|
|
294
|
+
const mk = (role: string, blocks: unknown): string =>
|
|
295
|
+
JSON.stringify({ type: role, timestamp: '2026-01-01T00:00:00Z', message: { role, content: blocks } });
|
|
296
|
+
fs.writeFileSync(
|
|
297
|
+
path.join(fx, 'fixture.jsonl'),
|
|
298
|
+
[
|
|
299
|
+
mk('assistant', [{ type: 'text', text: 'Done.\n\nSoll ich das so umsetzen?' }]),
|
|
300
|
+
mk('user', 'ja'),
|
|
301
|
+
mk('assistant', [
|
|
302
|
+
{ type: 'text', text: 'Done.\n\nShall I take the second half too?' },
|
|
303
|
+
{ type: 'tool_use', name: 'AskUserQuestion' },
|
|
304
|
+
]),
|
|
305
|
+
mk('user', 'ja'),
|
|
306
|
+
].join('\n'),
|
|
307
|
+
'utf8',
|
|
308
|
+
);
|
|
309
|
+
const r = measure(fx, 10);
|
|
310
|
+
const sums = r.form.text + r.form.native === r.unblockedAsks;
|
|
311
|
+
process.stdout.write(
|
|
312
|
+
`${sums ? '✅' : '❌'} form partition is exhaustive — text ${String(r.form.text)} + ` +
|
|
313
|
+
`native ${String(r.form.native)} = unblocked ${String(r.unblockedAsks)}\n`,
|
|
314
|
+
);
|
|
315
|
+
if (!sums) failed += 1;
|
|
316
|
+
const split = r.form.text === 1 && r.form.native === 1;
|
|
317
|
+
process.stdout.write(
|
|
318
|
+
`${split ? '✅' : '❌'} the fixture's two asks split one per class\n`,
|
|
319
|
+
);
|
|
320
|
+
if (!split) failed += 1;
|
|
321
|
+
} finally {
|
|
322
|
+
fs.rmSync(fx, { recursive: true, force: true });
|
|
323
|
+
}
|
|
224
324
|
const positives = cases.filter((c) => c[3]).length;
|
|
225
325
|
if (positives < 3) {
|
|
226
326
|
process.stderr.write('❌ probe_unblocked_ask --self-test: fewer than 3 positive cases — a truncated suite must fail.\n');
|
|
@@ -233,6 +333,32 @@ export function selfTest(): number {
|
|
|
233
333
|
return failed > 0 ? 1 : 0;
|
|
234
334
|
}
|
|
235
335
|
|
|
336
|
+
const HELP = `probe_unblocked_ask — the unblocked-ask rate, and the FORM each ask took.
|
|
337
|
+
|
|
338
|
+
Usage:
|
|
339
|
+
probe_unblocked_ask [--limit N] [--store PATH] [--json]
|
|
340
|
+
probe_unblocked_ask --self-test
|
|
341
|
+
probe_unblocked_ask --help
|
|
342
|
+
|
|
343
|
+
Dimensions reported:
|
|
344
|
+
hand-back turns assistant turns immediately followed by a user turn
|
|
345
|
+
unblocked asks …that hand a decision with no numbered-options block
|
|
346
|
+
malformed …of those, carrying no Recommendation:/Empfehlung: label
|
|
347
|
+
form the ask FORM partition of the unblocked asks:
|
|
348
|
+
native the turn carried a structured-ask tool call
|
|
349
|
+
text the ask was prose
|
|
350
|
+
text + native always equals the unblocked-ask total.
|
|
351
|
+
native is 0 wherever no host has an observed structured-ask
|
|
352
|
+
tool — a measured zero, not an uninstrumented one.
|
|
353
|
+
|
|
354
|
+
Options:
|
|
355
|
+
--limit N most recent N sessions (default 60)
|
|
356
|
+
--store PATH transcript store (default: the host's own)
|
|
357
|
+
--json emit the Result object instead of the report
|
|
358
|
+
--self-test run the detector fixtures, incl. the form-partition identity
|
|
359
|
+
--help this text
|
|
360
|
+
`;
|
|
361
|
+
|
|
236
362
|
export function main(argv: string[]): number {
|
|
237
363
|
let limit = 60;
|
|
238
364
|
let storeArg: string | null = null;
|
|
@@ -246,6 +372,10 @@ export function main(argv: string[]): number {
|
|
|
246
372
|
// in-process, and resolving a transcript store that may not exist here
|
|
247
373
|
// would make the sensitivity proof depend on the host having one.
|
|
248
374
|
else if (a === '--self-test') return selfTest();
|
|
375
|
+
else if (a === '--help' || a === '-h') {
|
|
376
|
+
process.stdout.write(HELP);
|
|
377
|
+
return 0;
|
|
378
|
+
}
|
|
249
379
|
else {
|
|
250
380
|
process.stderr.write(`probe_unblocked_ask: unrecognized argument: ${a}\n`);
|
|
251
381
|
return 0;
|
|
@@ -264,6 +394,9 @@ export function main(argv: string[]): number {
|
|
|
264
394
|
` …with a numbered block ${String(r.withBlock)} (excluded — check_reply_consistency reads that surface)\n` +
|
|
265
395
|
` …unblocked asks ${String(r.unblockedAsks)} ${pct(r.unblockedAsks, r.handbacks)} of hand-backs\n` +
|
|
266
396
|
` …of those, malformed ${String(r.malformed)} ${pct(r.malformed, r.unblockedAsks)} of unblocked asks\n` +
|
|
397
|
+
` form: text ${String(r.form.text)} ${pct(r.form.text, r.unblockedAsks)} of unblocked asks\n` +
|
|
398
|
+
` form: native ${String(r.form.native)} ${pct(r.form.native, r.unblockedAsks)} of unblocked asks\n` +
|
|
399
|
+
` form: text + native ${String(r.form.text + r.form.native)} (must equal unblocked asks: ${String(r.unblockedAsks)})\n` +
|
|
267
400
|
'\n CEILING, not a point estimate: "hands a decision" is a heuristic and a\n' +
|
|
268
401
|
' rhetorical question can match it. A recommendation is matched on its LABEL,\n' +
|
|
269
402
|
' which is the rule\'s own bar but a bar on form.\n',
|