@chemx/starter-kit 26.10.10-75 → 26.10.26-386
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/AGENTS.md +41 -19
- package/README.md +23 -13
- package/blueprints/_vitest.config.svelte.ts +11 -0
- package/blueprints/_vitest.config.vue.ts +11 -0
- package/blueprints/molecule-capsule/index.svelte.ts +2 -0
- package/blueprints/molecule-capsule/index.vue.ts +2 -0
- package/blueprints/molecule-capsule/m-chemx-badge/index.svelte.ts +3 -0
- package/blueprints/molecule-capsule/m-chemx-badge/index.vue.ts +3 -0
- package/blueprints/molecule-capsule/m-sample-card.spec.svelte.ts +33 -0
- package/blueprints/shims-svelte.d.ts +3 -0
- package/cli/apply-edits.js +4 -1
- package/cli/args-unsplit-hint.js +33 -0
- package/cli/audit/audit-plain.js +9 -0
- package/cli/audit/audit-summary.js +2 -2
- package/cli/audit/base-delta.js +89 -0
- package/cli/audit/coverage.js +27 -3
- package/cli/audit/gate-verdict.js +11 -0
- package/cli/audit/hook-shape-validator.js +28 -2
- package/cli/audit/inconclusive.js +36 -0
- package/cli/audit/lexicon-predicates.js +41 -10
- package/cli/audit/line-budgets.js +9 -1
- package/cli/audit/ratchet.js +45 -6
- package/cli/audit/rule-overrides.js +18 -4
- package/cli/audit/rule-revisions.js +25 -2
- package/cli/audit/rules-registry.js +110 -0
- package/cli/audit/rules.js +57 -13
- package/cli/audit/staged-delta.js +20 -11
- package/cli/audit/status-file.js +1 -1
- package/cli/audit/suppressions.js +47 -14
- package/cli/audit/tailwind-unscanned.js +328 -0
- package/cli/audit/types.d.ts +15 -1
- package/cli/audit-scan.js +31 -8
- package/cli/build/detector.js +41 -0
- package/cli/build/executor.js +7 -3
- package/cli/build/jest-config-modules.js +76 -0
- package/cli/build/pnpm-store-path.js +60 -0
- package/cli/build/reporter.js +5 -3
- package/cli/build/types.d.ts +2 -0
- package/cli/build.js +23 -3
- package/cli/check-report.js +52 -0
- package/cli/ci-checks.js +132 -0
- package/cli/ci-command.js +123 -0
- package/cli/ci-gh.js +38 -0
- package/cli/ci-protection.js +101 -0
- package/cli/cli-args.js +2 -0
- package/cli/cli-root-flags.js +17 -0
- package/cli/cli-root.js +246 -0
- package/cli/commands/cmd-audit.js +4 -1
- package/cli/commands/cmd-batch.js +4 -1
- package/cli/commands/cmd-check.js +12 -9
- package/cli/commands/cmd-mutate.js +193 -0
- package/cli/commands/cmd-router.js +49 -3
- package/cli/commands/cmd-run.js +125 -0
- package/cli/commands/cmd-scratch.js +70 -0
- package/cli/commands/cmd-staged-delta.js +50 -5
- package/cli/commands/cmd-worktree.js +214 -0
- package/cli/commands/unknown-flags.js +5 -3
- package/cli/commands/worktree-fixture.js +70 -0
- package/cli/commands-schema-badge.js +15 -0
- package/cli/commands-schema-ci.js +20 -0
- package/cli/commands-schema-commit.js +7 -4
- package/cli/commands-schema-edit.js +5 -5
- package/cli/commands-schema-host.js +5 -4
- package/cli/commands-schema-ops.js +11 -21
- package/cli/commands-schema-search.js +22 -7
- package/cli/commands-schema-team.js +87 -0
- package/cli/commands-schema-verify.js +16 -13
- package/cli/commands-schema-workspace.js +48 -0
- package/cli/commands-schema-wrappers.js +15 -10
- package/cli/commands-schema.js +3 -1
- package/cli/commit/commit-args.js +11 -7
- package/cli/commit/commit-facts.js +26 -10
- package/cli/commit/commit-git.js +21 -0
- package/cli/commit/commit-message.js +23 -8
- package/cli/commit/commit-record.js +6 -16
- package/cli/commit/commit-run.js +15 -6
- package/cli/commit/commit-validate.js +27 -3
- package/cli/config/index.js +2 -0
- package/cli/docs-check/changelog-fold.js +98 -0
- package/cli/docs-check/changelog-fragments.js +79 -0
- package/cli/docs-check/changelog-run.js +80 -0
- package/cli/docs-check/command-tree.js +5 -2
- package/cli/docs-check/resolve.js +4 -3
- package/cli/doctor/check-env.js +27 -1
- package/cli/doctor/check-kit.js +106 -0
- package/cli/doctor/check-mcp.js +19 -13
- package/cli/doctor/doctor-cli.js +20 -10
- package/cli/doctor/jest-deps.js +91 -0
- package/cli/doctor/mcp-groups.js +86 -0
- package/cli/doctor/proc-scan.js +22 -4
- package/cli/edit-guardrails.js +20 -10
- package/cli/generator-compact.js +3 -4
- package/cli/generator-templates/archetypes/layout-archetypes.js +1 -0
- package/cli/generator-templates/atom-import.js +31 -0
- package/cli/generator-templates/compact.js +7 -5
- package/cli/generator-templates/naming.js +16 -0
- package/cli/generator-templates/react.js +5 -4
- package/cli/generator-templates/svelte.js +6 -5
- package/cli/generator-templates/vue.js +8 -6
- package/cli/generator-templates.d.ts +4 -0
- package/cli/generator-templates.js +1 -1
- package/cli/generator.js +18 -2
- package/cli/git-file-listing.js +7 -4
- package/cli/help.js +7 -2
- package/cli/hooks/claude-pre-tool.js +68 -20
- package/cli/hooks/declared-identity.js +136 -0
- package/cli/hooks/dispatch-identity.js +49 -4
- package/cli/hooks/guard-allow.js +84 -0
- package/cli/hooks/guard-args.js +13 -0
- package/cli/hooks/guard-cases.js +3 -1
- package/cli/hooks/guard-config.js +5 -2
- package/cli/hooks/guard-find.js +38 -0
- package/cli/hooks/guard-invocation.js +1 -1
- package/cli/hooks/guard-paths.js +14 -6
- package/cli/hooks/guard-refine.js +131 -0
- package/cli/hooks/guard-rules-nudge.js +42 -3
- package/cli/hooks/guard-rules-reads.js +3 -8
- package/cli/hooks/guard-rules-shell.js +6 -16
- package/cli/hooks/guard-rules.js +11 -12
- package/cli/hooks/guard-shapes.js +104 -0
- package/cli/hooks/guard-test-runners.js +47 -0
- package/cli/hooks/native-edit-lock.js +31 -10
- package/cli/hooks/run-hook.js +10 -0
- package/cli/hooks/session-identity.js +25 -25
- package/cli/host-protocol.js +12 -9
- package/cli/index-admission.js +1 -1
- package/cli/index-freshness.js +2 -0
- package/cli/index-seed-parent.js +71 -0
- package/cli/index-seed.js +185 -0
- package/cli/index.js +20 -1
- package/cli/job-line-run.js +99 -0
- package/cli/job-line-spec-helpers.js +73 -0
- package/cli/job-line-text.js +72 -0
- package/cli/job-line-tickets.js +125 -0
- package/cli/job-line-turns.js +131 -0
- package/cli/job-line-wiring-helpers.js +75 -0
- package/cli/job-line.js +217 -0
- package/cli/job-record.js +67 -0
- package/cli/lattice-directives.js +10 -0
- package/cli/license.js +28 -3
- package/cli/lint-error.js +36 -0
- package/cli/lint-micro.js +91 -0
- package/cli/lint-report.js +77 -0
- package/cli/lint-scope.js +67 -0
- package/cli/literal-replace.js +35 -2
- package/cli/main.js +15 -0
- package/cli/mcp/antigravity.js +2 -1
- package/cli/mcp/help.js +3 -3
- package/cli/mcp/manifests-team.js +25 -5
- package/cli/mcp/manifests.js +10 -4
- package/cli/mcp/tools-audit.js +7 -3
- package/cli/mcp/tools-q.js +29 -4
- package/cli/mcp/tools-read.js +5 -0
- package/cli/mcp/tools-team-feed.js +12 -23
- package/cli/mcp/tools-team-task-extras.js +41 -0
- package/cli/mcp/tools-team-tasks.js +54 -15
- package/cli/mcp/tools-verify.js +47 -5
- package/cli/mutate-edit.js +190 -0
- package/cli/patch-block-blame.js +61 -0
- package/cli/patcher-cli.js +36 -8
- package/cli/patcher.js +22 -3
- package/cli/php/php-analyzer.js +241 -0
- package/cli/php/php-callables.js +170 -0
- package/cli/php/php-catch.js +88 -0
- package/cli/php/php-comments.js +58 -0
- package/cli/php/php-control-flow.js +345 -0
- package/cli/php/php-dangerous.js +138 -0
- package/cli/php/php-facts-vocab.js +52 -0
- package/cli/php/php-facts.js +399 -0
- package/cli/php/php-logging.js +101 -0
- package/cli/php/php-metrics.js +131 -0
- package/cli/php/php-names.js +151 -0
- package/cli/php/php-outline.js +42 -0
- package/cli/php/php-parse.js +119 -0
- package/cli/php/php-registrations.js +176 -0
- package/cli/php/php-symbols.js +209 -0
- package/cli/php/php-taint.js +447 -0
- package/cli/php/php-walk.js +189 -0
- package/cli/php/wp-auth-filters.js +170 -0
- package/cli/php/wp-detect.js +197 -0
- package/cli/php/wp-fail-open.js +69 -0
- package/cli/php/wp-index-files.js +219 -0
- package/cli/php/wp-nonce.js +90 -0
- package/cli/php/wp-output.js +138 -0
- package/cli/php/wp-project-index.js +333 -0
- package/cli/php/wp-registration.js +90 -0
- package/cli/php/wp-request-array.js +84 -0
- package/cli/php/wp-request-flow.js +432 -0
- package/cli/php/wp-rest-endpoints.js +338 -0
- package/cli/php/wp-route-bounds.js +173 -0
- package/cli/php/wp-route-rules.js +150 -0
- package/cli/php/wp-route-shadow.js +69 -0
- package/cli/php/wp-sql-classify.js +418 -0
- package/cli/php/wp-sql-lookup.js +245 -0
- package/cli/php/wp-sql-members.js +135 -0
- package/cli/php/wp-sql.js +105 -0
- package/cli/project-detector.js +11 -21
- package/cli/reader-cli.js +3 -0
- package/cli/reader-logic.js +2 -0
- package/cli/reader-outline-kind.js +15 -0
- package/cli/reader.js +13 -8
- package/cli/scaffold-frameworks.js +25 -8
- package/cli/scratch-dir.js +60 -0
- package/cli/search-args.js +14 -7
- package/cli/search-ast.js +7 -2
- package/cli/search-commands-literal.js +92 -48
- package/cli/search-commands.js +8 -43
- package/cli/search-literal-argv.js +34 -0
- package/cli/search-literal-files.js +23 -9
- package/cli/search-literal-view.js +55 -0
- package/cli/search-literal.js +81 -18
- package/cli/search-matched-text.js +22 -0
- package/cli/search-output.js +4 -1
- package/cli/search-q-fallback.js +13 -0
- package/cli/search-query-view.js +11 -7
- package/cli/search-regex-guard.js +86 -0
- package/cli/search-replace-blocks.js +36 -7
- package/cli/search-scan.js +12 -1
- package/cli/search-scope-check.js +28 -0
- package/cli/search-sync.js +6 -1
- package/cli/search.js +36 -38
- package/cli/slot-files.js +103 -0
- package/cli/status-command.js +18 -11
- package/cli/symbol-locator.js +4 -3
- package/cli/team/agent-identity.js +33 -0
- package/cli/team/audit-run-identity.js +8 -11
- package/cli/team/audit-run-invocations.js +2 -1
- package/cli/team/board-claim-record.js +34 -0
- package/cli/team/board-claims.js +125 -0
- package/cli/team/board-notice.js +47 -0
- package/cli/team/board-resolve.js +144 -0
- package/cli/team/claim-ignore-deps.js +15 -3
- package/cli/team/cli-exit-fixture.js +24 -0
- package/cli/team/coordination-busy.js +20 -1
- package/cli/team/help/benchmark.js +6 -0
- package/cli/team/help/brief.js +6 -0
- package/cli/team/help/card-rows.js +20 -0
- package/cli/team/help/dm.js +6 -0
- package/cli/team/help/feed.js +7 -0
- package/cli/team/help/inbox.js +6 -0
- package/cli/team/help/lock.js +50 -0
- package/cli/team/help/post.js +6 -0
- package/cli/team/help/route.js +6 -0
- package/cli/team/help/shared.js +7 -0
- package/cli/team/help/status.js +6 -0
- package/cli/team/help/task-actions.js +22 -0
- package/cli/team/help/task-options.js +44 -0
- package/cli/team/help/task.js +15 -0
- package/cli/team/help/team-card.js +18 -0
- package/cli/team/help/tokens.js +6 -0
- package/cli/team/help/train.js +6 -0
- package/cli/team/help/triage.js +6 -0
- package/cli/team/help/unlock.js +6 -0
- package/cli/team/identity-classification.js +205 -0
- package/cli/team/lease-activity.js +40 -18
- package/cli/team/lease-claim-roots.js +36 -0
- package/cli/team/lease-grace.js +103 -0
- package/cli/team/lock-hints.js +14 -0
- package/cli/team/lock-lines.js +57 -0
- package/cli/team/lock-targets.js +97 -0
- package/cli/team/task-add-from-file.js +157 -0
- package/cli/team/task-brief-store.js +89 -0
- package/cli/team/task-brief-view.js +95 -0
- package/cli/team/task-claim-age.js +103 -0
- package/cli/team/task-completion-output.js +3 -0
- package/cli/team/task-detail-sections.js +13 -1
- package/cli/team/task-file-rows.js +255 -0
- package/cli/team/task-kind.js +28 -0
- package/cli/team/task-list-view.js +32 -11
- package/cli/team/task-ref.js +16 -0
- package/cli/team/task-update-status.js +32 -0
- package/cli/team/task-yaml-lite.js +228 -0
- package/cli/team/team-board-target.js +52 -0
- package/cli/team/team-commands-board.js +17 -24
- package/cli/team/team-commands-lock-views.js +17 -5
- package/cli/team/team-commands-lock.js +103 -85
- package/cli/team/team-commands-repo.js +4 -1
- package/cli/team/team-commands-task.js +97 -22
- package/cli/team/team-commands-tokens.js +2 -1
- package/cli/team/team-commands-vds.js +10 -2
- package/cli/team/team-commands.js +57 -25
- package/cli/team/team-db-agents.js +5 -1
- package/cli/team/team-db-feed.js +31 -1
- package/cli/team/team-db-locks.js +3 -0
- package/cli/team/team-db-task-helpers.js +9 -2
- package/cli/team/team-db-tasks.js +12 -5
- package/cli/team/team-dependents-gate.js +46 -0
- package/cli/team/team-dispatch-prompts.js +1 -1
- package/cli/team/team-feed-view.js +71 -0
- package/cli/team/team-flag-values.js +56 -0
- package/cli/team/team-flags.js +39 -83
- package/cli/team/team-format.js +72 -135
- package/cli/team/team-help-golden.json +311 -0
- package/cli/team/team-schema.js +9 -0
- package/cli/team/team-status-view.js +50 -0
- package/cli/team/team-task-guard.js +5 -3
- package/cli/team/team-task-handoff.js +17 -4
- package/cli/team/team-tokens-epic.js +190 -0
- package/cli/team/team-triage-verify.js +42 -4
- package/cli/team/team-triage.js +6 -2
- package/cli/team/team-view-flags.js +39 -0
- package/cli/team/usage-render.js +2 -2
- package/cli/team/write-lock-guard.js +6 -3
- package/cli/terminal.js +16 -0
- package/cli/test-args.js +46 -0
- package/cli/test-audit.js +34 -18
- package/cli/test-changes.js +26 -3
- package/cli/test-command.js +26 -4
- package/cli/test-graph-names.js +58 -0
- package/cli/test-graph-open.js +2 -1
- package/cli/test-graph.js +31 -55
- package/cli/test-kit-fixture.js +43 -0
- package/cli/test-lanes.js +10 -5
- package/cli/test-meta.js +35 -0
- package/cli/test-open-ok.js +55 -0
- package/cli/test-output.js +30 -1
- package/cli/test-project-runner.js +119 -0
- package/cli/test-report.js +74 -14
- package/cli/test-run-size.js +22 -0
- package/cli/test-run.js +70 -19
- package/cli/test-scope.js +13 -5
- package/cli/test-select.js +70 -12
- package/cli/test-slots.js +81 -107
- package/cli/test-workers.js +28 -6
- package/cli/typecheck-audit.js +19 -5
- package/cli/verify-audit-step.js +45 -0
- package/cli/verify-changed.js +12 -1
- package/cli/verify-harness.js +83 -0
- package/cli/verify-helpers.js +15 -1
- package/cli/verify-lint.js +112 -131
- package/cli/verify-php-lint.js +102 -0
- package/cli/verify-report.js +47 -1
- package/cli/verify-steps.js +8 -4
- package/cli/verify.js +32 -45
- package/cli/wait-command.js +65 -10
- package/cli/wait-notice.js +27 -0
- package/cli/wait-post.js +45 -0
- package/cli/worktree-base.js +49 -0
- package/cli/worktree-git.js +94 -0
- package/cli/worktree-index.js +42 -0
- package/cli/worktree-modules.js +75 -0
- package/package.json +7 -3
package/AGENTS.md
CHANGED
|
@@ -8,16 +8,31 @@
|
|
|
8
8
|
## ⚡ Quick Agent Reflex Table (Token-Bounded Commands)
|
|
9
9
|
| Standard Reflex | `chemx` Equivalent | Token & Architecture Advantage |
|
|
10
10
|
| :--- | :--- | :--- |
|
|
11
|
-
| `grep -rn "pattern" .` | `chemx q -g "pattern"` or `chemx q -g "pattern" -
|
|
11
|
+
| `grep -rn "pattern" .` | `chemx q -g "pattern" --bare` or `chemx q -g "pattern" -C 3 --bare` | Fixed-string search matching rg token-for-token (57 chars / 14 tokens per hit); add `-C <N>` to inspect surrounding code in a single turn without a second read call. `a\|b` is literal text: add `--regex` for alternation (RE2 subset). `-l` prints the matching files once, `-o --unique` prints the distinct matched text (a `--gs-[a-z-]+` token list in one call), paths after the pattern scope it (`--glob <path>` is `--dir`; `--max <N>` is `-n`), dot-directories such as `.github` and `.claude` are searched (only git-ignored paths are left out; `--hidden` adds them), and `chemx q <text>` with no symbol hit runs this search for you. See [docs/search.md](docs/search.md). |
|
|
12
|
+
| `sed -n 'N,Mp' <file>` | `chemx read <file>:N-M --bare` | Verbatim slice matching sed byte-for-byte; 0 header, box or line number tokens. |
|
|
12
13
|
| `git diff` | `chemx d` | Zero-context (`-U0`), auto-collapses to `--stat` if > 80 lines, micro-syncs index. |
|
|
14
|
+
| Editing `docs/CHANGELOG.md` for your change | write `docs/changelog.d/<ticket>.md` (first line `### Fixed`, `### Added` or a title with the ticket id, then bullets); `chemx changelog --check` validates, `chemx changelog` folds the fragments into Unreleased and deletes them | One file per change, so parallel tickets never edit the same lines. Never edit `docs/CHANGELOG.md` directly. See [docs/changelog.d/README.md](docs/changelog.d/README.md). |
|
|
13
15
|
| `git log` | `chemx log -n 5` | One-line commit history (5 commits measured about 140 tokens on 2026-10-09, roughly 28 per commit). |
|
|
14
16
|
| `find . -name "*.vue"` | `chemx f "*.vue"` | Strictly filters by `.gitignore` and ignored dirs. |
|
|
15
17
|
| `cat package.json` | `chemx p -s` / `chemx p <script>` | Single script or dep extraction (`p -s` listing 15 scripts measured about 240 tokens on 2026-10-09). |
|
|
16
18
|
| `cat <data.json>` | `chemx j <file.json>` | Structural schema shape only; collapses repeating arrays. |
|
|
17
19
|
| `cat <file>` / `head <file>` | `chemx read <file> --outline` | AST signatures only (outline of `cli/reader.js` measured about 300 tokens against about 4,800 for the file, 2026-10-09; other targets in `benchmarks/README.md`). |
|
|
18
20
|
| `npm test` / `tsc` | `chemx test` / `chemx verify` | Prints a summary on success and the failing diffs on failure. |
|
|
21
|
+
| `eslint <paths>` / `npm run lint` | `chemx lint <paths> [--fix]` | ESLint grouped by file, plus the `chemx check` hazards of those paths in the same pass (inline booleans, dispatch switches), so there is no second pass. With no ESLint config or package.json (a WordPress plugin dir) it says so in one line and runs `chemx check` on the files instead. |
|
|
22
|
+
| `node tests/run-tests.mjs` (a repo's own harness runner, e.g. the WordPress plugins) | `chemx test` / `chemx test tests/x.test.mjs` | One file through a generated copy of the runner, failures only and one summary line, one run at a time on the machine (shared database). `.chemxrc` `guard.allow` (or its alias `test.runners`) lists a raw runner the guard should leave alone. |
|
|
19
23
|
| `cat .chemx/history.json` | `chemx audit --feed=scopes` / MCP `audit_feed` | Recorded audit results as flat rows (per scope, per run or per pillar) without re-running the audit; `--json` for every row. |
|
|
20
24
|
| Auditing each package by hand | `chemx audit --each=submodules` | One scoped audit per submodule or workspace package, then a one-line-per-package summary. |
|
|
25
|
+
| `cd ../other-repo && <command>` / `git -C ../other-repo log` | `chemx <command> --root=../other-repo` (any command: `log`, `d`, `status`, `q -g`, `read`, `team ...`; or `CHEMX_PROJECT_ROOT=<dir>`) | Runs as if started in that project, from any cwd; the shell never changes directory. `chemx read /abs/path/in/another/chemx/project` picks that project's index too. |
|
|
26
|
+
| `cd ../other-repo && chemx team task show 12` | `chemx team task show ../other-repo#12` or `--board=../other-repo` | A task number belongs to a board; this names it. A `team task list`/`show` footer says when you hold a claim on a different board. |
|
|
27
|
+
| `gh pr checks <n>`, `gh pr view <n> --json mergeStateStatus`, `sleep 120; gh pr checks <n>` | `chemx ci <n>` or `chemx ci <n> --wait` | One line per check (state, duration, name) and one merge-state line; exit 0 all passed, 1 a check failed, 3 pending or no checks reported yet, 2 could not read. `--wait` polls every 30 s and prints only what changed. `chemx ci --protection` warns (exit 1) when the default branch requires no status checks, because `gh pr merge --auto` then merges at once. See [docs/ci.md](docs/ci.md). |
|
|
28
|
+
| Adding up task costs by hand | `chemx team tokens --epic <id>` | Prompt, completion and cached tokens, cost and wall time over an epic's children (recursively), one line each and a total; a child that never recorded usage is a dash and counted unknown, not zero. See [docs/team-tokens.md](docs/team-tokens.md). |
|
|
29
|
+
| A shell loop of `team task add` to file an epic and its children | `chemx task add --from tasks.yaml` (`--dry-run` first) | Rows with `ref`, `parent_ref` and `deps` by ref, one transaction, one `#<id> added` line per row and a `refs:` map; any wrong row writes nothing. `chemx task` is `chemx team task`. |
|
|
30
|
+
| A 7 KB design contract in `--desc`, or in a `.md` file agents cat | `chemx team brief <id> --file design.md` (read: `task show <id> --full`) | The brief is stored on the task apart from its description; `task show` prints only its length. |
|
|
31
|
+
| `sleep`/poll loops for a peer's report | `chemx wait --post-matching "K9 merged" --from=@sonnet-k9` | Resolves when a feed post (any case, no lock events) matches; exit 0 / 2 on timeout. |
|
|
32
|
+
| `chemx test ... &` then a `sleep` or `kill -0` loop until it ends | `chemx test ... & chemx wait --job=$!` | Resolves when that chemx process has ended and exits with the job's own code (0 pass, 1 fail, 3 inconclusive); 2 on timeout. See [docs/test-lanes.md](docs/test-lanes.md). |
|
|
33
|
+
| A one-off script in a scratchpad other agents also write to (`/tmp/one.mjs`) | `chemx scratch one.mjs` (the directory: `chemx scratch`) | Prints `<project>/.chemx/scratch/<handle>/one.mjs` and makes the folder: one directory per handle, git-ignored, so another agent's `one.mjs` never replaces yours. See [docs/agent-workspace.md](docs/agent-workspace.md). |
|
|
34
|
+
| `git worktree add ../w -b fix/x origin/main` then a hand-made `node_modules` symlink; later `git worktree remove` and `git branch -d` | `chemx worktree add <path> -b <branch> [--link-modules\|--install]` / `chemx worktree remove <path>` | Branches from the remote default branch, gives the worktree a real `node_modules` of symlinks into the main checkout's (so an install in it never writes into the main checkout) or installs, and prints the path first plus the reminder that `chemx team` runs from the main checkout or with `--board`. `remove` deletes the branch when it is merged, squash merges included, into the default branch or the branch it was cut from (recorded at `add`), and refuses an unmerged one without `--force`. See [docs/agent-workspace.md](docs/agent-workspace.md). |
|
|
35
|
+
| `cp f f.bak; sed -i ... f; <test>; cp -i f.bak f` (a mutation check) | `chemx mutate f --apply 's/a/b/' --then "<test>"` | Applies a sed substitution or a diff file, runs the command like `chemx run`, always restores the original byte for byte (a copy sits in your scratch directory; `--keep` retains it) and exits with the command's code (125 = chemx stopped, e.g. nothing changed or a lease is held). See [docs/agent-workspace.md](docs/agent-workspace.md). |
|
|
21
36
|
| Multiple CLI actions | `chemx do "<cmd1>" "<cmd2>"` | Executes sequentially in a single warm Node process. |
|
|
22
37
|
| Multiple MCP tool calls | `chemx({ commands: [...] })` | Executes multiple sub-operations in a single agent turn. |
|
|
23
38
|
|
|
@@ -111,7 +126,7 @@ Use same-name shorthand across all languages; eliminate redundant `key: key` dup
|
|
|
111
126
|
### L. Database-First Swarm Coordination & The Zero-Markdown-Monolith Directive
|
|
112
127
|
- **Database as the Master Source of Truth**: The multi-agent orchestrator and subagents MUST record all milestones, task assignments, status changes, file locks, and architectural contracts directly in `.chemx/index.db` (`agent_tasks`, `file_lock_queue`, `agent_feed`) rather than creating monolithic, multi-thousand-token markdown specification files.
|
|
113
128
|
- **Prohibition on Monolithic Project Markdown Parsing**: Subagents MUST NOT load giant monolithic `PROJECT.md` or status markdown files into context. Subagents query only their assigned task slice and dependencies via `pnpm chemx team task list --agent=@my-id` or MCP `chemx_team_task`.
|
|
114
|
-
- **Leases and handoff**: A file lock is a 5-minute lease that any chemx command run as the holder extends and that lapses after 5 minutes without activity (`docs/team-locks.md`). The commit hook refuses staged files another handle leases live. `chemx team task handoff <id> @<to> --as=@<from>` passes a task on (assignee or task creator only); it keeps the task `in_progress`, records from/to/by in the feed, and does not move file locks, so the old holder releases and the new owner acquires.
|
|
129
|
+
- **Leases and handoff**: A file lock is a 5-minute lease that any chemx command run as the holder extends (from a git worktree too, once the holder has claimed a task) and that lapses after 5 minutes without activity; a lapse of at most 10 minutes is undone by the holder's next chemx command while the task is in_progress and nobody took or queued for the file. Wrap a long non-chemx command (a plugin's test runner, a script) as `chemx run -- <command>` and it renews your leases every minute while it runs (`docs/team-locks.md`). The commit hook refuses staged files another handle leases live. Claude's native `Edit`/`Write` are refused on a file another handle leases, but a lease held by a handle you named in an earlier chemx call (`--as`, `CHEMX_AGENT_ID`) this session is yours: name your handle on your lock and claim calls (`docs/hooks.md`, "Native edits and file locks"). `chemx team task handoff <id> @<to> --as=@<from>` passes a task on (assignee or task creator only); it keeps the task `in_progress`, records from/to/by in the feed, moves the board pointer to the new owner (their next `chemx commit` finds the task's board without `--board`), and does not move file locks, so the old holder releases and the new owner acquires. `chemx team lock a.js b.js c.js --as=@<you>` takes several paths in one call and answers one line per path (`granted until HH:MM`, `queued behind @x`, or `refused: <reason>`); an absolute path in a sibling repo works from any directory under the board root.
|
|
115
130
|
- **Project Markdown as a Generated Read-Only Projection**: If a human-readable `PROJECT.md` is requested, it MUST be generated on-demand as a read-only projection compiled from the database state via `pnpm chemx team status` or export tools.
|
|
116
131
|
|
|
117
132
|
### M. The Master MCP Tool Protocol & Single-Permission Dispatch
|
|
@@ -150,6 +165,8 @@ Use same-name shorthand across all languages; eliminate redundant `key: key` dup
|
|
|
150
165
|
// Preview first: dryRun returns the unified diff and writes nothing (CLI: --dry-run or -n)
|
|
151
166
|
chemx({ action: 'patch', params: { path: 'src/...', target: 'oldCode', replacement: 'newCode', dryRun: true } });
|
|
152
167
|
chemx({ action: 'patch', params: { path: 'src/...', target: 'oldCode', replacement: 'newCode' } });
|
|
168
|
+
// Several edits to one file go in ONE call (CLI: SEARCH/REPLACE blocks in a heredoc; API: blocks: [{ search, replace }]): all-or-nothing,
|
|
169
|
+
// and a block that does not match or trips a guard fails the call as "Block 2 of 3: ..." with the file untouched
|
|
153
170
|
// Removing a top-level declaration (renames included) is refused unless named (CLI: --allow-remove=oldName)
|
|
154
171
|
chemx({ action: 'patch', params: { path: 'src/...', target: 'oldName', replacement: 'newName', allowRemoved: ['oldName'] } });
|
|
155
172
|
// write creates; replacing an existing file needs overwrite (CLI: --overwrite)
|
|
@@ -175,7 +192,7 @@ Use same-name shorthand across all languages; eliminate redundant `key: key` dup
|
|
|
175
192
|
### N. Database-First Navigation, Symbol Connections & The Zero-Native-File-Dump Directive
|
|
176
193
|
- **Database Master Index First**: Before inspecting or modifying any file, AI agents MUST query the SQLite database (`.chemx/index.db`) or the AST Query Machine (`chemx({ action: 'team', params: { action: 'list' } })`, `chemx({ action: 'q', params: { query } })`) rather than exploring the filesystem with broad find or ripgrep commands.
|
|
177
194
|
- **Targeted Symbol-Only Extraction**: AI agents MUST NEVER dump entire source files into context. When an agent needs to inspect a function, hook, interface, or class, it MUST request only that specific symbol: `chemx({ action: 'read', params: { path, symbol: '<name>' } })`.
|
|
178
|
-
- **Read Window Auto-Outline**: Files longer than the chemx read window (100 lines by default), read without a target symbol or slice, automatically render an AST outline to prevent token exhaustion and host buffer spillovers (such as IDE `output.txt` dumps). The read window is a tool budget, not an architecture rule; file size is governed by 1.A.
|
|
195
|
+
- **Read Window Auto-Outline**: Files longer than the chemx read window (100 lines by default), read without a target symbol or slice, automatically render an AST outline to prevent token exhaustion and host buffer spillovers (such as IDE `output.txt` dumps). A file with nothing to outline (yaml, json, txt, shell, or Markdown without headings) reads as its first window of text with the next range named, not an empty outline. The read window is a tool budget, not an architecture rule; file size is governed by 1.A.
|
|
179
196
|
- **Enrich Mode (Component Capsules Only)**: When an agent needs both exported signatures AND logic flow for a component capsule, use `enrich: true` with `outline: true`: one response card holds the outline and a compacted logic skeleton, optionally with a forward trace or reverse caller chain (`traceSymbol`, `backtraceSymbol`). The skeleton generator is tuned for components; on procedural modules (services, CLI code, utilities) enrich saves only a few percent over reading the whole file, so use plain `outline: true` or `symbol: '<name>'` there.
|
|
180
197
|
- **Symbol Connection Graph Over Multi-File Dumps**: Rather than reading multiple files to understand imports and consumers, agents MUST request symbol connections: `chemx({ action: 'read', params: { path, symbol: '<name>', connections: true } })` or `chemx({ action: 'q', params: { query, connections: true } })`. This returns the definition, imported dependencies, and caller references. Size grows with the symbol: `q <symbol> --connections` measured about 140 tokens and `read --symbol --connections` about 2,400 for `readTokenOptimized` on 2026-10-09.
|
|
181
198
|
- **Token-First File Inspection & Native Fallback**: AI agents should prioritize Chemical X AST readers (`chemx read --outline` or `chemx read --symbol`) to conserve tokens. Dumping raw files burns thousands of tokens and causes premature context exhaustion. If an agent requires exact raw verification or unformatted bytes, native tools (`view_file`, `grep`, `cat`) are permitted as an intentional fallback.
|
|
@@ -251,7 +268,7 @@ Use same-name shorthand across all languages; eliminate redundant `key: key` dup
|
|
|
251
268
|
### A. Named Conditions & Two-Stage Composition
|
|
252
269
|
> The `if` never asks a question. The question is asked and named before the branch, and the `if` reads the answer.
|
|
253
270
|
|
|
254
|
-
- Every conditional test is a named boolean: an identifier, its negation,
|
|
271
|
+
- Every conditional test is a named boolean: an identifier, its negation, a bare property read (`sale.ok`, `items.length`), or a call whose name states an assertion or a predicate (3.F: `hasItems(cart)`, `map.has(key)`, `fs.existsSync(p)`). A raw comparison (`x === y`, `n >= 5`), inline compound logic (`a && b`), or a call whose name states no predicate (`RE.test(s)`, `user.getName()`) inside the test is a violation.
|
|
255
272
|
- Naming is the point, not the ceremony: it forces the question inline code hides (why `<= 0` and not `=== -1`?), and an edit to a named condition changes its definition without touching the branch.
|
|
256
273
|
- **Stage 1** names each atomic concept. **Stage 2** names the decision, only when 2 or more concepts compose; a single comparison gets one name and no wrapper. Guard with early returns.
|
|
257
274
|
```typescript
|
|
@@ -276,6 +293,7 @@ Use same-name shorthand across all languages; eliminate redundant `key: key` dup
|
|
|
276
293
|
1. **Structural primitives** (`lib/is/`: `value`, `collection`, `text`, `fs`, `type`): subject-agnostic and finite (`isAbsent`, `hasItems`, `isNonEmptyString`, `pathExists`). They import nothing, and the set does not grow. Every primitive is a TypeScript type predicate (`(x: unknown): x is string`), never a plain `boolean`, or callers lose narrowing.
|
|
277
294
|
2. **Domain vocabulary** (`<subsystem>/<domain>-predicates.<ext>`, colocated with the subsystem it describes): built from Layer 1. A condition earns a domain predicate at its 2nd use; a threshold duplicated across files (`score >= 90`) silently disagrees the day one copy changes, so name it once (`isGradeA`). Promote threshold literals in predicate bodies to named constants (`score >= GRADE_A_THRESHOLD`).
|
|
278
295
|
3. **Decisions**: single-use composites declared at the call site per 3.A. Never extract them: extracting every condition is *indirection masquerading as modularity* (1.A).
|
|
296
|
+
- A bare property read (`sale.ok`, `items.length`) or a predicate-method call (`map.has(key)`, `fs.existsSync(p)`) is already a named condition; 3.A does not ask for a const alias of it.
|
|
279
297
|
- Before naming a new predicate, search for an existing one: `chemx q -g "<likely words>"` for the literal names, plus `chemx({ action: 'q', params: { query: '<concept>', hybrid: true } })` for spelling variants. Neither finds pure synonyms (`hasNoItems` vs `isEmpty`), so also scan the predicates module of the domain. A synonym beside an existing predicate is lexicon rot.
|
|
280
298
|
- Repeated filter conditions are Layer 2 candidates: extract the predicate, compose a named higher-order filter, and keep derivations declarative:
|
|
281
299
|
```typescript
|
|
@@ -416,6 +434,7 @@ Raw inline `style="..."` attributes are strictly prohibited. Visual styling flow
|
|
|
416
434
|
- Long strings of utility shorthand recreate the exact cognitive noise and AI context degradation of inline styles.
|
|
417
435
|
- Decouple visual styling into scoped classes via `@apply` or centralized SCSS mixins (`@include glass;`).
|
|
418
436
|
- Templates must read like a clean, semantic outline rather than an unreadable wall of styling shorthand.
|
|
437
|
+
- Class strings must stay where Tailwind looks (`TAILWIND_UNSCANNED_CLASS_STRING`): in a Tailwind v3 project, a string of 3 or more utility classes in a `.js`, `.ts`, `.mjs` or `.cjs` file that no `content` glob of `tailwind.config` matches is purged from production CSS. Move it into a template or a file the globs scan, or add the file pattern to `content`. The rule reads the config as text and stays silent for Tailwind v4 or a config whose `content` list is not literal.
|
|
419
438
|
|
|
420
439
|
### C. Wrapper Atoms & Explicit Slot Forwarding
|
|
421
440
|
- Never use dynamic slot iteration with `v-for="(_, slot) in $slots"` in wrapper components.
|
|
@@ -524,9 +543,10 @@ Raw inline `style="..."` attributes are strictly prohibited. Visual styling flow
|
|
|
524
543
|
- Strictly prohibit executing verbose raw `npm test` or `pnpm test` in the shell: hundreds of passing test markers pollute context. If tests pass, agents get a short green summary; if tests fail, they get the failing test names, assertion messages, and diffs.
|
|
525
544
|
- **Test loop, in order of cost** (details: `docs/test-lanes.md`):
|
|
526
545
|
1. `chemx test --changed --depth=3`: only the specs within 3 import hops of your change. Fastest, and NOT proof: the report lists how many deeper specs were skipped.
|
|
527
|
-
2. `chemx test --changed`: every spec the import graph says your change can reach, fast lane only. Slow-lane specs that it reaches are named as not run.
|
|
528
|
-
3. `chemx test
|
|
529
|
-
4. `chemx test
|
|
546
|
+
2. `chemx test --changed`: every spec the import graph says your change can reach, plus the meta specs that scan the whole repo (`test-lanes.json` `meta`), fast lane only. On a clean tree it compares with the merge base of origin/main (or origin/master) and prints the base it chose. Slow-lane specs that it reaches are named as not run. A line `widened by cli/x.js (computed import without open-ok): +N specs` names a file whose computed `import()` put N specs into the run; declare a real entry point with `// chemx: open-ok` or an `openImports` rule in `test-lanes.json` instead of paying for it.
|
|
547
|
+
3. `chemx test --changed --all`: the same affected set, both lanes. CI runs both lanes, so run this (not step 2) before you push.
|
|
548
|
+
4. `chemx test`: the whole fast lane. `chemx test --all` adds the slow lane (specs that spawn the CLI or MCP server, stress and timing specs); that is what CI's `npm test` runs. `chemx test --slow` runs only the slow lane.
|
|
549
|
+
5. `chemx test --profile [--all] [--top=<n>]`: per-spec-file wall time, slowest first, with the measuring method printed beside the numbers.
|
|
530
550
|
- Lane membership lives in `test-lanes.json`, not in `package.json`. A new spec is fast unless the manifest (or a `*.slow.spec.js`, `*.perf.spec.js`, `*.e2e.spec.js` file name) says otherwise. A spec file that no test glob collects fails `cli/test-lanes-coverage.spec.js`.
|
|
531
551
|
- A spec that asserts a wall-clock or CPU budget belongs in the slow lane (name it `*.perf.spec.js`), or its margin must hold on a machine running many agents.
|
|
532
552
|
|
|
@@ -568,7 +588,7 @@ Raw inline `style="..."` attributes are strictly prohibited. Visual styling flow
|
|
|
568
588
|
### A. Installed Hooks, Not Prose
|
|
569
589
|
* Install the Claude Code integration with `chemx install-hooks --host=claude [--scope=local|project] [--dry-run]`. It adds the PreToolUse guard (`chemx hook claude-pre-tool`), the PostToolUse check (`claude-post-edit`), the SessionStart card and the statusline. The `.mcp.json` launch, the git pre-commit hook and the CI pin are opt-in: add `--write-mcp`, `--git-hooks` and `--pin-ci`; without them none of those files is written. Run `chemx doctor` when MCP answers look stale; `chemx doctor --fix` repairs hooks and the MCP launch only.
|
|
570
590
|
* Alternatively install the Claude Code plugin (`plugins/claude-code`, marketplace `.claude-plugin/marketplace.json`): the same hooks, the chemical-x MCP server and a short chemx skill, resolving the kit from `CHEMX_KIT`, the project's node_modules or PATH. Use the plugin or install-hooks, not both, or every hook runs twice. The statusline comes only from install-hooks.
|
|
571
|
-
* The guard tokenizes the shell command. Raw test/typecheck/lint/build runners, `git diff`/`git log` and reads of repo source files are routed through chemx. Native Read, Edit, Write and Grep on project text files are warned by default (`nativeFileTools` in `.chemxrc`, or `CHEMX_NATIVE_FILE_TOOLS`): `warn` names the chemx equivalent, `block` denies the call, `allow` is silent.
|
|
591
|
+
* The guard tokenizes the shell command. Raw test/typecheck/lint/build runners, `git diff`/`git log` and reads of repo source files are routed through chemx, but a call is denied only where the chemx call covers its exact shape: `git -C <dir> log`, `git log A..B`, `grep -c`/`-o` and similar get a nudge with the closest chemx form (`chemx log --root <dir>`, `chemx log -n 50 A..B`, `chemx q -g ... -o --unique`), and shapes chemx cannot do (`grep -v`, `find -mtime`, `command -v tool`, `sleep 30`, `git rebase|push|worktree`) run silently. A denied compound command names the denied segment and says the rest did not run. `.chemxrc` `guard.allow` lists command prefixes to leave alone; `guard.plainGit: true` drops the git status/add/commit nudges. Native Read, Edit, Write and Grep on project text files are warned by default (`nativeFileTools` in `.chemxrc`, or `CHEMX_NATIVE_FILE_TOOLS`): `warn` names the chemx equivalent, `block` denies the call, `allow` is silent.
|
|
572
592
|
|
|
573
593
|
### B. Bypass and Friction
|
|
574
594
|
* When chemx truly cannot do the job, append `# chemx-bypass: <reason>` to the Bash command. The bypass and every guard denial are logged to `.chemx/friction.jsonl`.
|
|
@@ -582,17 +602,19 @@ Shared-workspace protocol. Several agents edit this checkout at once, and chemx
|
|
|
582
602
|
|
|
583
603
|
1. Identity: Pass `--as=@<your session name>` on every `chemx team` command, or set `CHEMX_AGENT_ID`. Never act as the default `@agent`.
|
|
584
604
|
2. Before choosing work: Run `chemx status`, `chemx team status`, `chemx team inbox @<you>` and `chemx team task list --status=in_progress` so you know what is claimed and locked.
|
|
585
|
-
3. Claim: Find or create the task (`chemx team task add "<title>" --needs=light|standard|deep`), then run `chemx team task claim <id> --as=@<you>`.
|
|
586
|
-
4. Lock before edit: Run `chemx team lock acquire <file> --as=@<you> --purpose="#<id>"` before the first edit of each file. If another handle holds the lock, do not edit that file: send `chemx team dm @<handle> "<msg>" --as=@<you>` or pick other work.
|
|
587
|
-
5. Edit through chemx: Read with `chemx read <path> --outline
|
|
588
|
-
6.
|
|
589
|
-
7.
|
|
590
|
-
8.
|
|
591
|
-
9.
|
|
592
|
-
10.
|
|
593
|
-
11.
|
|
594
|
-
12.
|
|
595
|
-
13.
|
|
605
|
+
3. Claim: Find or create the task (`chemx team task add "<title>" --needs=light|standard|deep`; `chemx task` is short for `chemx team task`), then run `chemx team task claim <id> --as=@<you>`. Read a task cheaply with `chemx team task show <id> --brief`. An epic with children goes in one file: `chemx task add --from tasks.yaml --dry-run` checks it, the same command without `--dry-run` files it. A long brief is stored with `chemx team brief <id> --file <md>`.
|
|
606
|
+
4. Lock before edit: Run `chemx team lock acquire <file> --as=@<you> --purpose="#<id>"` before the first edit of each file; several files go in one call (`chemx team lock acquire a.js b.js --as=@<you>`, one result line per path). If another handle holds the lock, do not edit that file: send `chemx team dm @<handle> "<msg>" --as=@<you>` or pick other work.
|
|
607
|
+
5. Edit through chemx: Read with `chemx read <path> --outline`, `--symbol=<name>` or a line range (`chemx read <path>:N-M --bare`), search with `chemx q` (symbols) or `chemx q -g "<text>" -C 3 --bare` (text), find files with `chemx f`, and edit with `chemx patch` or `chemx write`. Avoid cat, sed, grep and direct file writes where chemx has an equivalent.
|
|
608
|
+
6. Workspace: Keep one-off files and branches out of the shared tree. A script or scratch file: `chemx scratch <name>` prints a path in your own git-ignored directory (`.chemx/scratch/<handle>/`), so another agent's file of the same name never replaces yours. Work that needs its own branch: `chemx worktree add <path> -b <branch> --link-modules`, then `chemx worktree remove <path>` once the branch has merged (it refuses an unmerged one). Run `chemx team` commands from the main checkout or with `--board=<dir>`, because a worktree can resolve another board. A mutation check: `chemx mutate <file> --apply "s/old/new/" --then "<command>"` runs the command on a changed file and puts the original back byte for byte. A long non-chemx command: `chemx run -- <command>` renews your file leases while it runs.
|
|
609
|
+
7. Test: Run targeted specs with `chemx test <spec files> [-t name]`. Run a full suite only after parallel editors have finished.
|
|
610
|
+
8. Commit: Run `chemx commit <files> -m "<type>(<area>): <summary> (#<id>)"`. It stages and commits only the listed files, runs the repository pre-commit hook and refuses a file under another handle's lease. From a worktree add `--board=<dir>` (the main checkout) so the task id is checked and the commit recorded on the board you claimed on; add `--no-skip-ci` when the commit must run CI (a commit to the default branch otherwise gets `[skip ci]` where the project sets commit.skipCi). Do not use `git add -A`, `git commit -a` or `git stash`; make a branch or worktree only through the Workspace step.
|
|
611
|
+
9. Pull request: Once a pull request is open, read its checks with `chemx ci <pr> --wait` instead of `gh pr checks --watch` or a sleep loop: exit 0 means every check passed, 1 a check failed, 3 still pending or none reported yet. `chemx ci --protection` warns when the default branch requires no status checks, because `gh pr merge --auto` then merges at once. If the project keeps `docs/changelog.d/`, write `docs/changelog.d/<ticket>.md` (a `###` heading line, then bullets citing `#<id>`) and leave `docs/CHANGELOG.md` alone: `chemx changelog --check` validates the fragments and `chemx changelog` folds them into Unreleased.
|
|
612
|
+
10. Wait: Use `chemx wait --task=<id>`, `chemx wait --lock-free=<file>`, `chemx wait --verify-idle`, `chemx wait --job=<pid>` (a backgrounded `chemx test ... & chemx wait --job=$!`; it exits with the job's own code) or `chemx wait --post-matching=<text> [--from=@<handle>]` (until a feed post matches) instead of sleep loops (a plain `sleep` of up to 120 seconds is fine). `--timeout=<30s|5m|2h>` bounds any of them. The result is true as of the last poll only.
|
|
613
|
+
11. Progress: Record progress with `chemx team task comment <id> "<msg>" --as=@<you>` and decisions with `chemx team post "<msg>" --type=decision --task=<id> --as=@<you>`.
|
|
614
|
+
12. Inbox: Run `chemx team inbox @<you>` at every task boundary: after claiming, after committing, and before starting the next task.
|
|
615
|
+
13. Handoff: To pass a task to another agent, run `chemx team task handoff <id> @<to> --as=@<you>`. Only the assignee or creator can hand off.
|
|
616
|
+
14. Finish: Run the package gate (`chemx verify`), commit, release every lock with `chemx team lock release <file> --as=@<you>`, then run `chemx team task done <id> --target=<file> --as=@<you>`. If the gate refuses, mark the task blocked with a reason. Do not use `--force`.
|
|
617
|
+
15. Friction: When a chemx command misbehaves, you bypass it, or a flag is missing, file `chemx team task add "Friction: <what happened>" --needs=light --desc="<exact command and output>"` and keep going.
|
|
596
618
|
|
|
597
619
|
Regenerate with `chemx pillars --protocol --write`; edits inside this block are overwritten.
|
|
598
620
|
<!-- chemx:protocol end -->
|
package/README.md
CHANGED
|
@@ -28,7 +28,7 @@
|
|
|
28
28
|
npm install -g chemx # or: npx chemx <command>
|
|
29
29
|
|
|
30
30
|
# 2. Check the install in your project
|
|
31
|
-
chemx doctor # read-only;
|
|
31
|
+
chemx doctor # read-only; kit commit vs CI pin, stale MCP servers by session, index, hooks, coverage
|
|
32
32
|
|
|
33
33
|
# 3. Wire Claude Code to chemx (writes .claude/settings.json; --dry-run shows the change first)
|
|
34
34
|
chemx install-hooks --host=claude --dry-run
|
|
@@ -180,7 +180,7 @@ chemx build --json
|
|
|
180
180
|
|
|
181
181
|
#### One gate rule
|
|
182
182
|
|
|
183
|
-
The pre-commit hook (`chemx audit --staged-delta`), the ratchet step of `chemx verify` (`chemx-ratchet.json`), `chemx team task done` and the `introducedViolations` list in `patch`/`write` results share one rule: a change fails when any rule's violation count rises in a file, at any severity. A commit the hook accepts therefore cannot turn the ratchet step red. `CHEMX_SKIP_PRECOMMIT=1` skips only the hook; `chemx verify` still applies the rule. On failure the hook prints each new hazard as `RULE@file:line`. Guarantees, non-guarantees and the specs that prove parity are in [docs/audit-gates.md](docs/audit-gates.md).
|
|
183
|
+
The pre-commit hook (`chemx audit --staged-delta`), the base delta gate (`chemx audit --delta-base=<ref>`, `chemx verify --changed --base=<ref>`), the ratchet step of `chemx verify` (`chemx-ratchet.json`), `chemx team task done` and the `introducedViolations` list in `patch`/`write` results share one rule: a change fails when any rule's violation count rises in a file, at any severity. A commit the hook accepts therefore cannot turn the ratchet step red. The base delta gate scores only the files a branch changed against their merge-base, so a branch that fixes one rule stays green even while the trunk is over the ratchet on other rules. `CHEMX_SKIP_PRECOMMIT=1` skips only the hook; `chemx verify` still applies the rule. On failure the hook prints each new hazard as `RULE@file:line`. Guarantees, non-guarantees and the specs that prove parity are in [docs/audit-gates.md](docs/audit-gates.md).
|
|
184
184
|
|
|
185
185
|
#### Interactive audit navigator
|
|
186
186
|
|
|
@@ -204,8 +204,11 @@ chemx read src/components/m-card.vue --start=10 --end=40
|
|
|
204
204
|
|
|
205
205
|
# Surgical file patching without full-file rewrites (literal replace, written through a temp file and rename, backup kept).
|
|
206
206
|
# Multi-line edits: SEARCH/REPLACE blocks in a quoted heredoc ($, quotes and backticks stay literal).
|
|
207
|
-
# Repeat the block for several edits; they apply in order and all-or-nothing
|
|
208
|
-
#
|
|
207
|
+
# Repeat the block for several edits; they apply in order and all-or-nothing: a block that does not
|
|
208
|
+
# match, or that the guards refuse (parse, a removed declaration, a missing import), fails the call,
|
|
209
|
+
# is named ("Block 2 of 3") and writes nothing. Keep every edit to a file in one call. To edit text that
|
|
210
|
+
# itself contains these markers, use 8 or more characters on all three marker lines (a SEARCH holding a
|
|
211
|
+
# conflict left by a botched rebase works with 7: the file decides which divider ends the SEARCH, longest match).
|
|
209
212
|
chemx patch <file> [--dry-run] <<'PATCH'
|
|
210
213
|
<<<<<<< SEARCH
|
|
211
214
|
const total = items.length;
|
|
@@ -219,8 +222,8 @@ chemx patch <file> --target="oldCode" --replacement="newCode"
|
|
|
219
222
|
# Removing a top-level declaration (renames included) is refused unless named
|
|
220
223
|
chemx patch <file> --target="oldName" --replacement="newName" --allow-remove=oldName
|
|
221
224
|
|
|
222
|
-
# write creates files
|
|
223
|
-
chemx write <file>
|
|
225
|
+
# write creates files (replacing one needs --overwrite; unknown flags refuse). Piped stdin is the content; `-` is optional.
|
|
226
|
+
chemx write <file> [--overwrite] [--dry-run] <<'EOF'
|
|
224
227
|
export const a = 1;
|
|
225
228
|
EOF
|
|
226
229
|
chemx write <file> --content="export const a = 1;" [--overwrite] [--dry-run]
|
|
@@ -230,6 +233,9 @@ chemx write <file> --content=$'\nexport const b = 2;\n' --append
|
|
|
230
233
|
|
|
231
234
|
# Literal text search; add paths to scope it. A missing path is an error, not an empty result.
|
|
232
235
|
chemx q -g "useCardController" src/components docs
|
|
236
|
+
# `a|b` is literal text: --regex searches a regular expression (RE2 subset). -l prints each matching file
|
|
237
|
+
# once; -o --unique prints the distinct matched text. `chemx q <text>` with no symbol hit runs this search itself.
|
|
238
|
+
chemx q -g "--gs-[a-z0-9-]+" --regex -o --unique
|
|
233
239
|
|
|
234
240
|
# Check that every chemx command named in your markdown exists (names only: flags and arguments are
|
|
235
241
|
# not checked, and nothing is run). Exits 1 with file:line for each failure.
|
|
@@ -266,9 +272,10 @@ chemx use-task-filter
|
|
|
266
272
|
### 4. Database-Driven Multi-Agent Swarm Coordination
|
|
267
273
|
Coordinate multi-agent swarms using the local SQLite store (`.chemx/index.db`) without multi-thousand-token markdown specification bloat:
|
|
268
274
|
```bash
|
|
269
|
-
#
|
|
270
|
-
#
|
|
271
|
-
|
|
275
|
+
# Triage is opt-in: `chemx audit --triage` (or `autoTriage: true` in .chemxrc) converts hazards into
|
|
276
|
+
# assignable team tasks, at most 50 per run. A plain `chemx audit` files nothing.
|
|
277
|
+
# Partial runs (--git, --fast, --staged, --no-index) never triage.
|
|
278
|
+
chemx audit --triage
|
|
272
279
|
chemx team task triage # triage on demand
|
|
273
280
|
|
|
274
281
|
# List tasks assigned to a specific agent
|
|
@@ -312,16 +319,17 @@ All reference pages: [docs/INDEX.md](docs/INDEX.md).
|
|
|
312
319
|
|
|
313
320
|
## Language-Agnostic Polyglot Architecture
|
|
314
321
|
|
|
315
|
-
Chemical X audits more than one language. The table below lists the extensions it parses and the checks each gets; any other file type is not audited. Depth differs by language: TypeScript, JavaScript, Vue and Svelte use
|
|
322
|
+
Chemical X audits more than one language. The table below lists the extensions it parses and the checks each gets; any other file type is not audited. Depth differs by language: TypeScript, JavaScript, Vue and Svelte use the Babel parser, PHP uses `php-parser`, C# uses structural patterns, and Python, Go and Rust get the universal text passes only, so they get fewer checks.
|
|
316
323
|
|
|
317
324
|
### Supported Language Ecosystems
|
|
318
325
|
|
|
319
326
|
| Language / Framework | Extensions | Parser Engine | Capabilities |
|
|
320
327
|
| :--- | :--- | :--- | :--- |
|
|
321
328
|
| **C# / .NET 10** | `.cs` | Structural Regex / AST | Line budgets, MediatR handlers, shallow catch detection, fake in-memory stubs (`UseInMemoryDatabase`), `using` namespace indexing |
|
|
322
|
-
| **Python** | `.py` |
|
|
323
|
-
| **Go** | `.go` |
|
|
324
|
-
| **Rust** | `.rs` |
|
|
329
|
+
| **Python** | `.py` | Universal text passes | Line budgets, AI slop text patterns, mock data and secret scanning, fake assertions (`assert True`), `def`/`class` and `import` indexing |
|
|
330
|
+
| **Go** | `.go` | Universal text passes | Line budgets, mock data scanning, tautological assertions, `func`/`type` package indexing |
|
|
331
|
+
| **Rust** | `.rs` | Universal text passes | Line budgets, AI slop text patterns, secret scanning, struct and fn indexing |
|
|
332
|
+
| **PHP / WordPress** | `.php` | `php-parser` AST (loaded only when a PHP file is audited) | Complexity, structural weight, nested ternary, swallowed catches, dynamic code and command execution, and in a WordPress plugin, theme or core tree: REST route permissions, missing nonces, and opt-in SQL, input, output, SSRF and redirect rules. See [docs/php-wordpress.md](docs/php-wordpress.md) |
|
|
325
333
|
| **TypeScript / JavaScript** | `.ts`, `.tsx`, `.js`, `.jsx` | `@babel/parser` AST (`.vue` via `@vue/compiler-sfc`: every script block plus the template) | AST visitors across 11 audit categories, mapped to the 7 pillars; hook contracts, 2-stage booleans, lazy rule trees |
|
|
326
334
|
| **Vue & Svelte** | `.vue`, `.svelte` | Babel + Template Registry | SFC template clone detection, reactive state verification |
|
|
327
335
|
|
|
@@ -334,7 +342,9 @@ Chemical X audits more than one language. The table below lists the extensions i
|
|
|
334
342
|
* **Polyglot Fake Green Tests:** Catches tautological assertions in C# (`Assert.True(true)`), Python (`assert True`), and Go (`assert.True(t, true)`).
|
|
335
343
|
2. **Tier 2: Deep Language-Specific Analyzers**
|
|
336
344
|
* **Babel Engine:** Deep AST inspection for JS/TS/Vue/Svelte (runs on JS, TS, Vue and Svelte files only).
|
|
345
|
+
* **PHP Engine:** `php-parser` AST passes for `.php` files, plus WordPress passes (REST permissions, nonces, SQL, input flow) when a plugin, theme or core tree is detected. Some rules are default-off; `docs/php-wordpress.md` lists the shipped defaults, the per-plugin index and its caps.
|
|
337
346
|
* **Control Flow & Guard Disciplines (`CONTROL_FLOW_CASCADE_GUARDS`, `CONTROL_FLOW_SILENT_GUARD`):** Flags silent bare returns in side-effecting code, and detects cascading early-return clusters (>= 3 guards), recommending extraction into `ruleTree` with 1-line callback aborts (Directive 3.H).
|
|
347
|
+
* **Tailwind content coverage (`TAILWIND_UNSCANNED_CLASS_STRING`):** In a Tailwind v3 project, flags a string literal of 3 or more utility classes (2 or more of them Tailwind-shaped) in a `.js`, `.ts`, `.mjs` or `.cjs` file that no `content` glob of `tailwind.config` covers, since Tailwind purges classes it never scans. The config is read as text and never imported; Tailwind v4, a missing config or a non-literal `content` list keep the rule silent. HIGH, Directive 5.B. `options.tailwindContent` overrides the globs for tests.
|
|
338
348
|
* **C# / Clean Architecture Analyzer:** Flags empty `catch (Exception) {}` blocks, simulated delays (`Task.Delay`), and monolithic controllers.
|
|
339
349
|
* **Swallowed catches (`ERROR_SWALLOWED_EXCEPTION`, `AI_SLOP_SHALLOW_CATCH`):** Each swallowing catch site reports once, as `ERROR_SWALLOWED_EXCEPTION` when the error is discarded or `AI_SLOP_SHALLOW_CATCH` when it is only logged to the console. Both are MEDIUM by default. A JS/TS catch escalates to HIGH when a `let` or `var` assigned in the try is read after it with no default or check first (silent `undefined` propagation); C# findings stay MEDIUM. Mark an intentional swallow with a `chemx-allow: best-effort <reason>` comment on the line above the catch, on the catch line, inside its body, or after the try block's closing brace when `catch` starts the next line. Only comments count (never string literals), and the reason is mandatory: an annotation without one is still flagged and its hazard says so.
|
|
340
350
|
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { defineConfig } from 'vitest/config';
|
|
2
|
+
import { svelte } from '@sveltejs/vite-plugin-svelte';
|
|
3
|
+
|
|
4
|
+
export default defineConfig({
|
|
5
|
+
plugins: [svelte()],
|
|
6
|
+
test: {
|
|
7
|
+
globals: true,
|
|
8
|
+
environment: 'node',
|
|
9
|
+
include: ['**/*.spec.ts', '**/*.test.ts']
|
|
10
|
+
}
|
|
11
|
+
});
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import { render } from 'svelte/server';
|
|
3
|
+
import MSampleCard from './m-sample-card.svelte';
|
|
4
|
+
|
|
5
|
+
describe('m-sample-card: MSampleCard Component', () => {
|
|
6
|
+
it('renders title, formatted value, and standard badge correctly', () => {
|
|
7
|
+
const { body } = render(MSampleCard, {
|
|
8
|
+
props: { title: 'Core Subscription', subtitle: 'Monthly compute tier', value: 750 }
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
expect(body).toContain('class="m-sample-card"');
|
|
12
|
+
expect(body).toContain('Core Subscription');
|
|
13
|
+
expect(body).toContain('Monthly compute tier');
|
|
14
|
+
expect(body).toContain('$750');
|
|
15
|
+
expect(body).toContain('m-sample-card__badge--standard');
|
|
16
|
+
expect(body).not.toContain('m-sample-card__action');
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
it('renders high-value modifier when numeric value exceeds 1000', () => {
|
|
20
|
+
const { body } = render(MSampleCard, { props: { title: 'Enterprise Cluster', value: 2500 } });
|
|
21
|
+
|
|
22
|
+
expect(body).toContain('Enterprise Cluster');
|
|
23
|
+
expect(body).toContain('$2,500');
|
|
24
|
+
expect(body).toContain('m-sample-card__badge--high-value');
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
it('renders the action button only when onAction is given', () => {
|
|
28
|
+
const { body } = render(MSampleCard, { props: { title: 'Actionable Unit', value: 1200, onAction: () => undefined } });
|
|
29
|
+
|
|
30
|
+
expect(body).toContain('m-sample-card__action');
|
|
31
|
+
expect(body).toContain('Action');
|
|
32
|
+
});
|
|
33
|
+
});
|
package/cli/apply-edits.js
CHANGED
|
@@ -21,9 +21,12 @@ import { findForeignLease } from './edit-locks.js';
|
|
|
21
21
|
import { syncLeasesAfterEdit } from './team/lease-renew.js';
|
|
22
22
|
import { countLines } from './line-count.js';
|
|
23
23
|
|
|
24
|
+
// A refusal caused by one block of a multi-block patch carries { block, blocks } (cli/patch-block-blame.js).
|
|
25
|
+
const issueLine = (issue) => `${issue.block ? `Block ${issue.block} of ${issue.blocks}: ` : ''}${issue.file}: ${issue.reason}`;
|
|
26
|
+
|
|
24
27
|
export class EditRefusedError extends Error {
|
|
25
28
|
constructor(issues) {
|
|
26
|
-
super(issues.map(
|
|
29
|
+
super(issues.map(issueLine).join('\n'));
|
|
27
30
|
this.name = 'EditRefusedError';
|
|
28
31
|
this.issues = issues;
|
|
29
32
|
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The zsh unsplit-argument hint (#6773). zsh does not word-split an unquoted $var, so `chemx commit $files`
|
|
3
|
+
* receives ONE argument, "a.js b.js", and every command answered with a message that blamed the files
|
|
4
|
+
* (not tracked, File not found, matched nothing). One shared check lets commit, check and test say the
|
|
5
|
+
* real cause: an argument that has whitespace between words and is not a path on disk.
|
|
6
|
+
*/
|
|
7
|
+
import fs from 'node:fs';
|
|
8
|
+
import path from 'node:path';
|
|
9
|
+
|
|
10
|
+
export const UNSPLIT_HINT = 'one argument contains spaces: did a zsh variable not split? use an array or ${=var}';
|
|
11
|
+
|
|
12
|
+
const hasInnerSpace = (arg) => /\S\s+\S/.test(String(arg));
|
|
13
|
+
|
|
14
|
+
const diskLookup = (cwd) => (arg) => fs.existsSync(path.resolve(cwd, arg));
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* @param {string[]} args Paths or specs a command expected as separate arguments.
|
|
18
|
+
* @param {{ cwd?: string, exists?: (arg: string) => boolean }} [options] `exists` decides what is on disk (default: relative to `cwd`).
|
|
19
|
+
* @returns {string|null} The first argument that has spaces between words and is not a path on disk.
|
|
20
|
+
*/
|
|
21
|
+
export const findUnsplitArgument = (args, { cwd = process.cwd(), exists = diskLookup(cwd) } = {}) => {
|
|
22
|
+
const candidates = (args ?? []).filter(hasInnerSpace);
|
|
23
|
+
return candidates.find((arg) => !exists(arg)) ?? null;
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
/** @returns {string} UNSPLIT_HINT when an argument looks unsplit, else ''. */
|
|
27
|
+
export const unsplitHint = (args, options = {}) => (findUnsplitArgument(args, options) === null ? '' : UNSPLIT_HINT);
|
|
28
|
+
|
|
29
|
+
/** @returns {string} `message` with the hint in parentheses when an argument looks unsplit. */
|
|
30
|
+
export const withUnsplitHint = (message, args, options = {}) => {
|
|
31
|
+
const hint = unsplitHint(args, options);
|
|
32
|
+
return hint ? `${message} (${hint})` : message;
|
|
33
|
+
};
|
package/cli/audit/audit-plain.js
CHANGED
|
@@ -10,6 +10,14 @@ const formatGate = (gate) => {
|
|
|
10
10
|
return `gate: ${verdict}${basis}${note}`;
|
|
11
11
|
};
|
|
12
12
|
|
|
13
|
+
// One line per language, only when more than one language was scanned.
|
|
14
|
+
const formatLanguageLines = (coverage) => {
|
|
15
|
+
const entries = Object.entries(coverage?.byLanguage ?? {});
|
|
16
|
+
const isMultiLanguage = entries.length > 1;
|
|
17
|
+
if (!isMultiLanguage) return [];
|
|
18
|
+
return entries.map(([language, c]) => ` ${language}: ${c.files} files, ${c.ast} ast, ${c.textOnly} text-only, ${c.parseErrors} parse errors`);
|
|
19
|
+
};
|
|
20
|
+
|
|
13
21
|
export const formatPlainAuditSummary = (summary) => {
|
|
14
22
|
const { critical, highMedium, low } = summary.hazards;
|
|
15
23
|
const total = critical + highMedium + low;
|
|
@@ -21,6 +29,7 @@ export const formatPlainAuditSummary = (summary) => {
|
|
|
21
29
|
for (const { rule, baseline, current } of summary.gate.regressions ?? []) {
|
|
22
30
|
lines.push(` regression: ${rule} ${baseline} -> ${current}`);
|
|
23
31
|
}
|
|
32
|
+
lines.push(...formatLanguageLines(summary.coverage));
|
|
24
33
|
lines.push('detail: chemx audit --json | chemx audit --unroll | chemx q --hazards');
|
|
25
34
|
return `${lines.join('\n')}\n`;
|
|
26
35
|
};
|
|
@@ -14,8 +14,8 @@ const countHazards = (violations = []) => {
|
|
|
14
14
|
|
|
15
15
|
const summarizeCoverageForCard = (coverage) => {
|
|
16
16
|
if (!coverage) return null;
|
|
17
|
-
const { files, astPct, parseErrors, textOnly, isPartial, sfc } = coverage;
|
|
18
|
-
return { files, astPct, parseErrors, textOnly, isPartial, sfcTemplates: sfc?.templatesParsed ?? 0 };
|
|
17
|
+
const { files, astPct, parseErrors, textOnly, isPartial, sfc, byLanguage = {} } = coverage;
|
|
18
|
+
return { files, astPct, parseErrors, textOnly, isPartial, sfcTemplates: sfc?.templatesParsed ?? 0, byLanguage };
|
|
19
19
|
};
|
|
20
20
|
|
|
21
21
|
export const buildAuditSummary = (report, { projectRoot, scope }) => {
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Delta gate against a base ref (#6091): each changed source file's working-tree content
|
|
3
|
+
* is audited against the same file at the base, and the gate fails only when a rule's
|
|
4
|
+
* violation count rises in a file, at any severity (gate-delta.js, the rule the pre-commit
|
|
5
|
+
* hook and the ratchet apply). Hazards the base already has never count, so a change that
|
|
6
|
+
* fixes one rule is not held red by other rules the repo-wide ratchet fails on.
|
|
7
|
+
*
|
|
8
|
+
* The base is merge-base(<ref>, HEAD) when there is one, the same starting point
|
|
9
|
+
* `verify --changed --base=<ref>` uses to list changed files; otherwise <ref> itself.
|
|
10
|
+
*/
|
|
11
|
+
import fs from 'node:fs';
|
|
12
|
+
import path from 'node:path';
|
|
13
|
+
import { loadProjectConfig } from '../config/index.js';
|
|
14
|
+
import { isSourceFilePath } from '../audit-preflight-git.js';
|
|
15
|
+
import { evaluateChanges, formatSites } from './gate-delta.js';
|
|
16
|
+
import { gitText, parseNameStatus, changeFromContents } from './staged-delta.js';
|
|
17
|
+
|
|
18
|
+
const DIFF_ARGS = ['diff', '--name-status', '-z', '-M', '--diff-filter=ACMR', '--relative'];
|
|
19
|
+
|
|
20
|
+
/** { label, rev } for an existing commit-ish, null when `baseRef` names none. A leading `-` is never a ref. */
|
|
21
|
+
const resolveBase = (cwd, baseRef) => {
|
|
22
|
+
const isOptionLike = typeof baseRef !== 'string' || baseRef === '' || baseRef.startsWith('-');
|
|
23
|
+
if (isOptionLike) return null;
|
|
24
|
+
const commit = gitText(cwd, ['rev-parse', '--verify', '--quiet', `${baseRef}^{commit}`]);
|
|
25
|
+
const isMissing = commit === null;
|
|
26
|
+
if (isMissing) return null;
|
|
27
|
+
const mergeBase = gitText(cwd, ['merge-base', baseRef, 'HEAD']);
|
|
28
|
+
return { label: baseRef, rev: (mergeBase ?? commit).trim() };
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
/** Untracked, non-ignored source files: new content in the working tree that git diff does not list. */
|
|
32
|
+
const listUntrackedSources = (cwd) =>
|
|
33
|
+
(gitText(cwd, ['ls-files', '--others', '--exclude-standard', '-z']) ?? '')
|
|
34
|
+
.split('\0')
|
|
35
|
+
.filter((file) => file !== '' && isSourceFilePath(file))
|
|
36
|
+
.map((file) => ({ file, basePath: null, isRename: false }));
|
|
37
|
+
|
|
38
|
+
/** Changed source files vs `rev` as { file, basePath, isRename }, paths relative to `cwd`; a file is listed once. */
|
|
39
|
+
const listBaseSources = (cwd, rev) => {
|
|
40
|
+
const tracked = parseNameStatus(gitText(cwd, [...DIFF_ARGS, rev, '--']) ?? '');
|
|
41
|
+
const seen = new Set(tracked.map((entry) => entry.file));
|
|
42
|
+
return [...tracked, ...listUntrackedSources(cwd).filter((entry) => !seen.has(entry.file))];
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
const readWorkingTree = (cwd, file) => {
|
|
46
|
+
const absPath = path.join(cwd, file);
|
|
47
|
+
const isFile = fs.statSync(absPath, { throwIfNoEntry: false })?.isFile() === true;
|
|
48
|
+
return isFile ? fs.readFileSync(absPath, 'utf-8') : null;
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
const toChange = (cwd, rev, entry, config) => {
|
|
52
|
+
const afterText = readWorkingTree(cwd, entry.file);
|
|
53
|
+
const isReadable = afterText !== null;
|
|
54
|
+
if (!isReadable) return null;
|
|
55
|
+
const baseText = entry.basePath ? gitText(cwd, ['show', `${rev}:./${entry.basePath}`]) ?? '' : '';
|
|
56
|
+
return changeFromContents(cwd, entry, { baseText, afterText, config });
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Per-rule delta of the working tree against `baseRef`. `options.config` replaces the project
|
|
61
|
+
* config loaded from `cwd` and `rawArgs` (verify passes the one it already holds).
|
|
62
|
+
*
|
|
63
|
+
* @returns {{ isPassing: boolean, base: string, rev?: string, checked: number, files: object[], baseMissing?: boolean, error?: string }}
|
|
64
|
+
* An unknown ref never throws and never passes: it returns baseMissing with isPassing false, so
|
|
65
|
+
* a typo cannot turn the gate green.
|
|
66
|
+
*/
|
|
67
|
+
export const evaluateBaseDelta = (cwd = process.cwd(), baseRef, rawArgs = [], options = {}) => {
|
|
68
|
+
const resolved = resolveBase(cwd, baseRef);
|
|
69
|
+
const isMissing = resolved === null;
|
|
70
|
+
if (isMissing) return { isPassing: false, base: baseRef ?? null, checked: 0, files: [], baseMissing: true, error: `unknown base revision "${baseRef ?? ''}"` };
|
|
71
|
+
const config = options.config ?? loadProjectConfig(cwd, rawArgs);
|
|
72
|
+
const entries = listBaseSources(cwd, resolved.rev);
|
|
73
|
+
const changes = entries.map((entry) => toChange(cwd, resolved.rev, entry, config)).filter(Boolean);
|
|
74
|
+
return { ...evaluateChanges(changes), base: resolved.label, rev: resolved.rev, checked: changes.length };
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
/** The delta as the compact block `verify --json` carries: rising rules and one `RULE@file:line [SEVERITY]` per new hazard. */
|
|
78
|
+
export const summarizeBaseDelta = (delta) => ({
|
|
79
|
+
base: delta.base,
|
|
80
|
+
passing: delta.isPassing,
|
|
81
|
+
checked: delta.checked,
|
|
82
|
+
...(delta.error ? { error: delta.error } : {}),
|
|
83
|
+
files: delta.files.map((entry) => ({
|
|
84
|
+
file: entry.file,
|
|
85
|
+
...(entry.renamedFrom ? { renamedFrom: entry.renamedFrom } : {}),
|
|
86
|
+
increases: entry.increases.map(({ rule, severity, before, after }) => ({ rule, severity, before, after })),
|
|
87
|
+
sites: formatSites(entry)
|
|
88
|
+
}))
|
|
89
|
+
});
|
package/cli/audit/coverage.js
CHANGED
|
@@ -13,10 +13,33 @@ export const COVERAGE_KINDS = Object.freeze({
|
|
|
13
13
|
|
|
14
14
|
export const createCoverageCollector = () => ({ files: [] });
|
|
15
15
|
|
|
16
|
-
/**
|
|
17
|
-
|
|
16
|
+
/**
|
|
17
|
+
* Records one file. `sfc` is { scriptBlocks, hasTemplate, isTemplateParsed } for .vue files;
|
|
18
|
+
* `language` (language id) and `reason` (why the file was not fully parsed) are optional.
|
|
19
|
+
*/
|
|
20
|
+
export const recordFileCoverage = (collector, { relativePath, kind, sfc = null, language = null, reason = null }) => {
|
|
18
21
|
if (!collector) return;
|
|
19
|
-
collector.files.push({ relativePath, kind, sfc });
|
|
22
|
+
collector.files.push({ relativePath, kind, sfc, language, reason });
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
const BUCKET_FIELD_BY_KIND = {
|
|
26
|
+
[COVERAGE_KINDS.AST]: 'ast',
|
|
27
|
+
[COVERAGE_KINDS.TEXT_ONLY]: 'textOnly',
|
|
28
|
+
[COVERAGE_KINDS.PARSE_ERROR]: 'parseErrors'
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
const summarizeByLanguage = (entries) => {
|
|
32
|
+
const byLanguage = {};
|
|
33
|
+
for (const entry of entries) {
|
|
34
|
+
if (!entry.language) continue;
|
|
35
|
+
const bucket = byLanguage[entry.language] ?? { files: 0, ast: 0, textOnly: 0, parseErrors: 0 };
|
|
36
|
+
bucket.files += 1;
|
|
37
|
+
const field = BUCKET_FIELD_BY_KIND[entry.kind];
|
|
38
|
+
const isCounted = Boolean(field);
|
|
39
|
+
if (isCounted) bucket[field] += 1;
|
|
40
|
+
byLanguage[entry.language] = bucket;
|
|
41
|
+
}
|
|
42
|
+
return byLanguage;
|
|
20
43
|
};
|
|
21
44
|
|
|
22
45
|
const isPartialEntry = (entry) => {
|
|
@@ -42,6 +65,7 @@ export const summarizeCoverage = (collector) => {
|
|
|
42
65
|
scriptBlocks: sfcEntries.reduce((sum, e) => sum + (e.sfc.scriptBlocks ?? 0), 0),
|
|
43
66
|
templatesParsed: sfcEntries.filter((e) => e.sfc.isTemplateParsed).length
|
|
44
67
|
},
|
|
68
|
+
byLanguage: summarizeByLanguage(entries),
|
|
45
69
|
isPartial: partialFiles.length > 0,
|
|
46
70
|
partialFiles: partialFiles.slice(0, 20)
|
|
47
71
|
};
|