@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
|
@@ -300,95 +300,45 @@ function _first_diff(a: string, b: string): string {
|
|
|
300
300
|
* a branch, a PR and a CI run — the exact cost `guard_release_branch_push` was
|
|
301
301
|
* added to avoid for the sibling obligation two releases earlier.
|
|
302
302
|
*
|
|
303
|
-
* This module has no imports on purpose (see `CURATED_HEAD_INSTRUCTION`)
|
|
304
|
-
*
|
|
305
|
-
*
|
|
303
|
+
* This module has no imports on purpose (see `CURATED_HEAD_INSTRUCTION`).
|
|
304
|
+
*
|
|
305
|
+
* **One reader left, as of ADR-261 (2026-09-07).** This paragraph used to say
|
|
306
|
+
* the definition was shared by "the writer, the local push guard and the CI
|
|
307
|
+
* gate", which was true while `mix_response_blockers` and
|
|
308
|
+
* `check_governance_mix_response` scanned a section for this marker. Both are
|
|
309
|
+
* deleted, and with them every search for it: the only consumer now is
|
|
310
|
+
* `render_mix_response` in this same file, which WRITES the line. Nothing reads
|
|
311
|
+
* a changelog looking for it, so do not assume a guard notices when it is
|
|
312
|
+
* missing — none does.
|
|
306
313
|
*/
|
|
307
314
|
export const MIX_RESPONSE_MARKER = '**Governance mix:**';
|
|
308
315
|
|
|
309
316
|
/**
|
|
310
|
-
*
|
|
311
|
-
*
|
|
312
|
-
* A generator that emits the measured level alone would satisfy the gate's
|
|
313
|
-
* length floor (`MIX_RESPONSE_MIN_CHARS`, 40) with a number the machine
|
|
314
|
-
* produced, turning a written-answer obligation into a formality the tool
|
|
315
|
-
* discharges for itself. That is a smuggled auto-approval, not a fix.
|
|
317
|
+
* Render the measured governance-versus-product line the writer emits.
|
|
316
318
|
*
|
|
317
|
-
*
|
|
318
|
-
*
|
|
319
|
-
* `DERIVED_MARKER` already follow. The net effect is only that the obligation
|
|
320
|
-
* moves from CI to the moment the section is written, where the fix is an edit
|
|
321
|
-
* to a file already open in the working tree.
|
|
322
|
-
*/
|
|
323
|
-
export const MIX_RESPONSE_PLACEHOLDER = '<the consumer work>';
|
|
324
|
-
|
|
325
|
-
/**
|
|
326
|
-
* Every placeholder token the writer emits, so none of them can ship.
|
|
319
|
+
* ONE line, and machine-written end to end. It reports what the span measured
|
|
320
|
+
* and asks the releaser for nothing.
|
|
327
321
|
*
|
|
328
|
-
* The
|
|
329
|
-
*
|
|
330
|
-
*
|
|
331
|
-
*
|
|
322
|
+
* The written-answer obligation is gone (ADR-261). This used to emit a second line — `Next cycle ships <the consumer work>,
|
|
323
|
+
* tracked in <roadmap or issue>.` — a placeholder no generator was allowed to
|
|
324
|
+
* fill, backed by four refusals, an interactive prompt, a staging channel under
|
|
325
|
+
* `## [Unreleased]` and a read-back obligation on the following release. The
|
|
326
|
+
* releaser had to type prose into a running pipeline to get a version out.
|
|
332
327
|
*
|
|
333
|
-
*
|
|
334
|
-
*
|
|
335
|
-
*
|
|
336
|
-
*
|
|
337
|
-
*/
|
|
338
|
-
export const MIX_RESPONSE_PLACEHOLDERS: readonly string[] = [
|
|
339
|
-
MIX_RESPONSE_PLACEHOLDER,
|
|
340
|
-
'<roadmap or issue>',
|
|
341
|
-
];
|
|
342
|
-
|
|
343
|
-
/**
|
|
344
|
-
* Render the response block the writer emits when the obligation triggers.
|
|
328
|
+
* The owner ended it: this package exists to make the maintainer's work
|
|
329
|
+
* cheaper, and a release gate that stops a green pipeline until a human writes
|
|
330
|
+
* a sentence about a FUTURE cycle is the opposite. The measurement stays
|
|
331
|
+
* because it is free and true; the homework does not.
|
|
345
332
|
*
|
|
346
|
-
*
|
|
347
|
-
* `
|
|
348
|
-
*
|
|
349
|
-
*
|
|
333
|
+
* The removed placeholder constants (`MIX_RESPONSE_PLACEHOLDER(S)`) and promise
|
|
334
|
+
* sentinels (`PROMISE_PHRASE`, `PROMISE_READBACK_MARKER`, `PROMISE_OUTCOMES`)
|
|
335
|
+
* are deleted rather than deprecated — a sentinel nothing writes and nothing
|
|
336
|
+
* refuses is dead weight that reads as live policy.
|
|
350
337
|
*/
|
|
351
338
|
export function render_mix_response(level: string): string[] {
|
|
352
|
-
return [
|
|
353
|
-
`> ${MIX_RESPONSE_MARKER} ${level}.`,
|
|
354
|
-
`> ${PROMISE_PHRASE} ${MIX_RESPONSE_PLACEHOLDER}, tracked in <roadmap or issue>.`,
|
|
355
|
-
];
|
|
339
|
+
return [`> ${MIX_RESPONSE_MARKER} ${level}.`];
|
|
356
340
|
}
|
|
357
341
|
|
|
358
|
-
/**
|
|
359
|
-
* The words that turn a governance response into a PROMISE about a later cycle.
|
|
360
|
-
*
|
|
361
|
-
* Named rather than inlined because two sides now read it: the writer emits it,
|
|
362
|
-
* and the next release reads the previous section for it to decide whether a
|
|
363
|
-
* read-back is owed. A promise nothing reads back costs nothing to restate, so
|
|
364
|
-
* it can be restated for as many cycles as there are cycles — the defect this
|
|
365
|
-
* constant exists to make addressable.
|
|
366
|
-
*/
|
|
367
|
-
export const PROMISE_PHRASE = 'Next cycle ships';
|
|
368
|
-
|
|
369
|
-
/**
|
|
370
|
-
* The marker under which a release head answers the PREVIOUS head's promise.
|
|
371
|
-
*
|
|
372
|
-
* A named sentinel, on the same discipline as `MIX_RESPONSE_MARKER` and
|
|
373
|
-
* `DERIVED_MARKER`: a shape match over prose would accept a sentence that
|
|
374
|
-
* happens to mention last cycle and reject a reworded answer.
|
|
375
|
-
*/
|
|
376
|
-
export const PROMISE_READBACK_MARKER = '**Previous cycle:**';
|
|
377
|
-
|
|
378
|
-
/**
|
|
379
|
-
* The three outcomes a read-back may report, and the reason there are exactly
|
|
380
|
-
* three.
|
|
381
|
-
*
|
|
382
|
-
* `shipped` and `did not ship` are the honest halves of the fact. `withdrawn`
|
|
383
|
-
* exists because a promise the project decided not to keep is a legitimate
|
|
384
|
-
* third state — but only WITH a reason, which is why the answer also has to
|
|
385
|
-
* clear a written-length floor rather than being one of these three words on
|
|
386
|
-
* its own. Without the third state the mechanism would push an author toward
|
|
387
|
-
* restating a dead promise as still-outstanding, which is the failure it is
|
|
388
|
-
* built to stop.
|
|
389
|
-
*/
|
|
390
|
-
export const PROMISE_OUTCOMES: readonly string[] = ['shipped', 'did not ship', 'withdrawn'];
|
|
391
|
-
|
|
392
342
|
/** Every `## X.Y.Z` release version in the file, in document order (newest first). */
|
|
393
343
|
export function changelog_versions(text: string): string[] {
|
|
394
344
|
const out: string[] = [];
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Roadmap `status:` values this repository has RETIRED, and the diagnostic a
|
|
3
|
+
* reader gets when one survives in a file.
|
|
4
|
+
*
|
|
5
|
+
* `status: carrier` was deleted by ADR-262. It had two jobs welded together: a
|
|
6
|
+
* structural one (this roadmap is the live destination of an archived parent's
|
|
7
|
+
* `[~]` carries) and an authority one (an autonomous run may not act on it
|
|
8
|
+
* until a human flips it to `ready`). The second was authored by the repository
|
|
9
|
+
* about its own maintainer, was enforced by nothing, and contradicted the drain
|
|
10
|
+
* command's own FORBIDDEN NON-HALT REASONS. The first is a property of the
|
|
11
|
+
* deferral EDGES, which `lint_deferral_integrity` already verifies from both
|
|
12
|
+
* ends, so nothing was left for a node-level status to carry.
|
|
13
|
+
*
|
|
14
|
+
* Deleting a vocabulary term silently is the failure this module exists to
|
|
15
|
+
* prevent: an old file would keep a value nothing reads, and the next author
|
|
16
|
+
* would infer a meaning from its presence. So a retired value is REJECTED with
|
|
17
|
+
* a migration diagnostic rather than ignored.
|
|
18
|
+
*
|
|
19
|
+
* The read is frontmatter-scoped on purpose, and that is inherited rather than
|
|
20
|
+
* newly decided: a fenced `status: carrier` inside a roadmap documenting the
|
|
21
|
+
* syntax is not a declaration, and a body-wide match turned exactly that into a
|
|
22
|
+
* hard failure before a review caught it.
|
|
23
|
+
*/
|
|
24
|
+
|
|
25
|
+
/** Retired `status:` values, mapped to the record that retired each one. */
|
|
26
|
+
export const RETIRED_STATUS_VALUES: ReadonlyMap<string, string> = new Map([
|
|
27
|
+
['carrier', 'ADR-262'],
|
|
28
|
+
]);
|
|
29
|
+
|
|
30
|
+
/** The leading `---\n…\n---` block, or null when the text carries none. */
|
|
31
|
+
export function frontmatterBlock(text: string): string | null {
|
|
32
|
+
const m = /^---\n([\s\S]*?)\n---/.exec(text);
|
|
33
|
+
return m === null ? null : (m[1] ?? '');
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/** The frontmatter `status:` value, lower-cased, or null when absent. */
|
|
37
|
+
export function frontmatterStatus(text: string | null | undefined): string | null {
|
|
38
|
+
if (text === null || text === undefined) {
|
|
39
|
+
return null;
|
|
40
|
+
}
|
|
41
|
+
const fm = frontmatterBlock(text);
|
|
42
|
+
if (fm === null) {
|
|
43
|
+
return null;
|
|
44
|
+
}
|
|
45
|
+
const m = /^status:[ \t]*([A-Za-z-]+)[ \t]*$/m.exec(fm);
|
|
46
|
+
return m === null ? null : (m[1] ?? '').toLowerCase();
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* The retired value this text declares, or null when it declares none.
|
|
51
|
+
*
|
|
52
|
+
* Returns the value rather than a boolean so the caller can name it in the
|
|
53
|
+
* diagnostic — "status: carrier is retired" is actionable, "invalid status" is
|
|
54
|
+
* not.
|
|
55
|
+
*/
|
|
56
|
+
export function declaresRetiredStatus(text: string | null | undefined): string | null {
|
|
57
|
+
const status = frontmatterStatus(text);
|
|
58
|
+
if (status === null) {
|
|
59
|
+
return null;
|
|
60
|
+
}
|
|
61
|
+
return RETIRED_STATUS_VALUES.has(status) ? status : null;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/** The migration diagnostic for a retired value. Names the record and the fix. */
|
|
65
|
+
export function retiredStatusDiagnostic(value: string): string {
|
|
66
|
+
const record = RETIRED_STATUS_VALUES.get(value) ?? 'a decision record';
|
|
67
|
+
return (
|
|
68
|
+
`declares the retired \`status: ${value}\`, deleted by ${record}. ` +
|
|
69
|
+
'A `status:` value no longer decides whether an autonomous run may act on ' +
|
|
70
|
+
'a roadmap. Use `status: ready` and put any genuine human-ACTION gate in a ' +
|
|
71
|
+
'`## Blockers` entry, or park the file under `agents/roadmaps/later/` with ' +
|
|
72
|
+
'an `entry_condition`. Both are read by a gate; a status is not.'
|
|
73
|
+
);
|
|
74
|
+
}
|
|
@@ -75,40 +75,146 @@
|
|
|
75
75
|
*/
|
|
76
76
|
|
|
77
77
|
import * as fs from 'node:fs';
|
|
78
|
+
import * as os from 'node:os';
|
|
78
79
|
import * as path from 'node:path';
|
|
79
80
|
|
|
80
|
-
/**
|
|
81
|
-
|
|
81
|
+
/**
|
|
82
|
+
* Catalogue roots under the WORKSPACE, in precedence order.
|
|
83
|
+
*
|
|
84
|
+
* `src/skills` is the authored tree and exists only in a maintainer checkout.
|
|
85
|
+
* `.claude/skills` is the project-scope projection — a consumer's own skills
|
|
86
|
+
* live there, and so do this repository's flat-command wrappers.
|
|
87
|
+
*/
|
|
88
|
+
export const PROJECT_CATALOGUE_ROOTS = ['src/skills', '.claude/skills'] as const;
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* The catalogue root under the user's HOME — what the host itself delivers.
|
|
92
|
+
*
|
|
93
|
+
* Home-relative rather than workspace-relative, which is why it cannot live in
|
|
94
|
+
* the list above: `~/.claude/skills` is one directory for every project on the
|
|
95
|
+
* machine, and on a consumer machine it is where `agent-config install` puts the
|
|
96
|
+
* shipped skills.
|
|
97
|
+
*/
|
|
98
|
+
export const HOST_CATALOGUE_ROOT = '.claude/skills';
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* The PROJECTION root, on its own, for the consumers that need exactly it.
|
|
102
|
+
*
|
|
103
|
+
* `capture_skill_catalogue` compares what a host DELIVERED against what was
|
|
104
|
+
* PROJECTED, so the authored tree is the wrong answer for it even though the
|
|
105
|
+
* ranker wants the authored tree first. Both questions were sharing
|
|
106
|
+
* `DEFAULT_CATALOGUE_ROOTS` and the 2026-09-07 reorder silently changed the
|
|
107
|
+
* second one — the finding a neutral review returned the same day. One constant
|
|
108
|
+
* for two questions is the defect; this is the second question, named.
|
|
109
|
+
*/
|
|
110
|
+
export const PROJECTION_CATALOGUE_ROOT = '.claude/skills';
|
|
82
111
|
|
|
83
112
|
/**
|
|
84
|
-
*
|
|
113
|
+
* Every root the resolver looks at, as human-readable LABELS.
|
|
114
|
+
*
|
|
115
|
+
* **Labels, never path inputs — this changed on 2026-09-07.**
|
|
116
|
+
*
|
|
117
|
+
* It used to be the resolver's own candidate list and callers joined it onto a
|
|
118
|
+
* root. It now carries a `~/`-prefixed entry, which `path.join(repo, ...)` turns
|
|
119
|
+
* into `<repo>/~/.claude/skills` — a candidate that can never exist. Use
|
|
120
|
+
* {@link resolveSkillCatalogueRoots} to RESOLVE and this list only to REPORT
|
|
121
|
+
* what was searched.
|
|
122
|
+
*
|
|
123
|
+
* For the `searched:` / `tried:` fields of the no-catalogue answers only — a
|
|
124
|
+
* reader needs to know `~` was consulted, and an absolute home path in a
|
|
125
|
+
* machine-readable payload is both noise and a small privacy leak.
|
|
126
|
+
*/
|
|
127
|
+
export const DEFAULT_CATALOGUE_ROOTS = [
|
|
128
|
+
...PROJECT_CATALOGUE_ROOTS,
|
|
129
|
+
`~/${HOST_CATALOGUE_ROOT}`,
|
|
130
|
+
] as const;
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* EVERY readable, non-empty catalogue root, in precedence order.
|
|
134
|
+
*
|
|
135
|
+
* Shared by the `skill-route` concern and the `suggest_skill_for_task` MCP
|
|
136
|
+
* handler on purpose: two resolvers over one catalogue is how a ranker and the
|
|
137
|
+
* tool that exposes it start ranking different trees.
|
|
138
|
+
*
|
|
139
|
+
* **Why a LIST, and not the first hit — owner decision, 2026-09-07.**
|
|
140
|
+
*
|
|
141
|
+
* This returned one root and stopped at the first match, which made the ranker
|
|
142
|
+
* see one tree and the session see another. A consumer project can carry its own
|
|
143
|
+
* skills in `<project>/.claude/skills` while the shipped catalogue sits in
|
|
144
|
+
* `~/.claude/skills`; first-hit-wins ranked the smaller of the two and reported
|
|
145
|
+
* the result as if it were the catalogue. Ranking the UNION ranks what the
|
|
146
|
+
* session can actually reach, which is the only set the answer is about.
|
|
147
|
+
*
|
|
148
|
+
* **The order, and what it is for.**
|
|
85
149
|
*
|
|
86
|
-
*
|
|
87
|
-
*
|
|
88
|
-
* `
|
|
89
|
-
*
|
|
90
|
-
*
|
|
150
|
+
* Precedence applies to a NAME COLLISION, never to which roots are read:
|
|
151
|
+
*
|
|
152
|
+
* 1. `src/skills` — the authored tree. In this repository it is the truth, and
|
|
153
|
+
* a maintainer editing a skill must see their edit ranked, not the
|
|
154
|
+
* projection of the last `generate-tools` run.
|
|
155
|
+
* 2. `<project>/.claude/skills` — project scope beats machine scope, the same
|
|
156
|
+
* direction every other per-project override in this suite takes.
|
|
157
|
+
* 3. `~/${HOST_CATALOGUE_ROOT}` — the host-global catalogue.
|
|
158
|
+
*
|
|
159
|
+
* **The two defects this replaced, both real.**
|
|
160
|
+
*
|
|
161
|
+
* It used to try `.claude/skills` FIRST, "because a CONSUMER install carries the
|
|
162
|
+
* former and only a maintainer checkout carries the latter". The premise was
|
|
163
|
+
* right and the conclusion did not follow: a consumer has no `src/skills`, so
|
|
164
|
+
* reading it first costs one `existsSync`.
|
|
165
|
+
*
|
|
166
|
+
* The ADR-236 amendment of 2026-09-07 turned that order into a live defect. The
|
|
167
|
+
* project layer now withholds every skill `~/.claude/skills` carries, so this
|
|
168
|
+
* repository's `.claude/skills` holds only the ~49 flat-command WRAPPERS —
|
|
169
|
+
* non-empty, so the old order resolved it, and each wrapper carries a `SKILL.md`,
|
|
170
|
+
* so nothing downstream could tell it was not the catalogue. Measured the same
|
|
171
|
+
* day: the `skill-route` hook ranked over 49 command wrappers instead of 299
|
|
172
|
+
* skills. Under the union those wrappers are still ranked, which is correct —
|
|
173
|
+
* they are reachable — but they no longer displace the catalogue.
|
|
174
|
+
*
|
|
175
|
+
* The non-empty guard keeps its own separate job: an EMPTY root read as a match
|
|
176
|
+
* made the ranker report an empty catalogue as an empty RESULT, the silent
|
|
177
|
+
* failure `road-to-inbox-harvest-2026-08-f-skill-selection-evidence` is about.
|
|
178
|
+
* Measured in that run: a worktree that had never run `generate-tools` ranked
|
|
179
|
+
* zero skills and exited 0 on a task that scores 47 against `src/skills`.
|
|
180
|
+
*
|
|
181
|
+
* @param home override for the user's home; production callers omit it.
|
|
91
182
|
*/
|
|
92
|
-
export function
|
|
93
|
-
|
|
94
|
-
|
|
183
|
+
export function resolveSkillCatalogueRoots(workspaceRoot: string, home?: string): string[] {
|
|
184
|
+
const userHome = home ?? process.env['HOME'] ?? os.homedir();
|
|
185
|
+
const candidates = [
|
|
186
|
+
...PROJECT_CATALOGUE_ROOTS.map((c) => path.join(workspaceRoot, c)),
|
|
187
|
+
path.join(userHome, HOST_CATALOGUE_ROOT),
|
|
188
|
+
];
|
|
189
|
+
const roots: string[] = [];
|
|
190
|
+
const seen = new Set<string>();
|
|
191
|
+
for (const abs of candidates) {
|
|
192
|
+
// A workspace that IS the home directory would offer `~/.claude/skills`
|
|
193
|
+
// twice and rank every skill in it twice. Cheap to exclude, and the
|
|
194
|
+
// duplicate would be invisible in the output.
|
|
195
|
+
if (seen.has(abs)) continue;
|
|
196
|
+
seen.add(abs);
|
|
95
197
|
try {
|
|
96
198
|
if (!fs.existsSync(abs) || !fs.statSync(abs).isDirectory()) continue;
|
|
97
|
-
// NON-EMPTY, and that qualifier is the whole fix. `.claude/skills`
|
|
98
|
-
// is a gitignored projection, so in a fresh worktree it EXISTS and
|
|
99
|
-
// holds nothing — and an empty root resolved as a match makes the
|
|
100
|
-
// ranker report an empty catalogue as an empty RESULT, which is the
|
|
101
|
-
// silent failure `road-to-inbox-harvest-2026-08-f-skill-selection-evidence`
|
|
102
|
-
// is about, arriving through a second door. Measured in that run: a
|
|
103
|
-
// worktree that had never run `generate-tools` ranked zero skills
|
|
104
|
-
// and exited 0 on a task that scores 47 against `src/skills`.
|
|
105
199
|
if (fs.readdirSync(abs).length === 0) continue;
|
|
106
|
-
|
|
200
|
+
roots.push(abs);
|
|
107
201
|
} catch {
|
|
108
202
|
// Unreadable candidate is not a match; try the next.
|
|
109
203
|
}
|
|
110
204
|
}
|
|
111
|
-
return
|
|
205
|
+
return roots;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* The highest-precedence root, or `null` when none is readable.
|
|
210
|
+
*
|
|
211
|
+
* Kept for the surfaces that genuinely name ONE directory — a report line, a
|
|
212
|
+
* `skills_root` field. Anything that RANKS should take
|
|
213
|
+
* {@link resolveSkillCatalogueRoots} instead: ranking the first root only is the
|
|
214
|
+
* defect that function exists to remove.
|
|
215
|
+
*/
|
|
216
|
+
export function resolveSkillsRoot(workspaceRoot: string, home?: string): string | null {
|
|
217
|
+
return resolveSkillCatalogueRoots(workspaceRoot, home)[0] ?? null;
|
|
112
218
|
}
|
|
113
219
|
|
|
114
220
|
/** Where observations accumulate. Append-only, one JSON object per line. */
|
|
@@ -223,6 +329,35 @@ function descriptionOf(frontmatter: string): string {
|
|
|
223
329
|
* the order a host listing follows and therefore the order a positional
|
|
224
330
|
* hypothesis is measured against.
|
|
225
331
|
*/
|
|
332
|
+
/**
|
|
333
|
+
* The catalogue as the RANKER sees it — every readable root, first name winning.
|
|
334
|
+
*
|
|
335
|
+
* Added 2026-09-07 after a second neutral review. `capture_skill_catalogue`'s D-4
|
|
336
|
+
* join says it reads "the catalogue the runtime ranker reads", and once the ranker
|
|
337
|
+
* moved to the union a single-root read stopped being that. Worse, the root the
|
|
338
|
+
* capture had been pointed at — `.claude/skills` — is the one the ADR-236
|
|
339
|
+
* amendment EMPTIED of skills: the join then read 49 command wrappers as the
|
|
340
|
+
* catalogue and published `pointableBare: 0` against `unpointableBare: 16`, a
|
|
341
|
+
* clean-looking verdict off the wrong tree, with the empty-guard silent because
|
|
342
|
+
* 49 is not 0.
|
|
343
|
+
*
|
|
344
|
+
* Positions are re-derived over the merged list, because a position is a property
|
|
345
|
+
* of the catalogue the host is shown, not of the root a name came from.
|
|
346
|
+
*/
|
|
347
|
+
export function readCatalogueAcross(roots: readonly string[]): CatalogueEntry[] {
|
|
348
|
+
const seen = new Set<string>();
|
|
349
|
+
const merged: CatalogueEntry[] = [];
|
|
350
|
+
for (const root of roots) {
|
|
351
|
+
for (const entry of readProjectedCatalogue(root)) {
|
|
352
|
+
if (seen.has(entry.name)) continue;
|
|
353
|
+
seen.add(entry.name);
|
|
354
|
+
merged.push(entry);
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
merged.sort((a, b) => (a.name < b.name ? -1 : a.name > b.name ? 1 : 0));
|
|
358
|
+
return merged.map((e, i) => ({ ...e, position: i + 1 }));
|
|
359
|
+
}
|
|
360
|
+
|
|
226
361
|
export function readProjectedCatalogue(root: string): CatalogueEntry[] {
|
|
227
362
|
// Membership is decided by "does <name>/SKILL.md resolve", never by
|
|
228
363
|
// `Dirent.isDirectory()`. The host-facing projection is a tree of SYMLINKS
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The structured-ask surface — one shared definition of "a host's own question
|
|
3
|
+
* tool", read by every consumer that needs it.
|
|
4
|
+
*
|
|
5
|
+
* Three consumers exist and they must not each invent their own answer:
|
|
6
|
+
*
|
|
7
|
+
* - `probe_unblocked_ask.ts` partitions hand-back ask turns into `text` and
|
|
8
|
+
* `native`, where `native` means the turn carried a call to such a tool.
|
|
9
|
+
* - `hooks/one_question_per_ask_hook.ts` denies a call carrying more than one
|
|
10
|
+
* question.
|
|
11
|
+
* - `_lib/host_capability.ts` carries the boolean `structured_ask` capability;
|
|
12
|
+
* the per-host SHAPE lives here rather than in the manifest, because every
|
|
13
|
+
* manifest field is a strict boolean that `asBool` coerces — a nested object
|
|
14
|
+
* put there would normalize silently to `false`.
|
|
15
|
+
*
|
|
16
|
+
* What is observed, and what is a shape guess — the line is drawn explicitly.
|
|
17
|
+
*
|
|
18
|
+
* `STRUCTURED_ASK_SHAPES` is **empty**. No host in this repository's registry
|
|
19
|
+
* carries an observed structured-ask tool, so there is no per-host tool name to
|
|
20
|
+
* match on, and inventing one from a vendor's documentation is exactly what
|
|
21
|
+
* `host-capability-manifest.md` § Observation protocol forbids.
|
|
22
|
+
*
|
|
23
|
+
* `STRUCTURED_ASK_TOOL_NAME_RE` is therefore a **name-shape pattern, not a host
|
|
24
|
+
* fact**. It exists so that if such a tool ever appears in a transcript, the
|
|
25
|
+
* probe MEASURES it rather than missing it — and so the number it reports today
|
|
26
|
+
* (zero) is a measurement rather than an absence of instrumentation. A reader
|
|
27
|
+
* must not read a match as evidence that a named host ships that tool; the only
|
|
28
|
+
* thing that establishes that is an observation written into
|
|
29
|
+
* `STRUCTURED_ASK_SHAPES` with the protocol's four-part citation.
|
|
30
|
+
*
|
|
31
|
+
* Consequence, stated rather than discovered later: `native` is expected to be
|
|
32
|
+
* **0** on every corpus this repo can scan today. That is the honest reading of
|
|
33
|
+
* a capability nobody has observed, not a broken detector.
|
|
34
|
+
*/
|
|
35
|
+
|
|
36
|
+
/** The per-host shape of a structured-ask tool, once one has been OBSERVED. */
|
|
37
|
+
export interface StructuredAskShape {
|
|
38
|
+
/** The tool name as it appears in a transcript's `tool_use` block. */
|
|
39
|
+
readonly tool: string;
|
|
40
|
+
/** How many questions one call may carry. `1` is the shape this repo wants. */
|
|
41
|
+
readonly max_questions: number;
|
|
42
|
+
/** How many options one question may offer. */
|
|
43
|
+
readonly max_options_per_question: number;
|
|
44
|
+
/** Whether the host's picker admits a free-text answer alongside the options. */
|
|
45
|
+
readonly free_text: boolean;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Observed per-host structured-ask shapes.
|
|
50
|
+
*
|
|
51
|
+
* EMPTY, deliberately. A row is written only from an observation in a real
|
|
52
|
+
* session under `contexts/execution/host-capability-manifest.md` § Observation
|
|
53
|
+
* protocol — host · host version · transcript reference · date. Never from a
|
|
54
|
+
* host's documentation, and never by analogy to another host.
|
|
55
|
+
*/
|
|
56
|
+
export const STRUCTURED_ASK_SHAPES: Readonly<Record<string, StructuredAskShape>> = {};
|
|
57
|
+
|
|
58
|
+
/** The observed shape for `hostId`, or `undefined` when none has been observed. */
|
|
59
|
+
export function structuredAskShape(
|
|
60
|
+
hostId: string | null | undefined,
|
|
61
|
+
): StructuredAskShape | undefined {
|
|
62
|
+
if (hostId === null || hostId === undefined) {
|
|
63
|
+
return undefined;
|
|
64
|
+
}
|
|
65
|
+
return STRUCTURED_ASK_SHAPES[hostId];
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Name-SHAPE pattern for a structured-ask tool. See the module header: this is
|
|
70
|
+
* a matcher, not a claim about any host.
|
|
71
|
+
*/
|
|
72
|
+
export const STRUCTURED_ASK_TOOL_NAME_RE = /^(ask[_-]?user[_-]?question|user[_-]?question|ask[_-]?question)s?$/i;
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Is `name` a structured-ask tool call?
|
|
76
|
+
*
|
|
77
|
+
* An observed per-host shape wins outright — that is a fact about the host. The
|
|
78
|
+
* name-shape pattern is the fallback, and it is why the probe can report a
|
|
79
|
+
* measured zero instead of nothing at all.
|
|
80
|
+
*/
|
|
81
|
+
export function isStructuredAskTool(name: string, hostId?: string | null): boolean {
|
|
82
|
+
const shape = structuredAskShape(hostId);
|
|
83
|
+
if (shape !== undefined) {
|
|
84
|
+
return name === shape.tool;
|
|
85
|
+
}
|
|
86
|
+
return STRUCTURED_ASK_TOOL_NAME_RE.test(name);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* How many questions a structured-ask tool payload carries.
|
|
91
|
+
*
|
|
92
|
+
* Returns `-1` when the payload's shape is not recognised at all — a caller
|
|
93
|
+
* that gates on this must treat `-1` as "cannot tell", never as "one". The two
|
|
94
|
+
* recognised shapes are the only ones a picker can plausibly use: an array of
|
|
95
|
+
* question objects under a `questions`-like key, or a single question field.
|
|
96
|
+
*
|
|
97
|
+
* A `questions` array of length 0 is 0, not 1: an empty ask is malformed and a
|
|
98
|
+
* gate should be able to say so.
|
|
99
|
+
*/
|
|
100
|
+
export function countStructuredAskQuestions(input: unknown): number {
|
|
101
|
+
if (input === null || typeof input !== 'object' || Array.isArray(input)) {
|
|
102
|
+
return -1;
|
|
103
|
+
}
|
|
104
|
+
const src = input as Record<string, unknown>;
|
|
105
|
+
for (const key of ['questions', 'question_list', 'items', 'prompts']) {
|
|
106
|
+
const v = src[key];
|
|
107
|
+
if (Array.isArray(v)) {
|
|
108
|
+
return v.length;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
for (const key of ['question', 'prompt', 'header']) {
|
|
112
|
+
if (typeof src[key] === 'string' && (src[key] as string).trim() !== '') {
|
|
113
|
+
return 1;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
return -1;
|
|
117
|
+
}
|