@monte3l/groundwork 0.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +23 -0
- package/bin/m3l-groundwork.mjs +10 -0
- package/dist/assets.d.ts +20 -0
- package/dist/assets.js +79 -0
- package/dist/caps.d.ts +25 -0
- package/dist/caps.js +69 -0
- package/dist/conflicts.d.ts +12 -0
- package/dist/conflicts.js +77 -0
- package/dist/emit.d.ts +7 -0
- package/dist/emit.js +42 -0
- package/dist/git.d.ts +3 -0
- package/dist/git.js +9 -0
- package/dist/harness/conformance.d.ts +20 -0
- package/dist/harness/conformance.js +18 -0
- package/dist/harness/frontmatter.d.ts +38 -0
- package/dist/harness/frontmatter.js +204 -0
- package/dist/harness/grade.d.ts +4 -0
- package/dist/harness/grade.js +105 -0
- package/dist/harness/rules.d.ts +55 -0
- package/dist/harness/rules.js +580 -0
- package/dist/harness/types.d.ts +32 -0
- package/dist/harness/types.js +9 -0
- package/dist/inventory.d.ts +63 -0
- package/dist/inventory.js +66 -0
- package/dist/jsonc.d.ts +14 -0
- package/dist/jsonc.js +83 -0
- package/dist/main.d.ts +24 -0
- package/dist/main.js +297 -0
- package/dist/merge-json.d.ts +74 -0
- package/dist/merge-json.js +135 -0
- package/dist/mode.d.ts +19 -0
- package/dist/mode.js +53 -0
- package/dist/packs.d.ts +61 -0
- package/dist/packs.js +186 -0
- package/dist/plugin.d.ts +23 -0
- package/dist/plugin.js +79 -0
- package/dist/report.d.ts +4 -0
- package/dist/report.js +323 -0
- package/dist/survey/fs-walk.d.ts +14 -0
- package/dist/survey/fs-walk.js +60 -0
- package/dist/survey/survey-docs.d.ts +4 -0
- package/dist/survey/survey-docs.js +69 -0
- package/dist/survey/survey-harness.d.ts +4 -0
- package/dist/survey/survey-harness.js +121 -0
- package/dist/survey/survey-shape.d.ts +4 -0
- package/dist/survey/survey-shape.js +182 -0
- package/dist/survey/survey-toolchain.d.ts +4 -0
- package/dist/survey/survey-toolchain.js +217 -0
- package/dist/survey/survey.d.ts +5 -0
- package/dist/survey/survey.js +21 -0
- package/dist/survey/types.d.ts +117 -0
- package/dist/survey/types.js +8 -0
- package/dist/tokens.d.ts +13 -0
- package/dist/tokens.js +13 -0
- package/dist/toolchain/conformance.d.ts +20 -0
- package/dist/toolchain/conformance.js +30 -0
- package/dist/toolchain/grade.d.ts +4 -0
- package/dist/toolchain/grade.js +244 -0
- package/dist/toolchain/rules.d.ts +118 -0
- package/dist/toolchain/rules.js +706 -0
- package/dist/toolchain/tsconfig-chain.d.ts +36 -0
- package/dist/toolchain/tsconfig-chain.js +116 -0
- package/dist/toolchain/types.d.ts +27 -0
- package/dist/toolchain/types.js +9 -0
- package/package.json +59 -0
- package/plugin/skills/customize/SKILL.md +305 -0
- package/plugin/src/domain-map.ts +134 -0
- package/plugin/src/index.ts +4 -0
- package/plugin/src/kind-facet-map.ts +174 -0
- package/plugin/src/pack-map.ts +65 -0
- package/templates/core/.claude/agents/Explore.md +43 -0
- package/templates/core/.claude/agents/code-implementer.md +258 -0
- package/templates/core/.claude/agents/code-reviewer.md +163 -0
- package/templates/core/.claude/agents/silent-failure-hunter.md +191 -0
- package/templates/core/.claude/agents/test-author.md +211 -0
- package/templates/core/.claude/hooks/guard-branch-isolation.mjs +123 -0
- package/templates/core/.claude/hooks/guard-double-background.mjs +113 -0
- package/templates/core/.claude/hooks/guard-git-push-signed.mjs +90 -0
- package/templates/core/.claude/hooks/guard-hub-src-writes.mjs +88 -0
- package/templates/core/.claude/hooks/guard-js-extension.mjs +66 -0
- package/templates/core/.claude/hooks/guard-no-commonjs.mjs +105 -0
- package/templates/core/.claude/hooks/guard-protected-paths.mjs +45 -0
- package/templates/core/.claude/hooks/guard-secret-writes.mjs +183 -0
- package/templates/core/.claude/hooks/inject-decision-gate.mjs +119 -0
- package/templates/core/.claude/hooks/post-edit-verify.mjs +150 -0
- package/templates/core/.claude/rules/agent-dispatch.md +121 -0
- package/templates/core/.claude/rules/refactoring.md +52 -0
- package/templates/core/.claude/rules/src.md +114 -0
- package/templates/core/.claude/rules/tests.md +129 -0
- package/templates/core/.claude/settings.json +111 -0
- package/templates/core/.claude/skills/creating-prs/SKILL.md +132 -0
- package/templates/core/.claude/skills/finishing-work/SKILL.md +117 -0
- package/templates/core/.claude/skills/harness-guidance/SKILL.md +140 -0
- package/templates/core/.claude/skills/harness-guidance/references/official-sources.md +58 -0
- package/templates/core/.claude/skills/starting-work/SKILL.md +94 -0
- package/templates/core/.claude/skills/triaging-ci/SKILL.md +111 -0
- package/templates/core/.claude/skills/typescript-guidance/SKILL.md +143 -0
- package/templates/core/.claude/skills/typescript-guidance/references/typescript-sources.md +102 -0
- package/templates/core/.claude/skills/writing-commits/SKILL.md +248 -0
- package/templates/core/.github/workflows/ci.yml +123 -0
- package/templates/core/.github/workflows/dependency-review.yml +26 -0
- package/templates/core/.github/workflows/security-audit.yml +54 -0
- package/templates/core/.node-version +1 -0
- package/templates/core/.prettierignore +5 -0
- package/templates/core/.prettierrc.json +4 -0
- package/templates/core/CLAUDE.md +127 -0
- package/templates/core/README.md +24 -0
- package/templates/core/_gitignore +19 -0
- package/templates/core/_npmrc +1 -0
- package/templates/core/bin/check-exports.mjs +92 -0
- package/templates/core/bin/check-harness.mjs +27 -0
- package/templates/core/bin/check-node-version.mjs +51 -0
- package/templates/core/bin/check-toolchain.mjs +20 -0
- package/templates/core/bin/lib/agent-roster.mjs +8 -0
- package/templates/core/bin/lib/frontmatter.mjs +210 -0
- package/templates/core/bin/lib/harness-rules.mjs +916 -0
- package/templates/core/bin/lib/protected-paths.mjs +23 -0
- package/templates/core/bin/lib/report.mjs +56 -0
- package/templates/core/bin/lib/signed-range.mjs +178 -0
- package/templates/core/bin/lib/toolchain-rules.mjs +1264 -0
- package/templates/core/bin/lib/verify-steps.mjs +131 -0
- package/templates/core/bin/lib/verify-steps.packs.json +1 -0
- package/templates/core/bin/lint-commit.mjs +50 -0
- package/templates/core/bin/strip-claude-trailers.mjs +25 -0
- package/templates/core/bin/verify.mjs +64 -0
- package/templates/core/commitlint.config.js +11 -0
- package/templates/core/docs/research/harness-refresh.md +27 -0
- package/templates/core/docs/research/typescript-refresh.md +32 -0
- package/templates/core/eslint.config.js +105 -0
- package/templates/core/knip.json +6 -0
- package/templates/core/lefthook.yml +39 -0
- package/templates/core/package.json +58 -0
- package/templates/core/pnpm-workspace.yaml +13 -0
- package/templates/core/src/index.ts +12 -0
- package/templates/core/tests/index.test.ts +8 -0
- package/templates/core/tsconfig.base.json +36 -0
- package/templates/core/tsconfig.build.json +10 -0
- package/templates/core/tsconfig.json +11 -0
- package/templates/core/vitest.config.ts +32 -0
- package/templates/packs/README.md +81 -0
- package/templates/packs/harness-extras/files/.claude/agents/type-design-analyzer.md +188 -0
- package/templates/packs/harness-extras/files/.claude/hooks/guard-readonly-bash.mjs +324 -0
- package/templates/packs/harness-extras/files/.claude/hooks/reinject-compact-handoff.mjs +197 -0
- package/templates/packs/harness-extras/files/.claude/hooks/write-compact-handoff.mjs +180 -0
- package/templates/packs/harness-extras/files/bin/check-file-budget.mjs +407 -0
- package/templates/packs/harness-extras/files/bin/file-budget-baseline.json +1 -0
- package/templates/packs/harness-extras/pack.json +65 -0
- package/templates/packs/statusline/files/.claude/hooks/statusline-layout.mjs +365 -0
- package/templates/packs/statusline/files/.claude/hooks/statusline.mjs +996 -0
- package/templates/packs/statusline/files/.claude/hooks/subagent-statusline.mjs +203 -0
- package/templates/packs/statusline/pack.json +31 -0
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* The single source of truth for what `pnpm verify` runs locally, what the
|
|
4
|
+
* lefthook `pre-push` lanes run, and what each `.github/workflows/ci.yml`
|
|
5
|
+
* job runs. Both YAML files name a *group*, never a step id: groups are a
|
|
6
|
+
* closed, five-member set (see GROUPS below); steps are not. That's what
|
|
7
|
+
* lets a new step -- core or pack-contributed -- join `pnpm verify` without
|
|
8
|
+
* ever touching either YAML file, and what keeps this the one list to keep
|
|
9
|
+
* in sync instead of three that agree by hand.
|
|
10
|
+
*
|
|
11
|
+
* Pack-installed steps live in verify-steps.packs.json, a plain JSON array
|
|
12
|
+
* the bootstrapper CLI appends to when installing a pack -- it has no JS
|
|
13
|
+
* parser and never gains one. A missing file means no packs are installed.
|
|
14
|
+
* A present-but-malformed file is a hard failure, never a silent fallback
|
|
15
|
+
* to core-only: a typo'd `group` would otherwise mean a step that runs in
|
|
16
|
+
* `pnpm verify` and in no gate at all, which is exactly the drift this file
|
|
17
|
+
* exists to prevent.
|
|
18
|
+
*
|
|
19
|
+
* Each step's `cmd` is the argv array to run (via `node:child_process`
|
|
20
|
+
* `spawnSync`, stdio inherited so failures are visible directly).
|
|
21
|
+
*/
|
|
22
|
+
import { readFileSync } from "node:fs";
|
|
23
|
+
import { fileURLToPath } from "node:url";
|
|
24
|
+
|
|
25
|
+
export const GROUPS = ["format", "lint", "typecheck", "build", "test"];
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* The gate steps this baseline ships, before any pack's are appended.
|
|
29
|
+
* @public Exported for the parity test that compares this file with its TypeScript twin in
|
|
30
|
+
* m3l-groundwork; nothing else in a bootstrapped project imports it.
|
|
31
|
+
*/
|
|
32
|
+
export const CORE_STEPS = [
|
|
33
|
+
{
|
|
34
|
+
id: "format",
|
|
35
|
+
group: "format",
|
|
36
|
+
name: "Format check",
|
|
37
|
+
cmd: ["pnpm", "format:check"],
|
|
38
|
+
},
|
|
39
|
+
{ id: "lint", group: "lint", name: "Lint", cmd: ["pnpm", "lint"] },
|
|
40
|
+
{
|
|
41
|
+
id: "harness",
|
|
42
|
+
group: "lint",
|
|
43
|
+
name: "Check Claude Code harness",
|
|
44
|
+
cmd: ["node", "bin/check-harness.mjs"],
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
id: "toolchain",
|
|
48
|
+
group: "lint",
|
|
49
|
+
name: "Check TypeScript toolchain",
|
|
50
|
+
cmd: ["node", "bin/check-toolchain.mjs"],
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
id: "knip",
|
|
54
|
+
group: "lint",
|
|
55
|
+
name: "Check unused code and dependencies",
|
|
56
|
+
cmd: ["pnpm", "knip"],
|
|
57
|
+
},
|
|
58
|
+
{
|
|
59
|
+
id: "typecheck",
|
|
60
|
+
group: "typecheck",
|
|
61
|
+
name: "Typecheck",
|
|
62
|
+
cmd: ["pnpm", "typecheck"],
|
|
63
|
+
},
|
|
64
|
+
{ id: "build", group: "build", name: "Build", cmd: ["pnpm", "build"] },
|
|
65
|
+
{
|
|
66
|
+
id: "test",
|
|
67
|
+
group: "test",
|
|
68
|
+
name: "Test (coverage)",
|
|
69
|
+
cmd: ["pnpm", "test:coverage"],
|
|
70
|
+
},
|
|
71
|
+
{
|
|
72
|
+
id: "exports",
|
|
73
|
+
group: "build",
|
|
74
|
+
name: "Check package exports",
|
|
75
|
+
cmd: ["node", "bin/check-exports.mjs"],
|
|
76
|
+
},
|
|
77
|
+
{
|
|
78
|
+
id: "node-version",
|
|
79
|
+
group: "build",
|
|
80
|
+
name: "Check Node version pin",
|
|
81
|
+
cmd: ["node", "bin/check-node-version.mjs"],
|
|
82
|
+
},
|
|
83
|
+
];
|
|
84
|
+
|
|
85
|
+
/** Reads and validates verify-steps.packs.json. Absence is fine (no packs installed). */
|
|
86
|
+
function readPackSteps() {
|
|
87
|
+
const path = fileURLToPath(
|
|
88
|
+
new URL("./verify-steps.packs.json", import.meta.url),
|
|
89
|
+
);
|
|
90
|
+
let raw;
|
|
91
|
+
try {
|
|
92
|
+
raw = readFileSync(path, "utf8");
|
|
93
|
+
} catch {
|
|
94
|
+
return [];
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
const parsed = JSON.parse(raw);
|
|
98
|
+
if (!Array.isArray(parsed)) {
|
|
99
|
+
throw new Error("verify-steps.packs.json must be a JSON array of steps");
|
|
100
|
+
}
|
|
101
|
+
for (const step of parsed) {
|
|
102
|
+
for (const field of ["id", "group", "name"]) {
|
|
103
|
+
if (typeof step?.[field] !== "string") {
|
|
104
|
+
throw new Error(
|
|
105
|
+
`verify-steps.packs.json: a step is missing "${field}"`,
|
|
106
|
+
);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
if (!Array.isArray(step.cmd) || step.cmd.length === 0) {
|
|
110
|
+
throw new Error(`verify-steps.packs.json: step "${step.id}" has no cmd`);
|
|
111
|
+
}
|
|
112
|
+
if (!GROUPS.includes(step.group)) {
|
|
113
|
+
throw new Error(
|
|
114
|
+
`verify-steps.packs.json: step "${step.id}" has unknown group "${step.group}" -- known: ${GROUPS.join(", ")}`,
|
|
115
|
+
);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
return parsed;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
export const VERIFY_STEPS = [...CORE_STEPS, ...readPackSteps()];
|
|
122
|
+
|
|
123
|
+
/** Look up one step by id, or `undefined` if no step has that id. */
|
|
124
|
+
export function findStep(id) {
|
|
125
|
+
return VERIFY_STEPS.find((step) => step.id === id);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/** All steps belonging to one group, in VERIFY_STEPS order. */
|
|
129
|
+
export function stepsInGroup(group) {
|
|
130
|
+
return VERIFY_STEPS.filter((step) => step.group === group);
|
|
131
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
[]
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Wraps `@commitlint/lint` + `@commitlint/load` directly (no `@commitlint/cli`
|
|
4
|
+
* dependency needed) against this project's `commitlint.config.js`. Also
|
|
5
|
+
* validates that no forbidden `Claude-*` trailer survived into the message
|
|
6
|
+
* (a backstop -- `strip-claude-trailers.mjs` runs first in the `commit-msg`
|
|
7
|
+
* hook and should have already removed any harness-injected one;
|
|
8
|
+
* `Co-Authored-By:` is never forbidden).
|
|
9
|
+
*
|
|
10
|
+
* Usage: `lint-commit.mjs --edit <path-to-commit-msg-file>` (lefthook's
|
|
11
|
+
* `commit-msg` hook contract) or `lint-commit.mjs <message>` directly.
|
|
12
|
+
*/
|
|
13
|
+
import process from "node:process";
|
|
14
|
+
import { readFileSync } from "node:fs";
|
|
15
|
+
import load from "@commitlint/load";
|
|
16
|
+
import lint from "@commitlint/lint";
|
|
17
|
+
|
|
18
|
+
const args = process.argv.slice(2);
|
|
19
|
+
const editIndex = args.indexOf("--edit");
|
|
20
|
+
const message =
|
|
21
|
+
editIndex === -1 ? args.join(" ") : readFileSync(args[editIndex + 1], "utf8");
|
|
22
|
+
|
|
23
|
+
const FORBIDDEN_TRAILER = /^Claude-(?!Session:)[A-Za-z-]*:/m;
|
|
24
|
+
|
|
25
|
+
function validateForbiddenTrailers(text) {
|
|
26
|
+
return !FORBIDDEN_TRAILER.test(text) && !/^Claude-Session:/m.test(text);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const config = await load({}, { file: "commitlint.config.js" });
|
|
30
|
+
const result = await lint(
|
|
31
|
+
message,
|
|
32
|
+
config.rules,
|
|
33
|
+
config.parserPreset ? { parserOpts: config.parserPreset.parserOpts } : {},
|
|
34
|
+
);
|
|
35
|
+
|
|
36
|
+
if (!validateForbiddenTrailers(message)) {
|
|
37
|
+
console.error(
|
|
38
|
+
"commit message carries a forbidden Claude-* trailer (only Co-Authored-By: is allowed)",
|
|
39
|
+
);
|
|
40
|
+
process.exit(1);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
if (!result.valid) {
|
|
44
|
+
for (const problem of result.errors) {
|
|
45
|
+
console.error(`✗ ${problem.message}`);
|
|
46
|
+
}
|
|
47
|
+
process.exit(1);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
console.log("✓ commit message is a valid Conventional Commit");
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* `commit-msg`: strips any harness-injected `Claude-*` trailer line from the
|
|
4
|
+
* commit message file in place before `lint-commit.mjs` validates it.
|
|
5
|
+
* `Co-Authored-By:` is never touched -- it is the one trailer this repo
|
|
6
|
+
* wants to keep.
|
|
7
|
+
*/
|
|
8
|
+
import process from "node:process";
|
|
9
|
+
import { readFileSync, writeFileSync } from "node:fs";
|
|
10
|
+
|
|
11
|
+
const path = process.argv[2];
|
|
12
|
+
if (!path) {
|
|
13
|
+
console.error("strip-claude-trailers: expected a commit-message file path");
|
|
14
|
+
process.exit(1);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const text = readFileSync(path, "utf8");
|
|
18
|
+
const stripped = text
|
|
19
|
+
.split("\n")
|
|
20
|
+
.filter((line) => !/^Claude-[A-Za-z-]*:/.test(line))
|
|
21
|
+
.join("\n");
|
|
22
|
+
|
|
23
|
+
if (stripped !== text) {
|
|
24
|
+
writeFileSync(path, stripped);
|
|
25
|
+
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* `pnpm verify` -- runs every step in VERIFY_STEPS (bin/lib/verify-steps.mjs)
|
|
4
|
+
* sequentially and reports pass/fail for each. `--step <id>` runs a single
|
|
5
|
+
* named step; `--group <name>` runs every step in one of the five fixed
|
|
6
|
+
* groups (format/lint/typecheck/build/test). `.github/workflows/ci.yml` and
|
|
7
|
+
* `lefthook.yml` each invoke `--group <name>`, never individual step ids, so
|
|
8
|
+
* a new step -- core or pack-contributed -- is picked up by both without
|
|
9
|
+
* either file changing.
|
|
10
|
+
*/
|
|
11
|
+
import process from "node:process";
|
|
12
|
+
import { spawnSync } from "node:child_process";
|
|
13
|
+
import {
|
|
14
|
+
VERIFY_STEPS,
|
|
15
|
+
GROUPS,
|
|
16
|
+
findStep,
|
|
17
|
+
stepsInGroup,
|
|
18
|
+
} from "./lib/verify-steps.mjs";
|
|
19
|
+
|
|
20
|
+
const args = process.argv.slice(2);
|
|
21
|
+
const stepIndex = args.indexOf("--step");
|
|
22
|
+
const requestedId = stepIndex === -1 ? null : args[stepIndex + 1];
|
|
23
|
+
const groupIndex = args.indexOf("--group");
|
|
24
|
+
const requestedGroup = groupIndex === -1 ? null : args[groupIndex + 1];
|
|
25
|
+
|
|
26
|
+
if (requestedGroup && !GROUPS.includes(requestedGroup)) {
|
|
27
|
+
console.error(
|
|
28
|
+
`verify: unknown group "${requestedGroup}" -- known groups: ${GROUPS.join(", ")}`,
|
|
29
|
+
);
|
|
30
|
+
process.exit(1);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const steps = requestedId
|
|
34
|
+
? [findStep(requestedId)].filter((step) => step !== undefined)
|
|
35
|
+
: requestedGroup
|
|
36
|
+
? stepsInGroup(requestedGroup)
|
|
37
|
+
: VERIFY_STEPS;
|
|
38
|
+
|
|
39
|
+
if (requestedId && steps.length === 0) {
|
|
40
|
+
console.error(
|
|
41
|
+
`verify: unknown step "${requestedId}" -- known ids: ${VERIFY_STEPS.map((s) => s.id).join(", ")}`,
|
|
42
|
+
);
|
|
43
|
+
process.exit(1);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
let failed = false;
|
|
47
|
+
for (const step of steps) {
|
|
48
|
+
console.log(`\n▶ ${step.name} (${step.id})`);
|
|
49
|
+
const [cmd, ...cmdArgs] = step.cmd;
|
|
50
|
+
const result = spawnSync(cmd, cmdArgs, { stdio: "inherit" });
|
|
51
|
+
if (result.status !== 0) {
|
|
52
|
+
failed = true;
|
|
53
|
+
console.error(`✗ ${step.name} failed`);
|
|
54
|
+
if (!requestedId && !requestedGroup) {
|
|
55
|
+
// Local `pnpm verify` (no --step/--group): keep going so a single
|
|
56
|
+
// early failure doesn't hide every other failing step in the same run.
|
|
57
|
+
continue;
|
|
58
|
+
}
|
|
59
|
+
break;
|
|
60
|
+
}
|
|
61
|
+
console.log(`✓ ${step.name}`);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
process.exit(failed ? 1 : 0);
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Conventional Commits, enforced on `commit-msg` via lefthook.
|
|
3
|
+
*
|
|
4
|
+
* `feat:` -> minor, `fix:` -> patch, `feat!:` / `BREAKING CHANGE:` -> major.
|
|
5
|
+
* Other types (`docs`, `refactor`, `test`, `chore`, ...) do not release.
|
|
6
|
+
*
|
|
7
|
+
* @type {import("@commitlint/types").UserConfig}
|
|
8
|
+
*/
|
|
9
|
+
export default {
|
|
10
|
+
extends: ["@commitlint/config-conventional"],
|
|
11
|
+
};
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# Harness refresh tracker
|
|
2
|
+
|
|
3
|
+
<!-- harness-refresh: last-verified=unset claude-code-version=unset -->
|
|
4
|
+
|
|
5
|
+
Living record of `harness-guidance` (refresh mode) sweeps — the per-facet,
|
|
6
|
+
per-source state a run diffs against, so each sweep reports what
|
|
7
|
+
**changed** since the last one instead of rediscovering the whole `.claude/`
|
|
8
|
+
surface from scratch. Updated **in place** on every run, never as a new
|
|
9
|
+
dated file.
|
|
10
|
+
|
|
11
|
+
This tracker has not had its first sweep yet (`last-verified=unset`). Run
|
|
12
|
+
`harness-guidance` in refresh mode — or let `/customize`'s guidance pass
|
|
13
|
+
seed it — to populate the sections below.
|
|
14
|
+
|
|
15
|
+
## Outstanding drift
|
|
16
|
+
|
|
17
|
+
_(none recorded yet — first sweep populates this section)_
|
|
18
|
+
|
|
19
|
+
## Facet state
|
|
20
|
+
|
|
21
|
+
| Facet | Last verified | Notes |
|
|
22
|
+
| ---------------------------- | ------------- | ----- |
|
|
23
|
+
| `models-tiering` | unset | |
|
|
24
|
+
| `cc-features-settings` | unset | |
|
|
25
|
+
| `agent-subagent-design` | unset | |
|
|
26
|
+
| `skills-context-engineering` | unset | |
|
|
27
|
+
| `hooks-lifecycle` | unset | |
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# TypeScript refresh tracker
|
|
2
|
+
|
|
3
|
+
<!-- typescript-refresh: last-verified=unset typescript-version=unset -->
|
|
4
|
+
|
|
5
|
+
Living record of `typescript-guidance` (refresh mode) sweeps — the
|
|
6
|
+
per-facet, per-source state a run diffs against, so each sweep reports what
|
|
7
|
+
**changed** since the last one instead of rediscovering the whole toolchain
|
|
8
|
+
from scratch. Updated **in place** on every run, never as a new dated file.
|
|
9
|
+
|
|
10
|
+
`typescript-version` in the header records the newest **upstream**
|
|
11
|
+
TypeScript release the last sweep verified against — deliberately distinct
|
|
12
|
+
from `package.json`'s own `typescript` devDependency pin, which this
|
|
13
|
+
tracker exists to check against, not restate. The two numbers disagreeing
|
|
14
|
+
is not a bug in the tracker; it's the finding.
|
|
15
|
+
|
|
16
|
+
This tracker has not had its first sweep yet (`last-verified=unset`). Run
|
|
17
|
+
`typescript-guidance` in refresh mode — or let `/customize`'s guidance pass
|
|
18
|
+
seed it — to populate the sections below.
|
|
19
|
+
|
|
20
|
+
## Outstanding drift
|
|
21
|
+
|
|
22
|
+
_(none recorded yet — first sweep populates this section)_
|
|
23
|
+
|
|
24
|
+
## Facet state
|
|
25
|
+
|
|
26
|
+
| Facet | Last verified | Notes |
|
|
27
|
+
| ---------------------------- | ------------- | ----- |
|
|
28
|
+
| `compiler-config-flags` | unset | |
|
|
29
|
+
| `modules-esm-node-interop` | unset | |
|
|
30
|
+
| `packaging-declaration-emit` | unset | |
|
|
31
|
+
| `lint-typing-rules` | unset | |
|
|
32
|
+
| `testing-language-features` | unset | |
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
// @ts-check
|
|
2
|
+
import js from "@eslint/js";
|
|
3
|
+
import { defineConfig } from "eslint/config";
|
|
4
|
+
import { configs } from "typescript-eslint";
|
|
5
|
+
import { importX } from "eslint-plugin-import-x";
|
|
6
|
+
import { createTypeScriptImportResolver } from "eslint-import-resolver-typescript";
|
|
7
|
+
import tsdoc from "eslint-plugin-tsdoc";
|
|
8
|
+
import globals from "globals";
|
|
9
|
+
|
|
10
|
+
const CJS_DIRNAME = "__dir" + "name";
|
|
11
|
+
const CJS_FILENAME = "__file" + "name";
|
|
12
|
+
|
|
13
|
+
export default defineConfig(
|
|
14
|
+
{
|
|
15
|
+
ignores: [
|
|
16
|
+
"**/dist/**",
|
|
17
|
+
"**/node_modules/**",
|
|
18
|
+
"**/coverage/**",
|
|
19
|
+
// .claude/agents|skills|rules contain only docs (prompts, reference
|
|
20
|
+
// data a skill reads, not code this project executes); hooks are the
|
|
21
|
+
// only code under .claude/ and stay linted below.
|
|
22
|
+
".claude/agents/**",
|
|
23
|
+
".claude/skills/**",
|
|
24
|
+
".claude/rules/**",
|
|
25
|
+
],
|
|
26
|
+
},
|
|
27
|
+
js.configs.recommended,
|
|
28
|
+
...configs.recommendedTypeChecked,
|
|
29
|
+
importX.flatConfigs.recommended,
|
|
30
|
+
importX.flatConfigs.typescript,
|
|
31
|
+
{
|
|
32
|
+
files: ["**/*.ts"],
|
|
33
|
+
linterOptions: {
|
|
34
|
+
// Stale eslint-disable directives are always a bug: they either never
|
|
35
|
+
// suppressed anything or the underlying finding was fixed, leaving
|
|
36
|
+
// noise that misleads reviewers.
|
|
37
|
+
reportUnusedDisableDirectives: "error",
|
|
38
|
+
},
|
|
39
|
+
languageOptions: {
|
|
40
|
+
parserOptions: {
|
|
41
|
+
projectService: {
|
|
42
|
+
allowDefaultProject: ["vitest.config.ts"],
|
|
43
|
+
},
|
|
44
|
+
tsconfigRootDir: import.meta.dirname,
|
|
45
|
+
},
|
|
46
|
+
},
|
|
47
|
+
settings: {
|
|
48
|
+
"import-x/resolver-next": [createTypeScriptImportResolver()],
|
|
49
|
+
},
|
|
50
|
+
rules: {
|
|
51
|
+
// --- ESM correctness: the #1 documented gotcha ---------------------
|
|
52
|
+
"import-x/extensions": [
|
|
53
|
+
"error",
|
|
54
|
+
"ignorePackages",
|
|
55
|
+
{ js: "always", ts: "never" },
|
|
56
|
+
],
|
|
57
|
+
|
|
58
|
+
// --- Strictness: no `any` in the public API -------------------------
|
|
59
|
+
"@typescript-eslint/no-explicit-any": "error",
|
|
60
|
+
"@typescript-eslint/no-non-null-assertion": "error",
|
|
61
|
+
"@typescript-eslint/no-floating-promises": "error",
|
|
62
|
+
"@typescript-eslint/switch-exhaustiveness-check": "error",
|
|
63
|
+
"@typescript-eslint/consistent-type-imports": "error",
|
|
64
|
+
"@typescript-eslint/no-unused-vars": [
|
|
65
|
+
"error",
|
|
66
|
+
{ argsIgnorePattern: "^_", varsIgnorePattern: "^_" },
|
|
67
|
+
],
|
|
68
|
+
|
|
69
|
+
// --- Style / design --------------------------------------------------
|
|
70
|
+
"prefer-const": "error",
|
|
71
|
+
"no-var": "error",
|
|
72
|
+
eqeqeq: ["error", "always", { null: "ignore" }],
|
|
73
|
+
|
|
74
|
+
// --- ESM only: ban CommonJS constructs -------------------------------
|
|
75
|
+
"no-restricted-globals": [
|
|
76
|
+
"error",
|
|
77
|
+
{ name: CJS_DIRNAME, message: "CommonJS only; this package is ESM." },
|
|
78
|
+
{ name: CJS_FILENAME, message: "CommonJS only; this package is ESM." },
|
|
79
|
+
{ name: "require", message: "CommonJS only; this package is ESM." },
|
|
80
|
+
],
|
|
81
|
+
"import-x/no-commonjs": "error",
|
|
82
|
+
},
|
|
83
|
+
},
|
|
84
|
+
{
|
|
85
|
+
files: ["src/**/*.ts"],
|
|
86
|
+
plugins: { tsdoc },
|
|
87
|
+
rules: {
|
|
88
|
+
"tsdoc/syntax": "warn",
|
|
89
|
+
"import-x/no-default-export": "error",
|
|
90
|
+
},
|
|
91
|
+
},
|
|
92
|
+
{
|
|
93
|
+
files: ["**/*.mjs", "**/*.js"],
|
|
94
|
+
...configs.disableTypeChecked,
|
|
95
|
+
},
|
|
96
|
+
{
|
|
97
|
+
files: ["bin/**/*.mjs", ".claude/hooks/**/*.mjs"],
|
|
98
|
+
languageOptions: {
|
|
99
|
+
globals: { ...globals.node },
|
|
100
|
+
},
|
|
101
|
+
rules: {
|
|
102
|
+
"import-x/no-unresolved": "off",
|
|
103
|
+
},
|
|
104
|
+
},
|
|
105
|
+
);
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://unpkg.com/knip@6/schema.json",
|
|
3
|
+
"entry": ["src/index.ts", "bin/*.mjs", ".claude/hooks/*.mjs"],
|
|
4
|
+
"project": ["src/**/*.ts", "bin/**/*.mjs", ".claude/hooks/**/*.mjs"],
|
|
5
|
+
"ignoreDependencies": ["@commitlint/config-conventional", "@commitlint/types"]
|
|
6
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# Git hooks, managed by Lefthook.
|
|
2
|
+
# Install/refresh with `pnpm exec lefthook install` (wired into `prepare`).
|
|
3
|
+
# yaml-language-server: $schema=https://json.schemastore.org/lefthook.json
|
|
4
|
+
|
|
5
|
+
# Fail closed when no lefthook binary resolves at all. Without this, a
|
|
6
|
+
# generated shim whose final else-branch only echoes "Can't find lefthook in
|
|
7
|
+
# PATH" falls through with exit 0, silently skipping every gate below.
|
|
8
|
+
assert_lefthook_installed: true
|
|
9
|
+
|
|
10
|
+
pre-commit:
|
|
11
|
+
parallel: true
|
|
12
|
+
commands:
|
|
13
|
+
lint:
|
|
14
|
+
glob: "**/*.ts"
|
|
15
|
+
run: pnpm exec eslint --fix {staged_files}
|
|
16
|
+
stage_fixed: true
|
|
17
|
+
format:
|
|
18
|
+
glob: "**/*.{ts,json,md,yml,yaml}"
|
|
19
|
+
run: pnpm exec prettier --write {staged_files}
|
|
20
|
+
stage_fixed: true
|
|
21
|
+
|
|
22
|
+
commit-msg:
|
|
23
|
+
commands:
|
|
24
|
+
commitlint:
|
|
25
|
+
run: node bin/strip-claude-trailers.mjs {1} && node bin/lint-commit.mjs --edit {1}
|
|
26
|
+
|
|
27
|
+
pre-push:
|
|
28
|
+
parallel: true
|
|
29
|
+
commands:
|
|
30
|
+
format:
|
|
31
|
+
run: node bin/verify.mjs --group format
|
|
32
|
+
lint:
|
|
33
|
+
run: node bin/verify.mjs --group lint
|
|
34
|
+
typecheck:
|
|
35
|
+
run: node bin/verify.mjs --group typecheck
|
|
36
|
+
build:
|
|
37
|
+
run: node bin/verify.mjs --group build
|
|
38
|
+
test:
|
|
39
|
+
run: node bin/verify.mjs --group test
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "__PROJECT_NAME__",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"private": true,
|
|
5
|
+
"type": "module",
|
|
6
|
+
"description": "Bootstrapped by m3l-groundwork.",
|
|
7
|
+
"packageManager": "pnpm@12.4.0",
|
|
8
|
+
"engines": {
|
|
9
|
+
"node": ">=24"
|
|
10
|
+
},
|
|
11
|
+
"exports": {
|
|
12
|
+
".": {
|
|
13
|
+
"types": "./dist/index.d.ts",
|
|
14
|
+
"import": "./dist/index.js"
|
|
15
|
+
}
|
|
16
|
+
},
|
|
17
|
+
"main": "./dist/index.js",
|
|
18
|
+
"types": "./dist/index.d.ts",
|
|
19
|
+
"files": [
|
|
20
|
+
"dist"
|
|
21
|
+
],
|
|
22
|
+
"scripts": {
|
|
23
|
+
"build": "tsc -b tsconfig.build.json",
|
|
24
|
+
"typecheck": "tsc -b --force",
|
|
25
|
+
"lint": "eslint .",
|
|
26
|
+
"format": "prettier --write . --cache --cache-strategy content",
|
|
27
|
+
"format:check": "prettier --check . --cache --cache-strategy content",
|
|
28
|
+
"test": "vitest run",
|
|
29
|
+
"test:coverage": "vitest run --coverage",
|
|
30
|
+
"knip": "knip",
|
|
31
|
+
"check:exports": "node bin/check-exports.mjs",
|
|
32
|
+
"check:node-version": "node bin/check-node-version.mjs",
|
|
33
|
+
"verify": "node bin/verify.mjs",
|
|
34
|
+
"prepare": "lefthook install"
|
|
35
|
+
},
|
|
36
|
+
"devDependencies": {
|
|
37
|
+
"@arethetypeswrong/cli": "^0.18.5",
|
|
38
|
+
"@commitlint/config-conventional": "^21.2.2",
|
|
39
|
+
"@commitlint/lint": "^21.2.2",
|
|
40
|
+
"@commitlint/load": "^21.2.2",
|
|
41
|
+
"@commitlint/types": "^21.2.0",
|
|
42
|
+
"@eslint/js": "^10.0.1",
|
|
43
|
+
"@types/node": "^24.13.3",
|
|
44
|
+
"@vitest/coverage-v8": "^4.1.11",
|
|
45
|
+
"eslint": "^10.9.1",
|
|
46
|
+
"eslint-import-resolver-typescript": "^4.4.5",
|
|
47
|
+
"eslint-plugin-import-x": "^4.17.1",
|
|
48
|
+
"eslint-plugin-tsdoc": "^0.5.2",
|
|
49
|
+
"globals": "^17.12.0",
|
|
50
|
+
"knip": "^6.34.0",
|
|
51
|
+
"lefthook": "^2.1.12",
|
|
52
|
+
"prettier": "^3.9.6",
|
|
53
|
+
"publint": "^0.3.24",
|
|
54
|
+
"typescript": "^6.0.3",
|
|
55
|
+
"typescript-eslint": "^8.69.0",
|
|
56
|
+
"vitest": "^4.1.11"
|
|
57
|
+
}
|
|
58
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# A single-package project still needs this file: pnpm 10+ moved
|
|
2
|
+
# install-script approval here (the `pnpm` field in package.json is no
|
|
3
|
+
# longer read).
|
|
4
|
+
allowBuilds:
|
|
5
|
+
# lefthook's postinstall fetches its platform binary; needed for the git
|
|
6
|
+
# hooks to actually work.
|
|
7
|
+
lefthook: true
|
|
8
|
+
# unrs-resolver is a native binary transitive dep of
|
|
9
|
+
# eslint-import-resolver-typescript; it works without its optional native
|
|
10
|
+
# acceleration, so this is explicitly false (not omitted) so pnpm skips
|
|
11
|
+
# the build with a notice instead of erroring when no compiler toolchain
|
|
12
|
+
# is available.
|
|
13
|
+
unrs-resolver: false
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Placeholder entry point — replace with your project's real public API.
|
|
3
|
+
*
|
|
4
|
+
* @example
|
|
5
|
+
* ```ts
|
|
6
|
+
* import { placeholder } from "./index.js";
|
|
7
|
+
* placeholder(); // "hello from this project"
|
|
8
|
+
* ```
|
|
9
|
+
*/
|
|
10
|
+
export function placeholder(): string {
|
|
11
|
+
return "hello from this project";
|
|
12
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json.schemastore.org/tsconfig",
|
|
3
|
+
"compilerOptions": {
|
|
4
|
+
"target": "es2023",
|
|
5
|
+
"lib": ["es2023"],
|
|
6
|
+
"types": ["node"],
|
|
7
|
+
"module": "nodenext",
|
|
8
|
+
"moduleResolution": "nodenext",
|
|
9
|
+
"strict": true,
|
|
10
|
+
"noUncheckedIndexedAccess": true,
|
|
11
|
+
"noImplicitOverride": true,
|
|
12
|
+
"noFallthroughCasesInSwitch": true,
|
|
13
|
+
"exactOptionalPropertyTypes": true,
|
|
14
|
+
"verbatimModuleSyntax": true,
|
|
15
|
+
"isolatedModules": true,
|
|
16
|
+
"noUncheckedSideEffectImports": true,
|
|
17
|
+
"noPropertyAccessFromIndexSignature": true,
|
|
18
|
+
"noImplicitReturns": true,
|
|
19
|
+
"allowUnreachableCode": false,
|
|
20
|
+
// isolatedDeclarations stays out of the base config deliberately: the
|
|
21
|
+
// build project sets it individually (see tsconfig.build.json), but the
|
|
22
|
+
// tooling project (tsconfig.json) sets declaration: false and includes
|
|
23
|
+
// tests/, which isolatedDeclarations does not tolerate.
|
|
24
|
+
//
|
|
25
|
+
// noUnusedLocals / noUnusedParameters are deliberately NOT set:
|
|
26
|
+
// @typescript-eslint/no-unused-vars already covers both at error level
|
|
27
|
+
// (eslint.config.js) with an `^_`-prefix escape hatch this codebase
|
|
28
|
+
// relies on; tsc's flags honor no such pattern for locals.
|
|
29
|
+
"declaration": true,
|
|
30
|
+
"declarationMap": true,
|
|
31
|
+
"sourceMap": true,
|
|
32
|
+
"composite": true,
|
|
33
|
+
"skipLibCheck": true,
|
|
34
|
+
"forceConsistentCasingInFileNames": true
|
|
35
|
+
}
|
|
36
|
+
}
|