@monte3l/groundwork 1.0.0-rc.2 → 1.0.0-rc.3
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/README.md +13 -5
- package/bin/m3l-groundwork.mjs +36 -2
- package/dist/assets.d.ts +10 -0
- package/dist/assets.js +14 -0
- package/dist/baseline-stage.d.ts +173 -0
- package/dist/baseline-stage.js +215 -0
- package/dist/caps.d.ts +4 -1
- package/dist/caps.js +17 -3
- package/dist/conflicts.d.ts +23 -2
- package/dist/conflicts.js +89 -11
- package/dist/customize-paths.d.ts +92 -0
- package/dist/customize-paths.js +115 -0
- package/dist/emit.d.ts +50 -1
- package/dist/emit.js +121 -13
- package/dist/fatal.d.ts +70 -0
- package/dist/fatal.js +132 -0
- package/dist/format-error.d.ts +113 -0
- package/dist/format-error.js +553 -0
- package/dist/fs-guard.d.ts +142 -0
- package/dist/fs-guard.js +222 -0
- package/dist/git.js +10 -1
- package/dist/harness/conformance.js +2 -0
- package/dist/harness/frontmatter.js +2 -13
- package/dist/harness/grade.js +37 -8
- package/dist/harness/rules.d.ts +20 -3
- package/dist/harness/rules.js +108 -6
- package/dist/harness/types.d.ts +13 -0
- package/dist/harness/types.js +3 -0
- package/dist/inventory.d.ts +77 -3
- package/dist/inventory.js +103 -12
- package/dist/jsonc.d.ts +49 -2
- package/dist/jsonc.js +140 -7
- package/dist/main.d.ts +62 -3
- package/dist/main.js +538 -67
- package/dist/merge-json.d.ts +47 -4
- package/dist/merge-json.js +120 -8
- package/dist/mode.js +12 -2
- package/dist/pack-stage.d.ts +210 -0
- package/dist/pack-stage.js +287 -0
- package/dist/packs.d.ts +19 -13
- package/dist/packs.js +231 -28
- package/dist/palette.d.ts +23 -0
- package/dist/palette.js +22 -0
- package/dist/plugin.d.ts +122 -6
- package/dist/plugin.js +687 -47
- package/dist/report.d.ts +21 -2
- package/dist/report.js +93 -5
- package/dist/staging.d.ts +176 -0
- package/dist/staging.js +375 -0
- package/dist/survey/fs-walk.d.ts +34 -2
- package/dist/survey/fs-walk.js +70 -5
- package/dist/survey/internal/blocked-path.d.ts +27 -0
- package/dist/survey/internal/blocked-path.js +129 -0
- package/dist/survey/internal/package-json.d.ts +13 -0
- package/dist/survey/internal/package-json.js +37 -0
- package/dist/survey/internal/read-guard.d.ts +178 -0
- package/dist/survey/internal/read-guard.js +276 -0
- package/dist/survey/survey-docs.d.ts +17 -2
- package/dist/survey/survey-docs.js +61 -21
- package/dist/survey/survey-harness.d.ts +20 -2
- package/dist/survey/survey-harness.js +87 -46
- package/dist/survey/survey-shape.d.ts +17 -2
- package/dist/survey/survey-shape.js +60 -46
- package/dist/survey/survey-toolchain.d.ts +16 -1
- package/dist/survey/survey-toolchain.js +69 -66
- package/dist/survey/survey.js +6 -4
- package/dist/survey/types.d.ts +79 -0
- package/dist/survey/types.js +2 -6
- package/dist/term.d.ts +80 -0
- package/dist/term.js +145 -0
- package/dist/tokens.js +2 -0
- package/dist/toolchain/conformance.js +2 -0
- package/dist/toolchain/grade.js +26 -8
- package/dist/toolchain/rules.d.ts +13 -4
- package/dist/toolchain/rules.js +16 -0
- package/dist/toolchain/tsconfig-chain.d.ts +2 -0
- package/dist/toolchain/tsconfig-chain.js +32 -8
- package/dist/toolchain/types.d.ts +16 -4
- package/dist/toolchain/types.js +3 -0
- package/package.json +4 -3
- package/plugin/skills/customize/SKILL.md +292 -18
- package/plugin/src/domain-map.ts +39 -14
- package/plugin/src/index.ts +5 -1
- package/plugin/src/kind-facet-map.ts +25 -8
- package/plugin/src/pack-map.ts +147 -25
- package/plugin/src/plugin-map.ts +236 -0
- package/templates/core/.claude/agents/Explore.md +0 -1
- package/templates/core/.claude/agents/code-implementer.md +4 -4
- package/templates/core/.claude/agents/code-reviewer.md +4 -4
- package/templates/core/.claude/agents/silent-failure-hunter.md +2 -2
- package/templates/core/.claude/agents/test-author.md +7 -5
- package/templates/core/.claude/hooks/guard-branch-isolation.mjs +56 -10
- package/templates/core/.claude/hooks/guard-double-background.mjs +13 -8
- package/templates/core/.claude/hooks/guard-git-push-signed.mjs +12 -9
- package/templates/core/.claude/hooks/guard-hub-src-writes.mjs +1890 -38
- package/templates/core/.claude/hooks/guard-js-extension.mjs +2 -1
- package/templates/core/.claude/hooks/guard-no-commonjs.mjs +7 -5
- package/templates/core/.claude/hooks/guard-secret-writes.mjs +7 -5
- package/templates/core/.claude/hooks/inject-decision-gate.mjs +12 -9
- package/templates/core/.claude/hooks/post-edit-verify.mjs +276 -98
- package/templates/core/.claude/rules/agent-dispatch.md +7 -0
- package/templates/core/.claude/rules/tests.md +2 -2
- package/templates/core/.claude/settings.json +5 -0
- package/templates/core/.claude/skills/finishing-work/SKILL.md +58 -10
- package/templates/core/.claude/skills/harness-guidance/SKILL.md +16 -7
- package/templates/core/.claude/skills/starting-work/SKILL.md +5 -0
- package/templates/core/.claude/skills/triaging-ci/SKILL.md +9 -8
- package/templates/core/.claude/skills/typescript-guidance/SKILL.md +137 -20
- package/templates/core/.claude/skills/typescript-guidance/references/area-catalog.md +135 -0
- package/templates/core/.claude/skills/typescript-guidance/references/tooling-sources.md +113 -0
- package/templates/core/.claude/skills/writing-commits/SKILL.md +2 -2
- package/templates/core/.github/dependabot.yml +18 -0
- package/templates/core/.github/workflows/ci.yml +15 -15
- package/templates/core/.github/workflows/dependency-review.yml +2 -2
- package/templates/core/.github/workflows/security-audit.yml +10 -4
- package/templates/core/.prettierignore +4 -0
- package/templates/core/CLAUDE.md +53 -3
- package/templates/core/README.md +30 -10
- package/templates/core/_gitignore +17 -0
- package/templates/core/bin/check-exports.mjs +11 -5
- package/templates/core/bin/lib/agent-roster.mjs +1 -1
- package/templates/core/bin/lib/frontmatter.mjs +4 -2
- package/templates/core/bin/lib/harness-rules.mjs +169 -20
- package/templates/core/bin/lib/protected-paths.mjs +93 -5
- package/templates/core/bin/lib/toolchain-rules.mjs +187 -26
- package/templates/core/bin/lib/verify-steps.mjs +29 -11
- package/templates/core/bin/verify.mjs +12 -0
- package/templates/core/eslint.config.js +5 -0
- package/templates/core/package.json +7 -7
- package/templates/core/vitest.config.ts +8 -1
- package/templates/packs/README.md +35 -24
- package/templates/packs/github/files/.claude/skills/reviewing-dependabot-prs/SKILL.md +133 -0
- package/templates/packs/github/files/.claude/skills/triaging-scan-alerts/SKILL.md +88 -0
- package/templates/packs/github/files/.claude/skills/watching-pr-checks/SKILL.md +94 -0
- package/templates/packs/github/files/.github/workflows/claude-pr-review.yml +267 -0
- package/templates/packs/github/files/.github/workflows/claude.yml +106 -0
- package/templates/packs/github/pack.json +19 -0
- package/templates/packs/harness-extras/files/.claude/hooks/guard-readonly-bash.mjs +1122 -65
- package/templates/packs/harness-extras/files/.claude/hooks/reinject-compact-handoff.mjs +147 -13
- package/templates/packs/{statusline → harness-extras}/files/.claude/hooks/statusline.mjs +6 -2
- package/templates/packs/harness-extras/files/.claude/hooks/write-compact-handoff.mjs +227 -44
- package/templates/packs/harness-extras/pack.json +18 -13
- package/templates/packs/publishing/files/.changeset/README.md +25 -0
- package/templates/packs/publishing/files/.changeset/config.json +7 -0
- package/templates/packs/publishing/files/.github/release-tools/package.json +9 -0
- package/templates/packs/publishing/files/.github/workflows/release.yml +293 -0
- package/templates/packs/publishing/files/REUSE.toml +28 -0
- package/templates/packs/publishing/files/bin/check-dts-deps.mjs +234 -0
- package/templates/packs/publishing/files/bin/check-license-headers.mjs +217 -0
- package/templates/packs/publishing/files/bin/check-publish-version.mjs +149 -0
- package/templates/packs/publishing/files/bin/lib/npm-publish-args.mjs +86 -0
- package/templates/packs/publishing/files/bin/pnpm-publish-shim.mjs +86 -0
- package/templates/packs/publishing/pack.json +35 -0
- package/templates/packs/{harness-extras → quality}/files/bin/check-file-budget.mjs +6 -4
- package/templates/packs/quality/pack.json +29 -0
- package/templates/packs/supply-chain/files/.github/workflows/gitleaks.yml +52 -0
- package/templates/packs/supply-chain/files/.github/workflows/scorecard.yml +48 -0
- package/templates/packs/supply-chain/files/.gitleaks.toml +2 -0
- package/templates/packs/supply-chain/pack.json +19 -0
- package/templates/packs/worktrees/files/.claude/hooks/ensure-worktree-deps.mjs +283 -0
- package/templates/packs/worktrees/files/.claude/hooks/guard-worktree-only.mjs +245 -0
- package/templates/packs/worktrees/files/.claude/hooks/repair-core-bare.mjs +335 -0
- package/templates/packs/worktrees/files/.claude/skills/working-in-worktrees/SKILL.md +139 -0
- package/templates/packs/worktrees/files/.worktreeinclude +11 -0
- package/templates/packs/worktrees/pack.json +64 -0
- package/templates/packs/statusline/pack.json +0 -31
- /package/templates/packs/{statusline → harness-extras}/files/.claude/hooks/statusline-layout.mjs +0 -0
- /package/templates/packs/{statusline → harness-extras}/files/.claude/hooks/subagent-statusline.mjs +0 -0
- /package/templates/packs/{harness-extras → quality}/files/.claude/agents/type-design-analyzer.md +0 -0
- /package/templates/packs/{harness-extras → quality}/files/bin/file-budget-baseline.json +0 -0
package/dist/survey/survey.js
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
// SPDX-FileCopyrightText: Copyright the m3l-groundwork contributors
|
|
2
|
+
// SPDX-License-Identifier: MIT
|
|
1
3
|
/**
|
|
2
4
|
* The survey aggregate: runs all four collectors against a target directory
|
|
3
5
|
* and returns the combined `ProjectSurvey`. See `types.ts` for the shape
|
|
@@ -11,11 +13,11 @@ import { surveyToolchain } from "./survey-toolchain.js";
|
|
|
11
13
|
export function surveyProject(dir) {
|
|
12
14
|
const undetermined = [];
|
|
13
15
|
return {
|
|
14
|
-
shape: surveyShape(dir),
|
|
16
|
+
shape: surveyShape(dir, undetermined),
|
|
15
17
|
toolchain: surveyToolchain(dir, undetermined),
|
|
16
|
-
harness: surveyHarness(dir),
|
|
17
|
-
docs: surveyDocs(dir),
|
|
18
|
-
undetermined,
|
|
18
|
+
harness: surveyHarness(dir, undetermined),
|
|
19
|
+
docs: surveyDocs(dir, undetermined),
|
|
20
|
+
undetermined: [...new Set(undetermined)],
|
|
19
21
|
};
|
|
20
22
|
}
|
|
21
23
|
//# sourceMappingURL=survey.js.map
|
package/dist/survey/types.d.ts
CHANGED
|
@@ -4,32 +4,56 @@
|
|
|
4
4
|
* `ProjectSurvey`, and `conflicts.ts`/`report.ts` need to name these shapes
|
|
5
5
|
* without importing a collector module just for its types.
|
|
6
6
|
*/
|
|
7
|
+
/** The package manager, inferred from which lockfile is present -- `unknown` when none is. */
|
|
7
8
|
export type PackageManager = "npm" | "pnpm" | "yarn" | "bun" | "unknown";
|
|
9
|
+
/** The monorepo tooling, inferred from its marker file (or `package.json`'s `workspaces`) -- `none` when nothing is found. */
|
|
8
10
|
export type MonorepoTool = "pnpm-workspaces" | "turbo" | "nx" | "lerna" | "npm-workspaces" | "none";
|
|
11
|
+
/** `package.json`'s `type` field as declared -- `unspecified` when it is absent or not one of the two known values. */
|
|
9
12
|
export type ModuleType = "module" | "commonjs" | "unspecified";
|
|
13
|
+
/** Where source lives: a `src/` or `lib/` directory, loose source files at the root, or `unknown`. */
|
|
10
14
|
export type SourceLayout = "src" | "lib" | "root" | "unknown";
|
|
15
|
+
/** Where tests live: a `tests/`/`test/` directory, `*.test`/`*.spec` files next to the source, or `unknown`. */
|
|
11
16
|
export type TestPlacement = "tests-dir" | "colocated" | "unknown";
|
|
17
|
+
/** A Node.js version pin, and the file or field it was read from. */
|
|
12
18
|
export interface NodeVersionPin {
|
|
19
|
+
/** Where the pin was found: `.node-version`, `.nvmrc`, or `package.json#engines.node`. */
|
|
13
20
|
source: string;
|
|
21
|
+
/** The pin's value verbatim (trimmed), never normalized to a semver range. */
|
|
14
22
|
value: string;
|
|
15
23
|
}
|
|
24
|
+
/** Raw `package.json` facts that hint at what kind of project this is -- evidence only, never the verdict. */
|
|
16
25
|
export interface KindEvidence {
|
|
26
|
+
/** Whether `package.json` declares an `exports` field. */
|
|
17
27
|
hasExportsMap: boolean;
|
|
28
|
+
/** Whether `package.json` declares a `bin` field. */
|
|
18
29
|
hasBinField: boolean;
|
|
30
|
+
/** Whether `package.json` declares a `main` field. */
|
|
19
31
|
hasMainField: boolean;
|
|
32
|
+
/** Well-known framework packages found among `dependencies`/`devDependencies`. */
|
|
20
33
|
frameworkDeps: string[];
|
|
21
34
|
}
|
|
35
|
+
/** Codebase-shape facts: package manager, monorepo tooling, module system, pins, and layout. */
|
|
22
36
|
export interface ShapeSurvey {
|
|
37
|
+
/** The package manager, from which lockfile is present. */
|
|
23
38
|
packageManager: PackageManager;
|
|
39
|
+
/** The monorepo tool, from its marker file or `package.json`'s `workspaces`. */
|
|
24
40
|
monorepoTool: MonorepoTool;
|
|
41
|
+
/** The workspace package globs the monorepo tool declares -- empty when it declares none or none were read. */
|
|
25
42
|
workspaceGlobs: string[];
|
|
43
|
+
/** `package.json`'s declared module system. */
|
|
26
44
|
moduleType: ModuleType;
|
|
45
|
+
/** The `typescript` version range from `devDependencies` (preferred) or `dependencies`, verbatim. */
|
|
27
46
|
typescriptVersion: string | undefined;
|
|
47
|
+
/** The first Node.js version pin found, or `undefined` when there is none. */
|
|
28
48
|
nodeVersionPin: NodeVersionPin | undefined;
|
|
49
|
+
/** Where the project's source files live. */
|
|
29
50
|
sourceLayout: SourceLayout;
|
|
51
|
+
/** Where the project's test files live. */
|
|
30
52
|
testPlacement: TestPlacement;
|
|
53
|
+
/** Evidence for what kind of project this is, left for `/customize` to interpret. */
|
|
31
54
|
kindEvidence: KindEvidence;
|
|
32
55
|
}
|
|
56
|
+
/** The project's tsconfig `extends` chain and the strict-family flags it resolves to. */
|
|
33
57
|
export interface TsconfigSurvey {
|
|
34
58
|
/** The extends chain, entry file first (child before parent), as absolute paths. */
|
|
35
59
|
files: string[];
|
|
@@ -38,77 +62,132 @@ export interface TsconfigSurvey {
|
|
|
38
62
|
/** False if any file in the chain failed to parse -- see `undetermined` on the parent survey. */
|
|
39
63
|
parsed: boolean;
|
|
40
64
|
}
|
|
65
|
+
/** The project's ESLint config file and what it references. */
|
|
41
66
|
export interface EslintSurvey {
|
|
67
|
+
/** The config file found at the project root, or `undefined` when there is none. */
|
|
42
68
|
configFile: string | undefined;
|
|
69
|
+
/** True for a flat config (`eslint.config.*`); false for a legacy `.eslintrc*` one or when there is no config. */
|
|
43
70
|
flat: boolean;
|
|
71
|
+
/** Plugin names found in the config's source text -- scraped, never by executing it. */
|
|
44
72
|
referencedPlugins: string[];
|
|
45
73
|
}
|
|
46
74
|
type TestRunnerTool = "vitest" | "jest" | "mocha" | "node-test" | "unknown";
|
|
75
|
+
/** The project's test runner, from its config file or `test` script. */
|
|
47
76
|
export interface TestRunnerSurvey {
|
|
77
|
+
/** The runner detected -- `unknown` when neither a config file nor the `test` script names one. */
|
|
48
78
|
tool: TestRunnerTool;
|
|
79
|
+
/** The runner's config file, or `undefined` when none was found. */
|
|
49
80
|
configFile: string | undefined;
|
|
50
81
|
}
|
|
51
82
|
type FormatterTool = "prettier" | "biome" | "unknown";
|
|
83
|
+
/** The project's code formatter, from its config file. */
|
|
52
84
|
export interface FormatterSurvey {
|
|
85
|
+
/** The formatter detected -- `unknown` when no known config file is present. */
|
|
53
86
|
tool: FormatterTool;
|
|
87
|
+
/** The formatter's config file, or `undefined` when none was found. */
|
|
54
88
|
configFile: string | undefined;
|
|
55
89
|
}
|
|
56
90
|
type GitHookManager = "lefthook" | "husky" | "simple-git-hooks" | "none";
|
|
91
|
+
/** The project's git-hook manager and where its config lives. */
|
|
57
92
|
export interface GitHooksSurvey {
|
|
93
|
+
/** The hook manager detected from its config file or directory -- `none` when none is found. */
|
|
58
94
|
manager: GitHookManager;
|
|
95
|
+
/** The manager's config file or directory, or `undefined` when there is no manager. */
|
|
59
96
|
configFile: string | undefined;
|
|
60
97
|
/** True whenever the manager's config is YAML/shell-script shaped -- this module indexes it, never parses it. */
|
|
61
98
|
needsReading: boolean;
|
|
62
99
|
}
|
|
100
|
+
/** The project's CI workflow files under `.github/workflows/`. */
|
|
63
101
|
export interface WorkflowsSurvey {
|
|
102
|
+
/** Workflow file names (`.yml`/`.yaml`), relative to `.github/workflows/`. */
|
|
64
103
|
files: string[];
|
|
104
|
+
/** True whenever any workflow file exists -- YAML is indexed, never parsed, so its steps need reading. */
|
|
65
105
|
needsReading: boolean;
|
|
66
106
|
}
|
|
107
|
+
/** Every toolchain enforcement mechanism the survey found in effect. */
|
|
67
108
|
export interface ToolchainSurvey {
|
|
109
|
+
/** The tsconfig `extends` chain and its effective strict-family flags. */
|
|
68
110
|
tsconfig: TsconfigSurvey;
|
|
111
|
+
/** The ESLint config, if any. */
|
|
69
112
|
eslint: EslintSurvey;
|
|
113
|
+
/** The test runner, if one was detected. */
|
|
70
114
|
testRunner: TestRunnerSurvey;
|
|
115
|
+
/** The code formatter, if one was detected. */
|
|
71
116
|
formatter: FormatterSurvey;
|
|
117
|
+
/** The git-hook manager, if one was detected. */
|
|
72
118
|
gitHooks: GitHooksSurvey;
|
|
119
|
+
/** The CI workflow files, if any. */
|
|
73
120
|
workflows: WorkflowsSurvey;
|
|
121
|
+
/** `package.json`'s `scripts`, name to command, verbatim (string values only). */
|
|
74
122
|
scripts: Record<string, string>;
|
|
75
123
|
}
|
|
124
|
+
/** One agent file under `.claude/agents/`. */
|
|
76
125
|
export interface HarnessAgent {
|
|
126
|
+
/** The agent's file name without its `.md` extension. */
|
|
77
127
|
name: string;
|
|
128
|
+
/** The agent's frontmatter `model` field, or `undefined` when absent or empty. */
|
|
78
129
|
model: string | undefined;
|
|
79
130
|
}
|
|
131
|
+
/** One skill directory under `.claude/skills/` that has a `SKILL.md`. */
|
|
80
132
|
export interface HarnessSkill {
|
|
133
|
+
/** The skill's frontmatter `name`, falling back to its directory name. */
|
|
81
134
|
name: string;
|
|
135
|
+
/** The skill's frontmatter `description`, or `undefined` when absent or empty. */
|
|
82
136
|
description: string | undefined;
|
|
83
137
|
}
|
|
138
|
+
/** One rule file under `.claude/rules/` -- the survey's index entry, unrelated to the harness grader's rule type. */
|
|
84
139
|
export interface HarnessRule {
|
|
140
|
+
/** The rule's file name without its `.md` extension. */
|
|
85
141
|
name: string;
|
|
142
|
+
/** The rule's frontmatter `paths` scope as text (a list joined with commas), or `undefined` when absent or empty. */
|
|
86
143
|
paths: string | undefined;
|
|
87
144
|
}
|
|
145
|
+
/** The project's existing Claude Code harness: its `.claude/` directory plus `CLAUDE.md`. */
|
|
88
146
|
export interface HarnessSurvey {
|
|
147
|
+
/** Whether a `.claude/` directory exists at all. */
|
|
89
148
|
present: boolean;
|
|
149
|
+
/** `settings.json` when `.claude/settings.json` exists, otherwise `undefined`. */
|
|
90
150
|
settingsFile: string | undefined;
|
|
151
|
+
/** Every agent found under `.claude/agents/`. */
|
|
91
152
|
agents: HarnessAgent[];
|
|
153
|
+
/** Every skill found under `.claude/skills/`. */
|
|
92
154
|
skills: HarnessSkill[];
|
|
155
|
+
/** Hook script file names (`.mjs`/`.js`) under `.claude/hooks/`. */
|
|
93
156
|
hooks: string[];
|
|
157
|
+
/** Every rule file found under `.claude/rules/`. */
|
|
94
158
|
rules: HarnessRule[];
|
|
159
|
+
/** Command file names (`.md`) under `.claude/commands/`. */
|
|
95
160
|
commands: string[];
|
|
161
|
+
/** Whether a `.claude/settings.local.json` exists, which may shadow shared settings. */
|
|
96
162
|
hasSettingsLocal: boolean;
|
|
163
|
+
/** Whether a root `CLAUDE.md` exists -- recorded even when `.claude/` does not. */
|
|
97
164
|
hasClaudeMd: boolean;
|
|
165
|
+
/** `CLAUDE.md`'s level 1-3 headings in document order -- empty when there is no `CLAUDE.md`. */
|
|
98
166
|
claudeMdHeadings: string[];
|
|
99
167
|
}
|
|
168
|
+
/** One indexed human-facing doc: where it is and its outline, never its content. */
|
|
100
169
|
export interface DocFile {
|
|
170
|
+
/** The doc's path on disk. */
|
|
101
171
|
path: string;
|
|
172
|
+
/** The doc's size on disk, in bytes. */
|
|
102
173
|
sizeBytes: number;
|
|
174
|
+
/** The doc's level 1-3 Markdown headings in document order. */
|
|
103
175
|
headings: string[];
|
|
104
176
|
}
|
|
177
|
+
/** The index of human-facing docs and guidelines the survey found. */
|
|
105
178
|
export interface DocsSurvey {
|
|
179
|
+
/** Every indexed doc: root README/CONTRIBUTING/style guides, then named doc directories. */
|
|
106
180
|
files: DocFile[];
|
|
107
181
|
}
|
|
182
|
+
/** The aggregate output of all four survey collectors -- adopt mode's offline index of the project. */
|
|
108
183
|
export interface ProjectSurvey {
|
|
184
|
+
/** Codebase-shape facts. */
|
|
109
185
|
shape: ShapeSurvey;
|
|
186
|
+
/** Toolchain enforcement in effect. */
|
|
110
187
|
toolchain: ToolchainSurvey;
|
|
188
|
+
/** The existing Claude Code harness. */
|
|
111
189
|
harness: HarnessSurvey;
|
|
190
|
+
/** The human-facing docs index. */
|
|
112
191
|
docs: DocsSurvey;
|
|
113
192
|
/** Things the survey attempted and could not parse or classify -- never silently dropped. */
|
|
114
193
|
undetermined: string[];
|
package/dist/survey/types.js
CHANGED
|
@@ -1,8 +1,4 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
* aggregate. Kept in one file since every collector's output composes into
|
|
4
|
-
* `ProjectSurvey`, and `conflicts.ts`/`report.ts` need to name these shapes
|
|
5
|
-
* without importing a collector module just for its types.
|
|
6
|
-
*/
|
|
1
|
+
// SPDX-FileCopyrightText: Copyright the m3l-groundwork contributors
|
|
2
|
+
// SPDX-License-Identifier: MIT
|
|
7
3
|
export {};
|
|
8
4
|
//# sourceMappingURL=types.js.map
|
package/dist/term.d.ts
ADDED
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { type PaletteRoleColors } from "./palette.js";
|
|
2
|
+
/**
|
|
3
|
+
* A semantic color role the palette defines for both themes.
|
|
4
|
+
*
|
|
5
|
+
* @example
|
|
6
|
+
* ```ts
|
|
7
|
+
* import type { PaletteRole } from "./term.js";
|
|
8
|
+
* const role: PaletteRole = "success";
|
|
9
|
+
* ```
|
|
10
|
+
*/
|
|
11
|
+
export type PaletteRole = keyof PaletteRoleColors;
|
|
12
|
+
/**
|
|
13
|
+
* Decides whether `stream` should receive color escapes. `NO_COLOR` (any
|
|
14
|
+
* value, even empty) disables color outright; otherwise `FORCE_COLOR` set to
|
|
15
|
+
* anything but `"0"` enables it; otherwise color follows `stream.isTTY`.
|
|
16
|
+
*
|
|
17
|
+
* @example
|
|
18
|
+
* ```ts
|
|
19
|
+
* import process from "node:process";
|
|
20
|
+
* import { supportsColor } from "./term.js";
|
|
21
|
+
* if (supportsColor(process.stdout, process.env)) {
|
|
22
|
+
* // safe to emit escapes
|
|
23
|
+
* }
|
|
24
|
+
* ```
|
|
25
|
+
*/
|
|
26
|
+
export declare function supportsColor(stream: {
|
|
27
|
+
readonly isTTY?: boolean;
|
|
28
|
+
}, env: NodeJS.ProcessEnv): boolean;
|
|
29
|
+
/**
|
|
30
|
+
* Converts a `#rrggbb` color into a 24-bit SGR foreground escape.
|
|
31
|
+
*
|
|
32
|
+
* @throws RangeError if `hex` is not exactly `#` plus six hex digits.
|
|
33
|
+
* @example
|
|
34
|
+
* ```ts
|
|
35
|
+
* import { truecolorSgr } from "./term.js";
|
|
36
|
+
* truecolorSgr("#086d31"); // "\x1b[38;2;8;109;49m"
|
|
37
|
+
* ```
|
|
38
|
+
*/
|
|
39
|
+
export declare function truecolorSgr(hex: string): string;
|
|
40
|
+
/**
|
|
41
|
+
* Converts a `#rrggbb` color into the SGR foreground escape of the nearest
|
|
42
|
+
* of the 16 standard ANSI colors (squared Euclidean RGB distance).
|
|
43
|
+
*
|
|
44
|
+
* @throws RangeError if `hex` is not exactly `#` plus six hex digits.
|
|
45
|
+
* @example
|
|
46
|
+
* ```ts
|
|
47
|
+
* import { nearest16Sgr } from "./term.js";
|
|
48
|
+
* nearest16Sgr("#cd0000"); // "\x1b[31m"
|
|
49
|
+
* ```
|
|
50
|
+
*/
|
|
51
|
+
export declare function nearest16Sgr(hex: string): string;
|
|
52
|
+
/**
|
|
53
|
+
* Picks the palette theme from `COLORFGBG` (`"fg;bg"`): `"light"` when the
|
|
54
|
+
* last segment is `15` or `7` (a white/light-grey background), `"dark"`
|
|
55
|
+
* otherwise, including when the variable is absent or malformed.
|
|
56
|
+
*
|
|
57
|
+
* @example
|
|
58
|
+
* ```ts
|
|
59
|
+
* import { resolveThemeId } from "./term.js";
|
|
60
|
+
* resolveThemeId({ COLORFGBG: "0;15" }); // "light"
|
|
61
|
+
* ```
|
|
62
|
+
*/
|
|
63
|
+
export declare function resolveThemeId(env: NodeJS.ProcessEnv): "light" | "dark";
|
|
64
|
+
/**
|
|
65
|
+
* Wraps `text` in the palette color for `role`, or returns it unchanged when
|
|
66
|
+
* {@link supportsColor} says `stream` should not receive color. Uses a 24-bit
|
|
67
|
+
* escape when `COLORTERM` is `truecolor`/`24bit`, the nearest ANSI-16 color
|
|
68
|
+
* otherwise.
|
|
69
|
+
*
|
|
70
|
+
* @example
|
|
71
|
+
* ```ts
|
|
72
|
+
* import process from "node:process";
|
|
73
|
+
* import { paint } from "./term.js";
|
|
74
|
+
* console.log(paint(process.stdout, "success", "done"));
|
|
75
|
+
* ```
|
|
76
|
+
*/
|
|
77
|
+
export declare function paint(stream: {
|
|
78
|
+
readonly isTTY?: boolean;
|
|
79
|
+
}, role: PaletteRole, text: string, env?: NodeJS.ProcessEnv): string;
|
|
80
|
+
//# sourceMappingURL=term.d.ts.map
|
package/dist/term.js
ADDED
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
// SPDX-FileCopyrightText: Copyright the m3l-groundwork contributors
|
|
2
|
+
// SPDX-License-Identifier: MIT
|
|
3
|
+
/**
|
|
4
|
+
* Zero-dependency terminal coloring over the generated {@link PALETTE}
|
|
5
|
+
* (`./palette.ts`, built from the vendored m3l-design tokens). Every function
|
|
6
|
+
* here is pure: color support, theme and color depth are all decided from
|
|
7
|
+
* the stream and environment the caller passes in.
|
|
8
|
+
*/
|
|
9
|
+
import process from "node:process";
|
|
10
|
+
import { PALETTE } from "./palette.js";
|
|
11
|
+
const HEX_COLOR = /^#([0-9a-f]{2})([0-9a-f]{2})([0-9a-f]{2})$/i;
|
|
12
|
+
/** The 16 standard ANSI foreground colors, as xterm's default RGB values. */
|
|
13
|
+
const ANSI_16 = [
|
|
14
|
+
{ code: 30, rgb: [0, 0, 0] },
|
|
15
|
+
{ code: 31, rgb: [205, 0, 0] },
|
|
16
|
+
{ code: 32, rgb: [0, 205, 0] },
|
|
17
|
+
{ code: 33, rgb: [205, 205, 0] },
|
|
18
|
+
{ code: 34, rgb: [0, 0, 238] },
|
|
19
|
+
{ code: 35, rgb: [205, 0, 205] },
|
|
20
|
+
{ code: 36, rgb: [0, 205, 205] },
|
|
21
|
+
{ code: 37, rgb: [229, 229, 229] },
|
|
22
|
+
{ code: 90, rgb: [127, 127, 127] },
|
|
23
|
+
{ code: 91, rgb: [255, 0, 0] },
|
|
24
|
+
{ code: 92, rgb: [0, 255, 0] },
|
|
25
|
+
{ code: 93, rgb: [255, 255, 0] },
|
|
26
|
+
{ code: 94, rgb: [92, 92, 255] },
|
|
27
|
+
{ code: 95, rgb: [255, 0, 255] },
|
|
28
|
+
{ code: 96, rgb: [0, 255, 255] },
|
|
29
|
+
{ code: 97, rgb: [255, 255, 255] },
|
|
30
|
+
];
|
|
31
|
+
const RESET = "\x1b[0m";
|
|
32
|
+
/** Parses a strict `#rrggbb` string; throws a `RangeError` naming the value otherwise. */
|
|
33
|
+
function parseHex(hex) {
|
|
34
|
+
const match = HEX_COLOR.exec(hex);
|
|
35
|
+
const [, r, g, b] = match ?? [];
|
|
36
|
+
if (r === undefined || g === undefined || b === undefined) {
|
|
37
|
+
throw new RangeError(`invalid hex color ${JSON.stringify(hex)}: expected #rrggbb`);
|
|
38
|
+
}
|
|
39
|
+
return [
|
|
40
|
+
Number.parseInt(r, 16),
|
|
41
|
+
Number.parseInt(g, 16),
|
|
42
|
+
Number.parseInt(b, 16),
|
|
43
|
+
];
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Decides whether `stream` should receive color escapes. `NO_COLOR` (any
|
|
47
|
+
* value, even empty) disables color outright; otherwise `FORCE_COLOR` set to
|
|
48
|
+
* anything but `"0"` enables it; otherwise color follows `stream.isTTY`.
|
|
49
|
+
*
|
|
50
|
+
* @example
|
|
51
|
+
* ```ts
|
|
52
|
+
* import process from "node:process";
|
|
53
|
+
* import { supportsColor } from "./term.js";
|
|
54
|
+
* if (supportsColor(process.stdout, process.env)) {
|
|
55
|
+
* // safe to emit escapes
|
|
56
|
+
* }
|
|
57
|
+
* ```
|
|
58
|
+
*/
|
|
59
|
+
export function supportsColor(stream, env) {
|
|
60
|
+
if (env["NO_COLOR"] !== undefined)
|
|
61
|
+
return false;
|
|
62
|
+
const forceColor = env["FORCE_COLOR"];
|
|
63
|
+
if (forceColor !== undefined)
|
|
64
|
+
return forceColor !== "0";
|
|
65
|
+
return stream.isTTY === true;
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Converts a `#rrggbb` color into a 24-bit SGR foreground escape.
|
|
69
|
+
*
|
|
70
|
+
* @throws RangeError if `hex` is not exactly `#` plus six hex digits.
|
|
71
|
+
* @example
|
|
72
|
+
* ```ts
|
|
73
|
+
* import { truecolorSgr } from "./term.js";
|
|
74
|
+
* truecolorSgr("#086d31"); // "\x1b[38;2;8;109;49m"
|
|
75
|
+
* ```
|
|
76
|
+
*/
|
|
77
|
+
export function truecolorSgr(hex) {
|
|
78
|
+
const [r, g, b] = parseHex(hex);
|
|
79
|
+
return `\x1b[38;2;${String(r)};${String(g)};${String(b)}m`;
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Converts a `#rrggbb` color into the SGR foreground escape of the nearest
|
|
83
|
+
* of the 16 standard ANSI colors (squared Euclidean RGB distance).
|
|
84
|
+
*
|
|
85
|
+
* @throws RangeError if `hex` is not exactly `#` plus six hex digits.
|
|
86
|
+
* @example
|
|
87
|
+
* ```ts
|
|
88
|
+
* import { nearest16Sgr } from "./term.js";
|
|
89
|
+
* nearest16Sgr("#cd0000"); // "\x1b[31m"
|
|
90
|
+
* ```
|
|
91
|
+
*/
|
|
92
|
+
export function nearest16Sgr(hex) {
|
|
93
|
+
const [r, g, b] = parseHex(hex);
|
|
94
|
+
let bestCode = 30;
|
|
95
|
+
let bestDistance = Number.POSITIVE_INFINITY;
|
|
96
|
+
for (const { code, rgb } of ANSI_16) {
|
|
97
|
+
const distance = (r - rgb[0]) ** 2 + (g - rgb[1]) ** 2 + (b - rgb[2]) ** 2;
|
|
98
|
+
if (distance < bestDistance) {
|
|
99
|
+
bestDistance = distance;
|
|
100
|
+
bestCode = code;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
return `\x1b[${String(bestCode)}m`;
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* Picks the palette theme from `COLORFGBG` (`"fg;bg"`): `"light"` when the
|
|
107
|
+
* last segment is `15` or `7` (a white/light-grey background), `"dark"`
|
|
108
|
+
* otherwise, including when the variable is absent or malformed.
|
|
109
|
+
*
|
|
110
|
+
* @example
|
|
111
|
+
* ```ts
|
|
112
|
+
* import { resolveThemeId } from "./term.js";
|
|
113
|
+
* resolveThemeId({ COLORFGBG: "0;15" }); // "light"
|
|
114
|
+
* ```
|
|
115
|
+
*/
|
|
116
|
+
export function resolveThemeId(env) {
|
|
117
|
+
const colorfgbg = env["COLORFGBG"];
|
|
118
|
+
if (colorfgbg?.includes(";") !== true)
|
|
119
|
+
return "dark";
|
|
120
|
+
const background = colorfgbg.slice(colorfgbg.lastIndexOf(";") + 1);
|
|
121
|
+
return background === "15" || background === "7" ? "light" : "dark";
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* Wraps `text` in the palette color for `role`, or returns it unchanged when
|
|
125
|
+
* {@link supportsColor} says `stream` should not receive color. Uses a 24-bit
|
|
126
|
+
* escape when `COLORTERM` is `truecolor`/`24bit`, the nearest ANSI-16 color
|
|
127
|
+
* otherwise.
|
|
128
|
+
*
|
|
129
|
+
* @example
|
|
130
|
+
* ```ts
|
|
131
|
+
* import process from "node:process";
|
|
132
|
+
* import { paint } from "./term.js";
|
|
133
|
+
* console.log(paint(process.stdout, "success", "done"));
|
|
134
|
+
* ```
|
|
135
|
+
*/
|
|
136
|
+
export function paint(stream, role, text, env = process.env) {
|
|
137
|
+
if (!supportsColor(stream, env))
|
|
138
|
+
return text;
|
|
139
|
+
const hex = PALETTE[resolveThemeId(env)][role];
|
|
140
|
+
const colorterm = env["COLORTERM"];
|
|
141
|
+
const truecolor = colorterm === "truecolor" || colorterm === "24bit";
|
|
142
|
+
const sgr = truecolor ? truecolorSgr(hex) : nearest16Sgr(hex);
|
|
143
|
+
return `${sgr}${text}${RESET}`;
|
|
144
|
+
}
|
|
145
|
+
//# sourceMappingURL=term.js.map
|
package/dist/tokens.js
CHANGED
package/dist/toolchain/grade.js
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
// SPDX-FileCopyrightText: Copyright the m3l-groundwork contributors
|
|
2
|
+
// SPDX-License-Identifier: MIT
|
|
1
3
|
/**
|
|
2
4
|
* Grades a project's TypeScript toolchain. `gradeToolchain` reads the tsconfig
|
|
3
5
|
* chains, ESLint and vitest configs, `package.json`, and the verify-step
|
|
@@ -7,8 +9,8 @@
|
|
|
7
9
|
*/
|
|
8
10
|
import { readFileSync } from "node:fs";
|
|
9
11
|
import { join } from "node:path";
|
|
10
|
-
import { readJsoncFile,
|
|
11
|
-
import {
|
|
12
|
+
import { readJsoncFile, stripJsComments } from "../jsonc.js";
|
|
13
|
+
import { walkBoundedForGrading } from "../survey/fs-walk.js";
|
|
12
14
|
import { RULES } from "./rules.js";
|
|
13
15
|
import { loadTsconfigChain } from "./tsconfig-chain.js";
|
|
14
16
|
import { TOOLCHAIN_CATEGORIES } from "./types.js";
|
|
@@ -45,7 +47,7 @@ function readRaw(path) {
|
|
|
45
47
|
/** File text with comments stripped, or `undefined` when unreadable. */
|
|
46
48
|
function readSource(path) {
|
|
47
49
|
try {
|
|
48
|
-
return
|
|
50
|
+
return stripJsComments(readFileSync(path, "utf8"));
|
|
49
51
|
}
|
|
50
52
|
catch {
|
|
51
53
|
return undefined;
|
|
@@ -72,21 +74,37 @@ function scrapeGateSteps(text) {
|
|
|
72
74
|
* Reads every `verify.mjs` invocation out of one YAML surface as data: the
|
|
73
75
|
* `--group`/`--step` each names. YAML is scraped, never parsed. An invocation
|
|
74
76
|
* naming neither (a matrix, or a bare full run) marks the surface `dynamic`.
|
|
77
|
+
* Comments are stripped per line before any `\` line continuation is joined,
|
|
78
|
+
* so a comment ending in `\` cannot swallow the next line. A quoted value is
|
|
79
|
+
* read unquoted. A mention inside an `echo` (in the same shell command, not an
|
|
80
|
+
* earlier `&&`/`;`/`|` segment) or inside a step's `name:` with no `run:`
|
|
81
|
+
* before it on the line is not an invocation.
|
|
75
82
|
*/
|
|
76
83
|
function scrapeLaneInvocations(text) {
|
|
77
84
|
const groups = [];
|
|
78
85
|
const steps = [];
|
|
79
86
|
let dynamic = false;
|
|
80
87
|
let seen = false;
|
|
81
|
-
|
|
82
|
-
|
|
88
|
+
const logicalLines = text
|
|
89
|
+
.split("\n")
|
|
90
|
+
.map((raw) => raw.replace(/(^|\s)#.*$/, ""))
|
|
91
|
+
.join("\n")
|
|
92
|
+
.replace(/\\\r?\n[ \t]*/g, " ")
|
|
93
|
+
.split("\n");
|
|
94
|
+
for (const line of logicalLines) {
|
|
83
95
|
const at = line.indexOf("verify.mjs");
|
|
84
96
|
if (at === -1)
|
|
85
97
|
continue;
|
|
98
|
+
const prefix = line.slice(0, at);
|
|
99
|
+
const lastSegment = prefix.split(/&&|\|\||[;|]/).pop() ?? "";
|
|
100
|
+
if (/\becho\b/.test(lastSegment))
|
|
101
|
+
continue;
|
|
102
|
+
if (/\bname\s*:/.test(prefix) && !/\brun\s*:/.test(prefix))
|
|
103
|
+
continue;
|
|
86
104
|
seen = true;
|
|
87
105
|
const rest = line.slice(at);
|
|
88
|
-
const group = /--group[ =]+([A-Za-z][\w-]*)
|
|
89
|
-
const step = /--step[ =]+([A-Za-z][\w-]*)
|
|
106
|
+
const group = /--group[ =]+["']?([A-Za-z][\w-]*)["']?/.exec(rest);
|
|
107
|
+
const step = /--step[ =]+["']?([A-Za-z][\w-]*)["']?/.exec(rest);
|
|
90
108
|
if (group?.[1] !== undefined)
|
|
91
109
|
groups.push(group[1]);
|
|
92
110
|
else if (step?.[1] !== undefined)
|
|
@@ -97,7 +115,7 @@ function scrapeLaneInvocations(text) {
|
|
|
97
115
|
return { seen, groups, steps, dynamic };
|
|
98
116
|
}
|
|
99
117
|
function loadSnapshot(root) {
|
|
100
|
-
const entries =
|
|
118
|
+
const entries = walkBoundedForGrading(root, PROJECT_WALK_DEPTH);
|
|
101
119
|
const projectFiles = new Set(entries.filter((e) => !e.isDirectory).map((e) => e.relPath));
|
|
102
120
|
const rootFiles = [...projectFiles].filter((p) => !p.includes("/"));
|
|
103
121
|
const packageRead = readJsoncFile(join(root, "package.json"));
|
|
@@ -1,10 +1,19 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* The toolchain grader's rules. Each rule is a pure function over a
|
|
3
3
|
* `ToolchainSnapshot` read once by `grade.ts`, so none of them touches the
|
|
4
|
-
* filesystem.
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
4
|
+
* filesystem. Rules come in two levels.
|
|
5
|
+
*
|
|
6
|
+
* Structural rules are pass/fail wiring checks: they catch defects `tsc` and
|
|
7
|
+
* ESLint do not -- a build project that emits nowhere, a verify step naming a
|
|
8
|
+
* script that does not exist -- and fail a gate.
|
|
9
|
+
*
|
|
10
|
+
* Rubric rules are a quality-judgement checklist rather than a wiring check:
|
|
11
|
+
* each scores how closely the project follows a recommended practice, and a
|
|
12
|
+
* miss only ever warns. What they encode is the floor -- the minimum baseline
|
|
13
|
+
* of practice that current official TypeScript / typescript-eslint guidance
|
|
14
|
+
* recommends (strict-family flags on, a modern module target, type-aware
|
|
15
|
+
* linting, and so on). A project can go beyond the floor; falling below it is
|
|
16
|
+
* what a rubric rule reports.
|
|
8
17
|
*
|
|
9
18
|
* `eslint.config.js`, `vitest.config.ts` and `verify-steps.mjs` are executable
|
|
10
19
|
* JavaScript, so their rules are regex scrapes over comment-stripped source,
|