@event4u/agent-config 9.11.0 → 9.13.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/marketplace.json +1 -1
- package/CHANGELOG.md +80 -204
- package/README.md +8 -1
- package/dist/agent-src/commands/fix/ci.md +29 -0
- package/dist/agent-src/commands/pr/create.md +60 -0
- package/dist/agent-src/contexts/authority/kernel-rule-edits.md +18 -2
- package/dist/agent-src/rules/active-remediation.md +0 -2
- package/dist/agent-src/rules/architecture.md +0 -1
- package/dist/agent-src/rules/artifact-drafting-protocol.md +0 -4
- package/dist/agent-src/rules/autonomous-execution.md +1 -2
- package/dist/agent-src/rules/broken-access-control.md +0 -2
- package/dist/agent-src/rules/code-comment-discipline.md +0 -3
- package/dist/agent-src/rules/code-provenance.md +0 -3
- package/dist/agent-src/rules/communication-through-line.md +2 -3
- package/dist/agent-src/rules/content-quoting-floor.md +0 -2
- package/dist/agent-src/rules/context-hygiene.md +1 -3
- package/dist/agent-src/rules/decision-revisit-gate.md +0 -2
- package/dist/agent-src/rules/design-fidelity.md +30 -1
- package/dist/agent-src/rules/domain-adoption-policy.md +0 -3
- package/dist/agent-src/rules/downstream-changes.md +10 -1
- package/dist/agent-src/rules/external-reference-deep-dive.md +0 -7
- package/dist/agent-src/rules/fast-path-marker-visibility.md +0 -1
- package/dist/agent-src/rules/git-history-discipline.md +0 -7
- package/dist/agent-src/rules/guidelines.md +0 -2
- package/dist/agent-src/rules/improve-before-implement.md +4 -2
- package/dist/agent-src/rules/invite-challenge.md +0 -4
- package/dist/agent-src/rules/linked-projects-onboarding-gate.md +0 -2
- package/dist/agent-src/rules/markdown-safe-codeblocks.md +0 -1
- package/dist/agent-src/rules/minimal-safe-diff.md +2 -2
- package/dist/agent-src/rules/missing-tool-handling.md +0 -1
- package/dist/agent-src/rules/no-attribution-footers.md +0 -3
- package/dist/agent-src/rules/no-decorative-emojis-in-git-surfaces.md +8 -5
- package/dist/agent-src/rules/no-pr-progress-comments.md +1 -3
- package/dist/agent-src/rules/no-roadmap-references.md +0 -2
- package/dist/agent-src/rules/output-discipline.md +0 -1
- package/dist/agent-src/rules/prefer-enums-over-literals.md +0 -2
- package/dist/agent-src/rules/preservation-guard.md +0 -3
- package/dist/agent-src/rules/role-mode-adherence.md +0 -1
- package/dist/agent-src/rules/secret-vcs-guard.md +0 -2
- package/dist/agent-src/rules/senior-engineering-discipline.md +0 -2
- package/dist/agent-src/rules/session-canary.md +0 -2
- package/dist/agent-src/rules/size-enforcement.md +0 -5
- package/dist/agent-src/rules/source-confidentiality.md +0 -3
- package/dist/agent-src/rules/source-discovery-gate.md +0 -2
- package/dist/agent-src/rules/think-before-action.md +3 -3
- package/dist/agent-src/rules/token-budget-discipline.md +0 -1
- package/dist/agent-src/rules/token-efficiency.md +1 -2
- package/dist/agent-src/rules/tool-safety.md +0 -1
- package/dist/agent-src/rules/user-interaction.md +0 -3
- package/dist/agent-src/rules/user-interrupt-priority.md +0 -1
- package/dist/agent-src/skills/agent-docs-writing/SKILL.md +8 -0
- package/dist/agent-src/skills/ai-code-blindspots/SKILL.md +1 -0
- package/dist/agent-src/skills/analysis-skill-router/SKILL.md +55 -86
- package/dist/agent-src/skills/character-consistency/SKILL.md +7 -4
- package/dist/agent-src/skills/decision-record/SKILL.md +8 -0
- package/dist/agent-src/skills/design-intelligence/SKILL.md +27 -1
- package/dist/agent-src/skills/design-intelligence/data/font-pairings-reference.csv +74 -74
- package/dist/agent-src/skills/design-intelligence/data/manifest.json +2 -0
- package/dist/agent-src/skills/design-review/SKILL.md +11 -0
- package/dist/agent-src/skills/design-system-capture/reference/design-system-json.md +8 -0
- package/dist/agent-src/skills/design-tokens/scripts/tokens.ts +0 -7
- package/dist/agent-src/skills/existing-ui-audit/SKILL.md +34 -3
- package/dist/agent-src/skills/fe-design/SKILL.md +10 -0
- package/dist/agent-src/skills/motion-choreographer/SKILL.md +7 -4
- package/dist/agent-src/skills/overbuild-review-lens/SKILL.md +201 -0
- package/dist/agent-src/skills/performance/SKILL.md +14 -0
- package/dist/agent-src/skills/pixar-storyteller/SKILL.md +8 -5
- package/dist/agent-src/skills/scene-expander/SKILL.md +9 -6
- package/dist/agent-src/skills/supply-chain-intake/SKILL.md +10 -0
- package/dist/agent-src/skills/typography-system/SKILL.md +55 -11
- package/dist/agent-src/skills/ui-apply-generic/SKILL.md +180 -0
- package/dist/agent-src/skills/universal-project-analysis/SKILL.md +14 -37
- package/dist/agent-src/skills/video-director/SKILL.md +9 -6
- package/dist/agent-src/templates/agents/agent-project-settings.example.yml +1 -1
- package/dist/agent-src/templates/scripts/work_engine/directives/ui/apply.ts +162 -17
- package/dist/agent-src/templates/scripts/work_engine/directives/ui/design.ts +118 -0
- package/dist/agent-src/templates/scripts/work_engine/directives/ui/polish.ts +49 -6
- package/dist/agent-src/templates/scripts/work_engine/directives/ui/review.ts +9 -5
- package/dist/agent-src/templates/scripts/work_engine/directives/ui/stack_bundles.ts +182 -29
- package/dist/agent-src/templates/scripts/work_engine/stack/detect.ts +311 -24
- package/dist/agent-src/templates/scripts/work_engine/state.ts +55 -0
- package/dist/cli-delegate/chunk-523HCFYO.js +90 -0
- package/dist/cli-delegate/{chunk-IR7P7JWA.js → chunk-A3HJ6NTI.js} +271 -59
- package/dist/cli-delegate/chunk-EJYIGZ3T.js +125 -0
- package/dist/cli-delegate/{chunk-RDGRHAAB.js → chunk-HYWSRNUX.js} +6 -86
- package/dist/cli-delegate/{chunk-YMFO6FDN.js → chunk-QFKQLQ7X.js} +313 -81
- package/dist/cli-delegate/{chunk-4SV3XGFA.js → chunk-TEGJU2JX.js} +7 -9
- package/dist/cli-delegate/chunk-TJIAM5FW.js +133 -0
- package/dist/cli-delegate/cmd_conformance.js +4 -7
- package/dist/cli-delegate/cmd_doctor.js +3 -5
- package/dist/cli-delegate/cmd_explain.js +153 -85
- package/dist/cli-delegate/cmd_export.js +5 -3
- package/dist/cli-delegate/cmd_fleet.js +2 -1
- package/dist/cli-delegate/cmd_preflight.js +6 -123
- package/dist/cli-delegate/cmd_prune.js +5 -3
- package/dist/cli-delegate/cmd_settings_migrate.js +5 -3
- package/dist/cli-delegate/cmd_sync.js +5 -3
- package/dist/cli-delegate/cmd_uninstall.js +6 -5
- package/dist/cli-delegate/cmd_validate.js +5 -3
- package/dist/discovery/deprecation-report.md +1 -1
- package/dist/discovery/discovery-manifest.json +116 -72
- package/dist/discovery/discovery-manifest.json.sha256 +1 -1
- package/dist/discovery/discovery-manifest.summary.md +3 -3
- package/dist/discovery/orphan-report.md +1 -1
- package/dist/discovery/packs.json +7 -5
- package/dist/discovery/trust-report.md +2 -2
- package/dist/discovery/workspaces.json +6 -4
- package/dist/hooks/dispatch.js +174 -13
- package/dist/install/install.mjs +605 -412
- package/dist/install/ruleInScope.js +123 -0
- package/dist/install/ruleInScope.js.map +1 -0
- package/dist/install/rule_scope.js +2 -2
- package/dist/install/rule_scope.js.map +1 -1
- package/dist/install/wizard-plan.js +10 -1
- package/dist/install/wizard-plan.js.map +1 -1
- package/dist/mcp/registry-manifest.json +3 -3
- package/dist/mcp/server.json +2 -2
- package/dist/mcp/server.mjs +710 -2491
- package/dist/router.json +1 -1
- package/dist/scripts/condense.js +8 -24
- package/dist/scripts/condense.js.map +1 -1
- package/docs/MIGRATION.md +1 -1
- package/docs/guidelines/agent-infra/agent-interaction-and-decision-quality.md +71 -0
- package/docs/guidelines/agent-infra/minimal-safe-diff-mechanics.md +24 -1
- package/docs/guidelines/code-clarity.md +9 -2
- package/docs/guidelines/design-fidelity-mechanics.md +58 -0
- package/llms.txt +63 -27
- package/package.json +4 -4
- package/src/agent-src/templates/scripts/work_engine/directives/ui/apply.ts +162 -17
- package/src/agent-src/templates/scripts/work_engine/directives/ui/design.ts +118 -0
- package/src/agent-src/templates/scripts/work_engine/directives/ui/polish.ts +49 -6
- package/src/agent-src/templates/scripts/work_engine/directives/ui/review.ts +9 -5
- package/src/agent-src/templates/scripts/work_engine/directives/ui/stack_bundles.ts +182 -29
- package/src/agent-src/templates/scripts/work_engine/stack/detect.ts +311 -24
- package/src/agent-src/templates/scripts/work_engine/state.ts +55 -0
- package/src/config/agent-settings.template.yml +8 -0
- package/src/install/ruleInScope.ts +133 -0
- package/src/install/rule_scope.ts +2 -2
- package/src/install/wizard-plan.ts +11 -1
- package/src/scripts/_lib/counted_probe.ts +142 -0
- package/src/scripts/_lib/global_deploy_inventory.ts +38 -2
- package/src/scripts/_lib/overbuild_lens_contract.ts +196 -0
- package/src/scripts/_lib/release_scope.ts +207 -0
- package/src/scripts/ai_council/stance_tally.ts +37 -2
- package/src/scripts/audit_auto_rules.ts +9 -9
- package/src/scripts/audit_likelihood.ts +3 -3
- package/src/scripts/audit_overlap.ts +2 -2
- package/src/scripts/audit_skill_overlap.ts +233 -95
- package/src/scripts/audit_skill_overlap_allowlist.json +4 -0
- package/src/scripts/build_proof.ts +15 -1
- package/src/scripts/check_dependency_floors.ts +16 -6
- package/src/scripts/check_gate_coverage.ts +6 -6
- package/src/scripts/check_installer_import_purity.ts +295 -0
- package/src/scripts/check_kernel_rule_bundle.ts +5 -4
- package/src/scripts/check_token_quality_golden.ts +0 -18
- package/src/scripts/code_graph/loader.ts +35 -6
- package/src/scripts/compile_router.ts +3 -8
- package/src/scripts/condense.ts +9 -30
- package/src/scripts/explain_run.ts +0 -1
- package/src/scripts/generate_pack_manifests.ts +3 -20
- package/src/scripts/hooks/block_kernel_rule_writes.ts +145 -10
- package/src/scripts/hooks/block_no_verify.ts +101 -0
- package/src/scripts/install-hooks.sh +36 -4
- package/src/scripts/install.ts +210 -5
- package/src/scripts/lint_agent_security.ts +5 -4
- package/src/scripts/lint_framework_leakage_allowlist.json +2 -2
- package/src/scripts/lint_regression.ts +92 -1
- package/src/scripts/lint_skill_descriptions.ts +93 -5
- package/src/scripts/lint_ui_stack_bundles.ts +31 -0
- package/src/scripts/project_thin_rules.ts +2 -2
- package/src/scripts/release.ts +107 -3
- package/src/scripts/resolve_lint_scope.ts +53 -0
- package/src/scripts/router_telemetry.ts +0 -5
- package/src/scripts/runtime_dispatcher.ts +40 -0
- package/src/scripts/schemas/pack.schema.json +1 -5
- package/src/scripts/schemas/rule.schema.json +1 -2
- package/src/scripts/self_review_gate.ts +15 -73
- package/src/scripts/skill_linter.ts +93 -11
- package/src/scripts/sweep_dead_scan_roots.ts +550 -0
- package/src/scripts/trigger_coverage.ts +11 -37
- package/dist/cli-delegate/chunk-7DSAMZFU.js +0 -356
- package/dist/cli-delegate/chunk-BJGXZNPM.js +0 -266
- package/dist/cli-delegate/chunk-TFG57JJM.js +0 -1969
|
@@ -44,11 +44,14 @@ const REPO_ROOT = path.resolve(fileURLToPath(import.meta.url), '..', '..', '..')
|
|
|
44
44
|
* exact pin cannot suffer the freshest-patch failure (the version is either
|
|
45
45
|
* cached or it is not), but it also forfeits patch fixes — so each one is
|
|
46
46
|
* named here rather than silently tolerated.
|
|
47
|
+
*
|
|
48
|
+
* Empty since the code-graph parser pair (`web-tree-sitter` /
|
|
49
|
+
* `tree-sitter-wasms`) left `dependencies` — it was the only entry. The pair's
|
|
50
|
+
* ABI pin now lives in `code_graph/loader.ts`'s install hint, because that is
|
|
51
|
+
* the only place a re-enabler reads it; this gate scans `dependencies` and so
|
|
52
|
+
* no longer sees the pair at all.
|
|
47
53
|
*/
|
|
48
|
-
export const EXACT_PIN_EXCEPTIONS: Readonly<Record<string, string>> = {
|
|
49
|
-
'web-tree-sitter': 'ABI-locked against tree-sitter-wasms; see code-graph engine pin',
|
|
50
|
-
'tree-sitter-wasms': 'ABI-locked against web-tree-sitter; see code-graph engine pin',
|
|
51
|
-
};
|
|
54
|
+
export const EXACT_PIN_EXCEPTIONS: Readonly<Record<string, string>> = {};
|
|
52
55
|
|
|
53
56
|
/**
|
|
54
57
|
* Floors that are non-settled ON PURPOSE, because every version below them is
|
|
@@ -74,8 +77,15 @@ const EXACT_VERSION = /^\d+\.\d+\.\d+$/;
|
|
|
74
77
|
/**
|
|
75
78
|
* Every violation in `dependencies`, as human-readable lines. Empty array
|
|
76
79
|
* means the manifest cannot produce a freshest-patch `ETARGET` for a consumer.
|
|
80
|
+
*
|
|
81
|
+
* `exactPinExceptions` defaults to the module's list and is injectable so the
|
|
82
|
+
* allow-an-exact-pin branch stays covered now that the real list is empty —
|
|
83
|
+
* otherwise the only way to test it would be to add a fake production entry.
|
|
77
84
|
*/
|
|
78
|
-
export function evaluate(
|
|
85
|
+
export function evaluate(
|
|
86
|
+
dependencies: Readonly<Record<string, string>>,
|
|
87
|
+
exactPinExceptions: Readonly<Record<string, string>> = EXACT_PIN_EXCEPTIONS,
|
|
88
|
+
): string[] {
|
|
79
89
|
const errors: string[] = [];
|
|
80
90
|
for (const [name, range] of Object.entries(dependencies)) {
|
|
81
91
|
const security = SECURITY_FLOOR_EXCEPTIONS[name];
|
|
@@ -89,7 +99,7 @@ export function evaluate(dependencies: Readonly<Record<string, string>>): string
|
|
|
89
99
|
continue;
|
|
90
100
|
}
|
|
91
101
|
if (EXACT_VERSION.test(range)) {
|
|
92
|
-
if (!(name in
|
|
102
|
+
if (!(name in exactPinExceptions)) {
|
|
93
103
|
errors.push(
|
|
94
104
|
`${name}@${range}: exact pin without an entry in EXACT_PIN_EXCEPTIONS — ` +
|
|
95
105
|
'add the reason there, or use a settled caret floor (^X.Y.0)',
|
|
@@ -31,13 +31,13 @@
|
|
|
31
31
|
* Exit codes: 0 all enforced gates cleared their floor · 1 a gate is blind,
|
|
32
32
|
* collapsed, or silent · 2 the manifest itself is missing/empty/malformed.
|
|
33
33
|
*/
|
|
34
|
-
import { spawnSync } from 'node:child_process';
|
|
35
34
|
import * as fs from 'node:fs';
|
|
36
35
|
import * as path from 'node:path';
|
|
37
36
|
import process from 'node:process';
|
|
38
37
|
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
39
38
|
|
|
40
39
|
import yaml from 'js-yaml';
|
|
40
|
+
import { runCountedProbe } from './_lib/counted_probe.js';
|
|
41
41
|
|
|
42
42
|
const _HERE = fileURLToPath(import.meta.url);
|
|
43
43
|
const REPO_ROOT = path.resolve(path.dirname(_HERE), '..', '..');
|
|
@@ -184,11 +184,11 @@ export function classify(
|
|
|
184
184
|
|
|
185
185
|
function run_gate(spec: GateSpec): { scanned: number | null; crashed: boolean; exit_code: number | null } {
|
|
186
186
|
const runner = path.join(REPO_ROOT, 'scripts-run');
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
});
|
|
191
|
-
if (r.
|
|
187
|
+
// Bounded read: a gate whose output overflowed would lose its `scanned:`
|
|
188
|
+
// line and be classified `silent` — the scan-scope guard reporting a dead
|
|
189
|
+
// scope that is merely truncated. `runCountedProbe` throws instead.
|
|
190
|
+
const r = runCountedProbe(runner, [`src/scripts/${spec.id}`, ...spec.argv], { cwd: REPO_ROOT });
|
|
191
|
+
if (r.failure !== null && r.status === null) return { scanned: null, crashed: true, exit_code: null };
|
|
192
192
|
// A gate may legitimately exit non-zero (it found real violations) and still
|
|
193
193
|
// have scanned plenty — coverage and verdict are different questions.
|
|
194
194
|
return { scanned: parse_scanned(`${r.stdout}\n${r.stderr}`), crashed: false, exit_code: r.status };
|
|
@@ -0,0 +1,295 @@
|
|
|
1
|
+
#!/usr/bin/env tsx
|
|
2
|
+
/**
|
|
3
|
+
* Guard against a maintainer-only CLI being bundled into the consumer installer.
|
|
4
|
+
*
|
|
5
|
+
* ## The incident this exists to prevent (road-to-consistent-rule-scoping, 2026-07-31)
|
|
6
|
+
*
|
|
7
|
+
* `src/install/rule_scope.ts` imported the rule-scoping predicate from
|
|
8
|
+
* `src/scripts/condense.ts` — deliberately, so install semantics could not drift
|
|
9
|
+
* from projection semantics. Correct intent. But `condense.ts` is maintainer CLI
|
|
10
|
+
* tooling that ends in a module-level self-invoke:
|
|
11
|
+
*
|
|
12
|
+
* const isMain = _isCliEntry();
|
|
13
|
+
* if (isMain) { process.exit(main()); }
|
|
14
|
+
*
|
|
15
|
+
* `_isCliEntry()` compares `import.meta.url` against `process.argv[1]`. That is
|
|
16
|
+
* right for a file run directly — and wrong once esbuild BUNDLES the module,
|
|
17
|
+
* because `import.meta.url` then resolves to the BUNDLE's own URL, which *is*
|
|
18
|
+
* `argv[1]` when a consumer runs `node dist/install/install.mjs`. The guard
|
|
19
|
+
* fired inside the installer, `condense.main()` ran, found no maintainer source
|
|
20
|
+
* tree in a consumer checkout, printed "No source directory found" and called
|
|
21
|
+
* `process.exit(1)` — killing the install AFTER the payload had already synced.
|
|
22
|
+
*
|
|
23
|
+
* All four `smoke-public-install` legs went red. Nothing caught it earlier:
|
|
24
|
+
* `prepack-check` verifies that imports resolve to SHIPPED paths (they did),
|
|
25
|
+
* the bundle-freshness gate only byte-diffs the rebuilt output, and the
|
|
26
|
+
* behavioural smoke test only runs post-push in its own workflow.
|
|
27
|
+
*
|
|
28
|
+
* ## What this checks
|
|
29
|
+
*
|
|
30
|
+
* The transitive relative-import closure of the installer bundle entry must
|
|
31
|
+
* contain NO module-level `process.exit(` outside the entry itself.
|
|
32
|
+
*
|
|
33
|
+
* "Module-level" is decided structurally, not textually: the AST walk descends
|
|
34
|
+
* through top-level statements and nested blocks/ifs/try but STOPS at any
|
|
35
|
+
* function, method, class, arrow or accessor body. So
|
|
36
|
+
* `function main() { process.exit(1) }` is fine — it only runs when called —
|
|
37
|
+
* while `if (isMain) { process.exit(main()) }` is not, because importing the
|
|
38
|
+
* module runs it.
|
|
39
|
+
*
|
|
40
|
+
* The entry module is exempt: it IS the CLI, and its own exit is the point.
|
|
41
|
+
*
|
|
42
|
+
* ## Why this shape and not the alternatives
|
|
43
|
+
*
|
|
44
|
+
* - **Not a string scan of the built bundle.** That reports a mystery literal
|
|
45
|
+
* with no cause. This reports the import CHAIN
|
|
46
|
+
* (`install.ts → rule_scope.ts → condense.ts`), which is the actionable fact.
|
|
47
|
+
* - **Not "forbid importing condense.ts".** A denylist of one file rots the
|
|
48
|
+
* moment a second CLI module grows an entry guard. The hazard is the
|
|
49
|
+
* module-level exit, so that is what is detected.
|
|
50
|
+
* - **Runs on SOURCE, so it needs no build.** It fails in `task ci` before a
|
|
51
|
+
* push instead of on four CI legs after one.
|
|
52
|
+
*
|
|
53
|
+
* Exit 0 clean, 1 on any finding. No network, no build, no spend.
|
|
54
|
+
*/
|
|
55
|
+
|
|
56
|
+
import * as fs from 'node:fs';
|
|
57
|
+
import * as path from 'node:path';
|
|
58
|
+
import { fileURLToPath } from 'node:url';
|
|
59
|
+
|
|
60
|
+
import ts from 'typescript';
|
|
61
|
+
|
|
62
|
+
const _HERE = path.dirname(fileURLToPath(import.meta.url));
|
|
63
|
+
const REPO_ROOT = path.resolve(_HERE, '..', '..');
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Bundle entry points that ship to consumers as a single file.
|
|
67
|
+
*
|
|
68
|
+
* Keep in sync with the `build:*bundle` scripts in package.json. Each entry is
|
|
69
|
+
* exempt from the check for its own module-level exit — it is the CLI.
|
|
70
|
+
*/
|
|
71
|
+
const BUNDLE_ENTRIES: readonly string[] = [
|
|
72
|
+
'src/scripts/install.ts', // → dist/install/install.mjs (build:install-bundle)
|
|
73
|
+
];
|
|
74
|
+
|
|
75
|
+
interface Finding {
|
|
76
|
+
/** The offending module, repo-relative POSIX. */
|
|
77
|
+
readonly module: string;
|
|
78
|
+
/** 1-indexed line of the offending call. */
|
|
79
|
+
readonly line: number;
|
|
80
|
+
/** How the entry reaches it, entry first. */
|
|
81
|
+
readonly chain: readonly string[];
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function toPosix(p: string): string {
|
|
85
|
+
return p.split(path.sep).join('/');
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function relToRepo(abs: string, root: string = REPO_ROOT): string {
|
|
89
|
+
return toPosix(path.relative(root, abs));
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Resolve a relative import specifier to a source file on disk.
|
|
94
|
+
*
|
|
95
|
+
* The codebase imports with `.js` extensions (NodeNext) while the sources are
|
|
96
|
+
* `.ts`, so try the TypeScript twin first, then the literal path, then an
|
|
97
|
+
* `index` file. Returns `null` for anything unresolvable — a bare package
|
|
98
|
+
* specifier, a type-only path that no longer exists, or a `.json` asset.
|
|
99
|
+
*/
|
|
100
|
+
function resolveImport(fromFile: string, specifier: string): string | null {
|
|
101
|
+
if (!specifier.startsWith('.')) return null;
|
|
102
|
+
const base = path.resolve(path.dirname(fromFile), specifier);
|
|
103
|
+
const candidates = [
|
|
104
|
+
base.replace(/\.js$/, '.ts'),
|
|
105
|
+
base.replace(/\.js$/, '.tsx'),
|
|
106
|
+
base,
|
|
107
|
+
`${base}.ts`,
|
|
108
|
+
path.join(base, 'index.ts'),
|
|
109
|
+
];
|
|
110
|
+
for (const c of candidates) {
|
|
111
|
+
try {
|
|
112
|
+
if (fs.statSync(c).isFile()) return c;
|
|
113
|
+
} catch {
|
|
114
|
+
// next candidate
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
return null;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/** Relative import specifiers declared by `source`, in source order. */
|
|
121
|
+
function importSpecifiers(source: ts.SourceFile): string[] {
|
|
122
|
+
const out: string[] = [];
|
|
123
|
+
const visit = (node: ts.Node): void => {
|
|
124
|
+
if (
|
|
125
|
+
(ts.isImportDeclaration(node) || ts.isExportDeclaration(node)) &&
|
|
126
|
+
node.moduleSpecifier !== undefined &&
|
|
127
|
+
ts.isStringLiteral(node.moduleSpecifier)
|
|
128
|
+
) {
|
|
129
|
+
out.push(node.moduleSpecifier.text);
|
|
130
|
+
} else if (
|
|
131
|
+
ts.isCallExpression(node) &&
|
|
132
|
+
node.expression.kind === ts.SyntaxKind.ImportKeyword &&
|
|
133
|
+
node.arguments.length > 0 &&
|
|
134
|
+
ts.isStringLiteral(node.arguments[0] as ts.Node)
|
|
135
|
+
) {
|
|
136
|
+
// A dynamic import() is still bundled by esbuild unless code-split,
|
|
137
|
+
// so it carries the same hazard as a static one.
|
|
138
|
+
out.push((node.arguments[0] as ts.StringLiteral).text);
|
|
139
|
+
}
|
|
140
|
+
ts.forEachChild(node, visit);
|
|
141
|
+
};
|
|
142
|
+
ts.forEachChild(source, visit);
|
|
143
|
+
return out;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/** True for nodes whose body only runs when called — the walk stops here. */
|
|
147
|
+
function isDeferredScope(node: ts.Node): boolean {
|
|
148
|
+
return (
|
|
149
|
+
ts.isFunctionDeclaration(node) ||
|
|
150
|
+
ts.isFunctionExpression(node) ||
|
|
151
|
+
ts.isArrowFunction(node) ||
|
|
152
|
+
ts.isMethodDeclaration(node) ||
|
|
153
|
+
ts.isConstructorDeclaration(node) ||
|
|
154
|
+
ts.isGetAccessorDeclaration(node) ||
|
|
155
|
+
ts.isSetAccessorDeclaration(node) ||
|
|
156
|
+
ts.isClassDeclaration(node) ||
|
|
157
|
+
ts.isClassExpression(node)
|
|
158
|
+
);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/** Is `node` a `process.exit(...)` call? */
|
|
162
|
+
function isProcessExitCall(node: ts.Node): boolean {
|
|
163
|
+
if (!ts.isCallExpression(node)) return false;
|
|
164
|
+
const callee = node.expression;
|
|
165
|
+
if (!ts.isPropertyAccessExpression(callee)) return false;
|
|
166
|
+
return (
|
|
167
|
+
ts.isIdentifier(callee.expression) &&
|
|
168
|
+
callee.expression.text === 'process' &&
|
|
169
|
+
callee.name.text === 'exit'
|
|
170
|
+
);
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* 1-indexed lines of every `process.exit(` that executes at MODULE LOAD.
|
|
175
|
+
*
|
|
176
|
+
* Descends through statement-level structure (blocks, if/else, try/catch/finally,
|
|
177
|
+
* loops, labels) and stops at any deferred scope, so a call inside a function is
|
|
178
|
+
* not reported while one inside a top-level `if` is.
|
|
179
|
+
*/
|
|
180
|
+
function moduleLevelExitLines(source: ts.SourceFile): number[] {
|
|
181
|
+
const lines: number[] = [];
|
|
182
|
+
const visit = (node: ts.Node): void => {
|
|
183
|
+
if (isDeferredScope(node)) return;
|
|
184
|
+
if (isProcessExitCall(node)) {
|
|
185
|
+
lines.push(source.getLineAndCharacterOfPosition(node.getStart(source)).line + 1);
|
|
186
|
+
}
|
|
187
|
+
ts.forEachChild(node, visit);
|
|
188
|
+
};
|
|
189
|
+
ts.forEachChild(source, visit);
|
|
190
|
+
return lines;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
function parse(file: string): ts.SourceFile | null {
|
|
194
|
+
let text: string;
|
|
195
|
+
try {
|
|
196
|
+
text = fs.readFileSync(file, 'utf-8');
|
|
197
|
+
} catch {
|
|
198
|
+
return null;
|
|
199
|
+
}
|
|
200
|
+
return ts.createSourceFile(file, text, ts.ScriptTarget.ESNext, true);
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
/**
|
|
204
|
+
* Walk one entry's closure; return every module-level exit found below it.
|
|
205
|
+
*
|
|
206
|
+
* `root` defaults to the repo; tests pass a fixture tree so the chain-reporting
|
|
207
|
+
* itself is covered, not only the per-file detector.
|
|
208
|
+
*/
|
|
209
|
+
function auditEntry(entryRel: string, root: string = REPO_ROOT): Finding[] {
|
|
210
|
+
const entryAbs = path.join(root, entryRel);
|
|
211
|
+
if (!fs.existsSync(entryAbs)) {
|
|
212
|
+
process.stderr.write(
|
|
213
|
+
`check-installer-import-purity: entry ${entryRel} does not exist — ` +
|
|
214
|
+
'update BUNDLE_ENTRIES\n',
|
|
215
|
+
);
|
|
216
|
+
process.exitCode = 1;
|
|
217
|
+
return [];
|
|
218
|
+
}
|
|
219
|
+
const findings: Finding[] = [];
|
|
220
|
+
const seen = new Set<string>([entryAbs]);
|
|
221
|
+
// BFS so the reported chain is a shortest path — the least confusing one.
|
|
222
|
+
const queue: Array<{ file: string; chain: string[] }> = [
|
|
223
|
+
{ file: entryAbs, chain: [entryRel] },
|
|
224
|
+
];
|
|
225
|
+
while (queue.length > 0) {
|
|
226
|
+
const { file, chain } = queue.shift() as { file: string; chain: string[] };
|
|
227
|
+
const source = parse(file);
|
|
228
|
+
if (source === null) continue;
|
|
229
|
+
|
|
230
|
+
// The entry is the CLI; its own module-level exit is intended.
|
|
231
|
+
if (file !== entryAbs) {
|
|
232
|
+
for (const line of moduleLevelExitLines(source)) {
|
|
233
|
+
findings.push({ module: relToRepo(file, root), line, chain });
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
for (const spec of importSpecifiers(source)) {
|
|
238
|
+
const target = resolveImport(file, spec);
|
|
239
|
+
if (target === null || seen.has(target)) continue;
|
|
240
|
+
seen.add(target);
|
|
241
|
+
queue.push({ file: target, chain: [...chain, relToRepo(target, root)] });
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
return findings;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
function main(): number {
|
|
248
|
+
const all: Finding[] = [];
|
|
249
|
+
for (const entry of BUNDLE_ENTRIES) {
|
|
250
|
+
all.push(...auditEntry(entry));
|
|
251
|
+
}
|
|
252
|
+
if (all.length === 0) {
|
|
253
|
+
process.stdout.write(
|
|
254
|
+
`✅ installer import purity: ${BUNDLE_ENTRIES.length} bundle entr` +
|
|
255
|
+
`${BUNDLE_ENTRIES.length === 1 ? 'y' : 'ies'} reach no module-level ` +
|
|
256
|
+
'process.exit()\n',
|
|
257
|
+
);
|
|
258
|
+
return 0;
|
|
259
|
+
}
|
|
260
|
+
process.stdout.write('❌ installer import purity — module-level process.exit() reachable:\n');
|
|
261
|
+
for (const f of all) {
|
|
262
|
+
process.stdout.write(`\n ${f.module}:${f.line}\n`);
|
|
263
|
+
process.stdout.write(` reached via: ${f.chain.join(' → ')}\n`);
|
|
264
|
+
}
|
|
265
|
+
process.stdout.write(
|
|
266
|
+
'\n Why this fails the build: esbuild bundles the whole closure into\n' +
|
|
267
|
+
' dist/install/install.mjs. A module-level process.exit() then runs when a\n' +
|
|
268
|
+
' CONSUMER loads the installer — and a CLI entry guard comparing\n' +
|
|
269
|
+
' import.meta.url to process.argv[1] evaluates TRUE inside a bundle, because\n' +
|
|
270
|
+
' the bundle IS argv[1].\n\n' +
|
|
271
|
+
' Fix: move the code the installer actually needs into a module with no CLI\n' +
|
|
272
|
+
' entry and no top-level exit, and let the CLI re-export it. Do NOT relax\n' +
|
|
273
|
+
' this gate — the failure it prevents kills a consumer install after the\n' +
|
|
274
|
+
' payload has already been written.\n',
|
|
275
|
+
);
|
|
276
|
+
return 1;
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
const _isCliEntry = (): boolean => {
|
|
280
|
+
if (process.argv[1] === undefined) return false;
|
|
281
|
+
try {
|
|
282
|
+
return (
|
|
283
|
+
fs.realpathSync(fileURLToPath(import.meta.url)) ===
|
|
284
|
+
fs.realpathSync(path.resolve(process.argv[1]))
|
|
285
|
+
);
|
|
286
|
+
} catch {
|
|
287
|
+
return false;
|
|
288
|
+
}
|
|
289
|
+
};
|
|
290
|
+
|
|
291
|
+
if (_isCliEntry()) {
|
|
292
|
+
process.exit(main());
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
export { auditEntry, moduleLevelExitLines, BUNDLE_ENTRIES };
|
|
@@ -36,6 +36,7 @@ import * as fs from 'node:fs';
|
|
|
36
36
|
import * as path from 'node:path';
|
|
37
37
|
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
38
38
|
import { KERNEL_RULE_FILENAMES } from './_lib/kernel_rules.js';
|
|
39
|
+
import { runCountedProbe } from './_lib/counted_probe.js';
|
|
39
40
|
|
|
40
41
|
const KERNEL_RULES: ReadonlySet<string> = KERNEL_RULE_FILENAMES;
|
|
41
42
|
|
|
@@ -56,13 +57,13 @@ const KERNEL_DIR = 'src/rules';
|
|
|
56
57
|
const DEFAULT_LABEL = 'bundled-always-rules-acknowledged';
|
|
57
58
|
|
|
58
59
|
function _git_changed_files(base_ref: string): string[] {
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
});
|
|
60
|
+
// Bounded read: a truncated diff would shrink the change set and let the
|
|
61
|
+
// bundle guard pass on files it never saw.
|
|
62
|
+
const res = runCountedProbe('git', ['diff', '--name-only', `${base_ref}...HEAD`]);
|
|
62
63
|
// Python uses check_output(stderr=STDOUT, text=True): on non-zero it raises
|
|
63
64
|
// CalledProcessError and the handler prints the combined output. spawnSync
|
|
64
65
|
// does not raise; mirror by checking status and combining stdout+stderr.
|
|
65
|
-
if (res.
|
|
66
|
+
if (!res.ok) {
|
|
66
67
|
const combined = `${res.stdout ?? ''}${res.stderr ?? ''}`;
|
|
67
68
|
process.stderr.write(`❌ git diff failed: ${combined.trim()}\n`);
|
|
68
69
|
return [];
|
|
@@ -113,20 +113,6 @@ export function router_triggers(router: Json): Map<string, Array<Record<string,
|
|
|
113
113
|
return map;
|
|
114
114
|
}
|
|
115
115
|
|
|
116
|
-
// trigger_coverage.ts word semantics: lowercase alpha-led tokens, len > 2.
|
|
117
|
-
const _WORD = /[a-z][a-z0-9_]+/g;
|
|
118
|
-
|
|
119
|
-
function _tokens(text: string): Set<string> {
|
|
120
|
-
const out = new Set<string>();
|
|
121
|
-
const low = text.toLowerCase();
|
|
122
|
-
_WORD.lastIndex = 0;
|
|
123
|
-
let m: RegExpExecArray | null;
|
|
124
|
-
while ((m = _WORD.exec(low)) !== null) {
|
|
125
|
-
if (m[0].length > 2) out.add(m[0]);
|
|
126
|
-
}
|
|
127
|
-
return out;
|
|
128
|
-
}
|
|
129
|
-
|
|
130
116
|
/**
|
|
131
117
|
* Whether a task exercises ≥1 trigger of the rule (prompt↔trigger
|
|
132
118
|
* falsifiability, Phase 3). Kernel rules always fire.
|
|
@@ -140,16 +126,12 @@ export function task_fires_rule(
|
|
|
140
126
|
if (kernel.has(ruleId)) return true;
|
|
141
127
|
const triggers = router_triggers(router).get(ruleId) ?? [];
|
|
142
128
|
const low = task.prompt.toLowerCase();
|
|
143
|
-
const toks = _tokens(task.prompt);
|
|
144
129
|
const files = Array.isArray(task.context_files) ? task.context_files : [];
|
|
145
130
|
const cmd = typeof task.command === 'string' ? task.command : '';
|
|
146
131
|
for (const trig of triggers) {
|
|
147
132
|
if ('keyword' in trig || 'phrase' in trig) {
|
|
148
133
|
const needle = String(trig.keyword ?? trig.phrase).toLowerCase();
|
|
149
134
|
if (needle && low.includes(needle)) return true;
|
|
150
|
-
} else if ('intent' in trig) {
|
|
151
|
-
const words = _tokens(String(trig.intent));
|
|
152
|
-
if (words.size > 0 && [...words].every((w) => toks.has(w))) return true;
|
|
153
135
|
} else if ('path_prefix' in trig) {
|
|
154
136
|
const prefix = String(trig.path_prefix);
|
|
155
137
|
if (files.some((f) => f.startsWith(prefix))) return true;
|
|
@@ -2,10 +2,11 @@
|
|
|
2
2
|
* WASM tree-sitter loader for the code-graph engine.
|
|
3
3
|
*
|
|
4
4
|
* Pinned pair (ABI-verified 2026-07-23): `web-tree-sitter@0.24.7` +
|
|
5
|
-
* `tree-sitter-wasms@0.1.13` → grammar ABI 14. The ABI smoke test
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
* no node-gyp — grammars load from `node_modules` via the resolved
|
|
5
|
+
* `tree-sitter-wasms@0.1.13` → grammar ABI 14. The ABI smoke test lives in
|
|
6
|
+
* `tests/scripts/code_graph.test.ts`; it runs in this repo because the pair is
|
|
7
|
+
* a devDependency. Consumers install neither (see `INSTALL_HINT` below). No
|
|
8
|
+
* network, no node-gyp — grammars load from `node_modules` via the resolved
|
|
9
|
+
* path.
|
|
9
10
|
*
|
|
10
11
|
* web-tree-sitter 0.24.x is CJS with a single default export (the Parser
|
|
11
12
|
* class) and the classic `Parser.Language.load()` API; we reach it through
|
|
@@ -51,10 +52,32 @@ interface ParserStatic {
|
|
|
51
52
|
Language: { load(wasmPath: string): Promise<TsLanguage> };
|
|
52
53
|
}
|
|
53
54
|
|
|
55
|
+
/**
|
|
56
|
+
* The parser pair is a devDependency, not a runtime one: this repo's tests still
|
|
57
|
+
* exercise the loader, while a consumer install resolves neither. The engine is
|
|
58
|
+
* permanently `enabled: false` (measured recall 0.365 vs grep 0.797), so
|
|
59
|
+
* shipping ~51 MB of WASM to every consumer for a path none of them can reach
|
|
60
|
+
* was pure install cost. A consumer re-enabling the engine installs the
|
|
61
|
+
* ABI-locked pair themselves — this message carries the pin, because
|
|
62
|
+
* `check_dependency_floors` only scans the `dependencies` block.
|
|
63
|
+
*/
|
|
64
|
+
const INSTALL_HINT =
|
|
65
|
+
'the code-graph engine needs its parser pair, which this package no longer ships to consumers ' +
|
|
66
|
+
'(the engine is disabled by a recorded null result). Install the ABI-locked pair:\n' +
|
|
67
|
+
' npm i web-tree-sitter@0.24.7 tree-sitter-wasms@0.1.13\n' +
|
|
68
|
+
'Both versions are exact on purpose — the pair is ABI-coupled (grammar ABI 14) ' +
|
|
69
|
+
'and must move together.';
|
|
70
|
+
|
|
54
71
|
let _wasmDir: string | null = null;
|
|
55
72
|
function wasmDir(): string {
|
|
56
73
|
if (_wasmDir === null) {
|
|
57
|
-
|
|
74
|
+
let pkgJson: string;
|
|
75
|
+
try {
|
|
76
|
+
pkgJson = require.resolve('tree-sitter-wasms/package.json');
|
|
77
|
+
} catch {
|
|
78
|
+
throw new Error(`tree-sitter-wasms is not installed — ${INSTALL_HINT}`);
|
|
79
|
+
}
|
|
80
|
+
_wasmDir = path.join(path.dirname(pkgJson), 'out');
|
|
58
81
|
}
|
|
59
82
|
return _wasmDir;
|
|
60
83
|
}
|
|
@@ -72,7 +95,13 @@ function _Parser(): ParserStatic {
|
|
|
72
95
|
// Require exactly once and cache the class reference — repeated
|
|
73
96
|
// createRequire calls under tsx can hand back a module view whose static
|
|
74
97
|
// `Language` is not yet populated, which breaks the second grammar load.
|
|
75
|
-
if (_mod === null)
|
|
98
|
+
if (_mod === null) {
|
|
99
|
+
try {
|
|
100
|
+
_mod = require('web-tree-sitter') as ParserStatic;
|
|
101
|
+
} catch {
|
|
102
|
+
throw new Error(`web-tree-sitter is not installed — ${INSTALL_HINT}`);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
76
105
|
return _mod;
|
|
77
106
|
}
|
|
78
107
|
|
|
@@ -73,14 +73,9 @@ const LEGACY_TIER_MAP: Record<string, string> = {
|
|
|
73
73
|
};
|
|
74
74
|
|
|
75
75
|
const ALLOWED_TIERS = new Set(['kernel', 'tier-1', 'tier-2']);
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
'intent',
|
|
80
|
-
'file_pattern',
|
|
81
|
-
'path_prefix',
|
|
82
|
-
'command',
|
|
83
|
-
]);
|
|
76
|
+
// `intent` was removed: it never auto-matched at runtime, so declaring one gave
|
|
77
|
+
// the author a false activation path. Compiling one is now a hard error.
|
|
78
|
+
const ALLOWED_TRIGGER_KEYS = new Set(['keyword', 'phrase', 'file_pattern', 'path_prefix', 'command']);
|
|
84
79
|
|
|
85
80
|
function _parse_frontmatter(text: string): JsonObject {
|
|
86
81
|
if (!text.startsWith('---\n')) {
|
package/src/scripts/condense.ts
CHANGED
|
@@ -238,6 +238,13 @@ function _stripChars(s: string, chars: string): string {
|
|
|
238
238
|
// the repo root.
|
|
239
239
|
// =============================================================================
|
|
240
240
|
|
|
241
|
+
// road-to-consistent-rule-scoping: the predicate moved to a side-effect-free
|
|
242
|
+
// module so the consumer installer can import it WITHOUT pulling this file's
|
|
243
|
+
// module-level CLI self-invoke into its bundle. Re-exported here because four
|
|
244
|
+
// in-repo consumers import it from `condense.js`.
|
|
245
|
+
export { rule_in_scope } from '../install/ruleInScope.js';
|
|
246
|
+
import { rule_in_scope } from '../install/ruleInScope.js';
|
|
247
|
+
|
|
241
248
|
const _HERE = path.dirname(fileURLToPath(import.meta.url)); // <repo>/src/scripts
|
|
242
249
|
const _DEFAULT_PROJECT_ROOT = path.resolve(_HERE, '..', '..');
|
|
243
250
|
|
|
@@ -1014,34 +1021,6 @@ function _read_rule_packs(): string[] | null {
|
|
|
1014
1021
|
* `null` (no role filtering), so every existing 2- and 3-arg call site
|
|
1015
1022
|
* compiles and behaves unchanged.
|
|
1016
1023
|
*/
|
|
1017
|
-
export function rule_in_scope(
|
|
1018
|
-
source_path: string,
|
|
1019
|
-
scope: readonly string[] | null,
|
|
1020
|
-
pack_scope: readonly string[] | null = null,
|
|
1021
|
-
role_scope: readonly string[] | null = null,
|
|
1022
|
-
): boolean {
|
|
1023
|
-
if (scope === null && pack_scope === null && role_scope === null) {
|
|
1024
|
-
return true;
|
|
1025
|
-
}
|
|
1026
|
-
const [meta] = _parse_frontmatter(_readText(source_path));
|
|
1027
|
-
if (meta['type'] === 'always' || meta['alwaysApply'] === true) {
|
|
1028
|
-
return true; // kernel always projects
|
|
1029
|
-
}
|
|
1030
|
-
const axis = (key: string, configured: readonly string[] | null): boolean => {
|
|
1031
|
-
if (configured === null) {
|
|
1032
|
-
return true;
|
|
1033
|
-
}
|
|
1034
|
-
const values = Array.isArray(meta[key])
|
|
1035
|
-
? (meta[key] as unknown[]).map((w) => String(w))
|
|
1036
|
-
: [];
|
|
1037
|
-
if (values.length === 0) {
|
|
1038
|
-
return true; // untagged → fail safe: ship it
|
|
1039
|
-
}
|
|
1040
|
-
return values.some((v) => configured.includes(v));
|
|
1041
|
-
};
|
|
1042
|
-
return axis('workspaces', scope) && axis('packs', pack_scope) && axis('roles', role_scope);
|
|
1043
|
-
}
|
|
1044
|
-
|
|
1045
1024
|
/** List rule basenames under RULES_SOURCE, workspace/pack scope applied. */
|
|
1046
1025
|
function _scoped_rule_basenames(): string[] {
|
|
1047
1026
|
const scope = _read_rule_workspaces();
|
|
@@ -1197,7 +1176,7 @@ function _yaml_scalar(value: string): string {
|
|
|
1197
1176
|
* Derive host-native activation globs from a rule's `triggers:` frontmatter
|
|
1198
1177
|
* (road-to-request-scoped-rule-load Phase 2). `file_pattern` triggers map
|
|
1199
1178
|
* verbatim; `path_prefix` triggers map to `<prefix>**`. Keyword / phrase /
|
|
1200
|
-
*
|
|
1179
|
+
* command triggers produce no glob — those rules stay
|
|
1201
1180
|
* description-activated (Cursor Agent-Requested / Windsurf model_decision).
|
|
1202
1181
|
*/
|
|
1203
1182
|
export function derive_trigger_globs(meta: Record<string, unknown>): string[] {
|
|
@@ -1245,7 +1224,7 @@ export function _emit_windsurf_rule(source: string, target: string): void {
|
|
|
1245
1224
|
const description = _strip(String(meta['description'] ?? '').replace(/\n/g, ' '));
|
|
1246
1225
|
const always_apply = Boolean(meta['alwaysApply'] || meta['type'] === 'always');
|
|
1247
1226
|
// Path-shaped triggers activate host-natively via Windsurf's `glob`
|
|
1248
|
-
// trigger; keyword/
|
|
1227
|
+
// trigger; keyword/phrase-only rules keep `model_decision`.
|
|
1249
1228
|
const globs = always_apply ? [] : derive_trigger_globs(meta);
|
|
1250
1229
|
const trigger = always_apply ? 'always_on' : globs.length > 0 ? 'glob' : 'model_decision';
|
|
1251
1230
|
const lines = [
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
* regenerated on every `task sync` and must never be hand-edited.
|
|
18
18
|
*
|
|
19
19
|
* For each pack home we emit:
|
|
20
|
-
* - `<home>/pack.yaml` — id, label, owner, requires,
|
|
20
|
+
* - `<home>/pack.yaml` — id, label, owner, requires, artefact_count, …
|
|
21
21
|
* - `<home>/README.md` — pack description + per-artefact description table
|
|
22
22
|
*
|
|
23
23
|
* CLI:
|
|
@@ -40,7 +40,6 @@ const PACKAGES = path.join(ROOT, 'packages');
|
|
|
40
40
|
const SRC_DOMAINS = path.join(ROOT, 'src', 'domains');
|
|
41
41
|
const SRC_PACKS = path.join(ROOT, 'src', 'packs');
|
|
42
42
|
const PACKS_VOCAB = path.join(ROOT, 'src', 'config', 'discovery', 'packs.yml');
|
|
43
|
-
const PACKAGE_JSON = path.join(ROOT, 'package.json');
|
|
44
43
|
const GENERATED_HEADER =
|
|
45
44
|
'# Generated by scripts/generate_pack_manifests.ts — DO NOT EDIT BY HAND\n';
|
|
46
45
|
|
|
@@ -138,14 +137,6 @@ function _vocab_lookup(packsVocab: Dict[]): Record<string, Dict> {
|
|
|
138
137
|
return out;
|
|
139
138
|
}
|
|
140
139
|
|
|
141
|
-
function _package_version(): string {
|
|
142
|
-
if (!_exists(PACKAGE_JSON)) {
|
|
143
|
-
return '0.0.0';
|
|
144
|
-
}
|
|
145
|
-
const pkg = JSON.parse(fs.readFileSync(PACKAGE_JSON, 'utf-8')) as Dict;
|
|
146
|
-
return _pyTruthy(pkg.version) ? String(pkg.version) : '0.0.0';
|
|
147
|
-
}
|
|
148
|
-
|
|
149
140
|
function _artefact_record(p: string, logicalRel: string, fm: Dict): Artefact {
|
|
150
141
|
const name = _pyTruthy(fm.name)
|
|
151
142
|
? String(fm.name)
|
|
@@ -256,12 +247,7 @@ function _category_from_logical(logicalRel: string): string {
|
|
|
256
247
|
* Build the pack.yaml meta dict. Mirrors `_build_pack_yaml`. Insertion
|
|
257
248
|
* order here is irrelevant — `_py_safe_dump` sorts keys (sort_keys=True).
|
|
258
249
|
*/
|
|
259
|
-
function _build_pack_yaml(
|
|
260
|
-
pid: string,
|
|
261
|
-
vocab: Record<string, Dict>,
|
|
262
|
-
artefacts: Artefact[],
|
|
263
|
-
version: string,
|
|
264
|
-
): Dict {
|
|
250
|
+
function _build_pack_yaml(pid: string, vocab: Record<string, Dict>, artefacts: Artefact[]): Dict {
|
|
265
251
|
const meta = vocab[pid] ?? {};
|
|
266
252
|
const label = _pyTruthy(meta.label)
|
|
267
253
|
? String(meta.label)
|
|
@@ -289,7 +275,6 @@ function _build_pack_yaml(
|
|
|
289
275
|
trust_level_default: _pyTruthy(meta.trust_level_default)
|
|
290
276
|
? (meta.trust_level_default as unknown)
|
|
291
277
|
: 'core',
|
|
292
|
-
version,
|
|
293
278
|
artefact_count: artefacts.length,
|
|
294
279
|
};
|
|
295
280
|
if (_isPlainObject(meta.onboarding)) {
|
|
@@ -330,7 +315,6 @@ function _render_readme(packMeta: Dict, artefacts: Artefact[]): string {
|
|
|
330
315
|
const requires = (packMeta.requires as unknown[]) ?? [];
|
|
331
316
|
lines.push(
|
|
332
317
|
`- **id**: \`${String(packMeta.id)}\``,
|
|
333
|
-
`- **version**: \`${String(packMeta.version)}\``,
|
|
334
318
|
`- **owner**: ${owner.map(String).join(', ') || '—'}`,
|
|
335
319
|
`- **requires**: ${requires.map(String).join(', ') || '—'}`,
|
|
336
320
|
`- **artefacts**: ${String(packMeta.artefact_count)}`,
|
|
@@ -369,7 +353,6 @@ function _write_if_changed(p: string, content: string, check: boolean): boolean
|
|
|
369
353
|
export function main(argv: string[] = process.argv.slice(2)): number {
|
|
370
354
|
const check = argv.includes('--check');
|
|
371
355
|
const vocab = _vocab_lookup((_load_yaml(PACKS_VOCAB) as Dict[]) ?? []);
|
|
372
|
-
const version = _package_version();
|
|
373
356
|
const homes = _pack_homes();
|
|
374
357
|
if (homes.length === 0) {
|
|
375
358
|
process.stderr.write(
|
|
@@ -380,7 +363,7 @@ export function main(argv: string[] = process.argv.slice(2)): number {
|
|
|
380
363
|
let drift = 0;
|
|
381
364
|
for (const [pid, homeDir, mode] of homes) {
|
|
382
365
|
const artefacts = _collect_artefacts(pid, homeDir, mode);
|
|
383
|
-
const meta = _build_pack_yaml(pid, vocab, artefacts
|
|
366
|
+
const meta = _build_pack_yaml(pid, vocab, artefacts);
|
|
384
367
|
const yamlBody = GENERATED_HEADER + _py_safe_dump(meta);
|
|
385
368
|
const readmeBody = _render_readme(meta, artefacts);
|
|
386
369
|
const label = _relativeToPosix(homeDir, ROOT);
|