@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,23 @@
|
|
|
1
|
+
// Single source of truth for the guarded source/test path shape used by the
|
|
2
|
+
// Claude hook layer to prevent hub-authored or branch-isolation writes into
|
|
3
|
+
// source and test trees. Shared by:
|
|
4
|
+
// - .claude/hooks/guard-branch-isolation.mjs (blocks writes while HEAD is main)
|
|
5
|
+
// - .claude/hooks/guard-hub-src-writes.mjs (blocks hub writes on any branch)
|
|
6
|
+
//
|
|
7
|
+
// Keeping the regex in one place means neither guard can silently diverge
|
|
8
|
+
// from the other when the protected glob set evolves.
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Returns true if `filePath` has any `src/` or `tests/` path segment --
|
|
12
|
+
* this single check covers a flat `src/`/`tests/` layout AND a nested one
|
|
13
|
+
* (`packages/<pkg>/src/`), since both contain the literal substring
|
|
14
|
+
* `/src/` preceded by a path boundary.
|
|
15
|
+
*
|
|
16
|
+
* Matches both relative and absolute paths (the `(^|\/)` anchor).
|
|
17
|
+
*
|
|
18
|
+
* @param {string} filePath
|
|
19
|
+
* @returns {boolean}
|
|
20
|
+
*/
|
|
21
|
+
export function isProtectedPath(filePath) {
|
|
22
|
+
return /(^|\/)src\//.test(filePath) || /(^|\/)tests\//.test(filePath);
|
|
23
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Shared structured-reporter contract every bin/check-*.mjs and bin/verify.mjs
|
|
4
|
+
* gate routes through, so output is consistent (human-readable by default,
|
|
5
|
+
* `--json` for machine consumption) without each gate reimplementing it.
|
|
6
|
+
*/
|
|
7
|
+
import { execFileSync } from "node:child_process";
|
|
8
|
+
|
|
9
|
+
/** True when `--json` is present in the given argv. */
|
|
10
|
+
export function parseJsonFlag(argv) {
|
|
11
|
+
return argv.includes("--json");
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/** The repository root, via `git rev-parse --show-toplevel`. */
|
|
15
|
+
export function repoRoot() {
|
|
16
|
+
return execFileSync("git", ["rev-parse", "--show-toplevel"], {
|
|
17
|
+
encoding: "utf8",
|
|
18
|
+
}).trim();
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Creates a reporter that accumulates `ok`/`warn`/`fail` lines, printing each
|
|
23
|
+
* immediately (unless `json`, in which case only `finish()` prints, as one
|
|
24
|
+
* JSON object). `finish()` sets `process.exitCode` (1 if anything failed) and
|
|
25
|
+
* returns whether the run passed.
|
|
26
|
+
*/
|
|
27
|
+
export function createReporter(json) {
|
|
28
|
+
const lines = [];
|
|
29
|
+
let failed = false;
|
|
30
|
+
|
|
31
|
+
function ok(message) {
|
|
32
|
+
lines.push({ level: "ok", message });
|
|
33
|
+
if (!json) console.log(` ok ${message}`);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function warn(message) {
|
|
37
|
+
lines.push({ level: "warn", message });
|
|
38
|
+
if (!json) console.warn(`warn ${message}`);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function fail(message) {
|
|
42
|
+
failed = true;
|
|
43
|
+
lines.push({ level: "fail", message });
|
|
44
|
+
if (!json) console.error(`fail ${message}`);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function finish() {
|
|
48
|
+
if (json) {
|
|
49
|
+
console.log(JSON.stringify({ ok: !failed, lines }, null, 2));
|
|
50
|
+
}
|
|
51
|
+
process.exitCode = failed ? 1 : 0;
|
|
52
|
+
return !failed;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
return { ok, warn, fail, finish };
|
|
56
|
+
}
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared git-signature helpers for the push-time guard.
|
|
3
|
+
*
|
|
4
|
+
* @see .claude/hooks/guard-git-push-signed.mjs -- the agent-side PreToolUse
|
|
5
|
+
* hook that inspects a `git push` Bash command before it runs.
|
|
6
|
+
*/
|
|
7
|
+
import { execFileSync } from "node:child_process";
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Signature codes that `git`'s `%G?` placeholder considers acceptable:
|
|
11
|
+
* G = a good (validly verified) signature,
|
|
12
|
+
* U = a good signature with unknown validity (signer key not in the local
|
|
13
|
+
* trust store) -- still cryptographically valid, so we accept it.
|
|
14
|
+
* Everything else -- N (none), B (bad), E (cannot check), X/Y/R
|
|
15
|
+
* (expired/revoked) -- is treated as unsigned/unverified.
|
|
16
|
+
*/
|
|
17
|
+
const VALID_SIGNATURE_CODES = new Set(["G", "U"]);
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Default git runner; returns stdout as a string. Injectable for tests.
|
|
21
|
+
* stderr is discarded so expected failures while probing candidate bases
|
|
22
|
+
* (e.g. "no upstream configured") don't leak into the hook's output -- the
|
|
23
|
+
* throw is what callers act on.
|
|
24
|
+
*/
|
|
25
|
+
function defaultRunGit(args) {
|
|
26
|
+
return execFileSync("git", args, {
|
|
27
|
+
encoding: "utf8",
|
|
28
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// `git` global options that consume the following token as their argument, so
|
|
33
|
+
// we can skip past them when hunting for the subcommand (e.g. `git -c k=v push`).
|
|
34
|
+
const GLOBAL_OPTS_WITH_ARG = new Set([
|
|
35
|
+
"-c",
|
|
36
|
+
"-C",
|
|
37
|
+
"--git-dir",
|
|
38
|
+
"--work-tree",
|
|
39
|
+
"--namespace",
|
|
40
|
+
"--exec-path",
|
|
41
|
+
]);
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Decide whether a shell command string performs a real `git push` (as opposed
|
|
45
|
+
* to a dry run or some other git subcommand). Handles `&&`/`||`/`;`/newline
|
|
46
|
+
* chains by inspecting each segment, and skips git global options.
|
|
47
|
+
*
|
|
48
|
+
* @param {string} command
|
|
49
|
+
* @returns {{ isPush: boolean, dryRun: boolean }}
|
|
50
|
+
*/
|
|
51
|
+
export function parseGitPush(command) {
|
|
52
|
+
if (typeof command !== "string") return { isPush: false, dryRun: false };
|
|
53
|
+
for (const segment of command.split(/&&|\|\||;|\n/)) {
|
|
54
|
+
const tokens = segment.trim().split(/\s+/).filter(Boolean);
|
|
55
|
+
let i = tokens.findIndex((t) => t === "git" || t.endsWith("/git"));
|
|
56
|
+
if (i === -1) continue;
|
|
57
|
+
i += 1;
|
|
58
|
+
while (i < tokens.length) {
|
|
59
|
+
const t = tokens[i];
|
|
60
|
+
if (GLOBAL_OPTS_WITH_ARG.has(t)) {
|
|
61
|
+
i += 2;
|
|
62
|
+
continue;
|
|
63
|
+
}
|
|
64
|
+
if (t.startsWith("-")) {
|
|
65
|
+
i += 1;
|
|
66
|
+
continue;
|
|
67
|
+
}
|
|
68
|
+
break;
|
|
69
|
+
}
|
|
70
|
+
if (tokens[i] !== "push") continue;
|
|
71
|
+
const rest = tokens.slice(i + 1);
|
|
72
|
+
const dryRun = rest.includes("--dry-run") || rest.includes("-n");
|
|
73
|
+
return { isPush: true, dryRun };
|
|
74
|
+
}
|
|
75
|
+
return { isPush: false, dryRun: false };
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* The commits that a push would send: everything reachable from `HEAD` but
|
|
80
|
+
* not from the branch's upstream or `origin/main` -- both that resolve are
|
|
81
|
+
* excluded together, not just the first. Local `main` is used only as a
|
|
82
|
+
* LAST RESORT, when neither remote ref resolves (e.g. a fresh local-only
|
|
83
|
+
* repo with no `origin`) -- never unioned in alongside them, since `main`
|
|
84
|
+
* can be `HEAD` itself and excluding it unconditionally would erase the
|
|
85
|
+
* very commits being pushed.
|
|
86
|
+
*
|
|
87
|
+
* Falls back to just `HEAD` when nothing resolves at all (a brand-new
|
|
88
|
+
* repo/branch). Already-pushed history is intentionally excluded -- we only
|
|
89
|
+
* vet what's new.
|
|
90
|
+
*
|
|
91
|
+
* @param {(args: string[]) => string} [runGit]
|
|
92
|
+
* @returns {string[]} commit SHAs (newest first), possibly empty
|
|
93
|
+
*/
|
|
94
|
+
export function outgoingCommits(runGit = defaultRunGit) {
|
|
95
|
+
const resolves = (ref) => {
|
|
96
|
+
try {
|
|
97
|
+
runGit(["rev-parse", "--verify", "--quiet", ref]);
|
|
98
|
+
return true;
|
|
99
|
+
} catch {
|
|
100
|
+
return false;
|
|
101
|
+
}
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
const excludeRefs = ["@{upstream}", "origin/main"].filter(resolves);
|
|
105
|
+
if (excludeRefs.length === 0 && resolves("main")) {
|
|
106
|
+
excludeRefs.push("main");
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
if (excludeRefs.length === 0) {
|
|
110
|
+
try {
|
|
111
|
+
return [runGit(["rev-parse", "HEAD"]).trim()].filter(Boolean);
|
|
112
|
+
} catch {
|
|
113
|
+
return [];
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
try {
|
|
117
|
+
const out = runGit(["rev-list", "HEAD", "--not", ...excludeRefs]);
|
|
118
|
+
return out
|
|
119
|
+
.split("\n")
|
|
120
|
+
.map((s) => s.trim())
|
|
121
|
+
.filter(Boolean);
|
|
122
|
+
} catch {
|
|
123
|
+
return [];
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* The `%G?` signature code for a single commit.
|
|
129
|
+
*
|
|
130
|
+
* @param {string} sha
|
|
131
|
+
* @param {(args: string[]) => string} [runGit]
|
|
132
|
+
* @returns {string}
|
|
133
|
+
*/
|
|
134
|
+
function commitSignatureCode(sha, runGit = defaultRunGit) {
|
|
135
|
+
return runGit(["show", "--no-patch", "--format=%G?", sha]).trim();
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Filter a list of commit SHAs down to those whose signature is missing or
|
|
140
|
+
* invalid (i.e. `%G?` is not in {@link VALID_SIGNATURE_CODES}). A commit whose
|
|
141
|
+
* code cannot be read is reported as unsigned rather than silently skipped.
|
|
142
|
+
*
|
|
143
|
+
* @param {string[]} shas
|
|
144
|
+
* @param {(args: string[]) => string} [runGit]
|
|
145
|
+
* @returns {{ sha: string, code: string }[]}
|
|
146
|
+
*/
|
|
147
|
+
export function unsignedCommits(shas, runGit = defaultRunGit) {
|
|
148
|
+
const bad = [];
|
|
149
|
+
for (const sha of shas) {
|
|
150
|
+
let code;
|
|
151
|
+
try {
|
|
152
|
+
code = commitSignatureCode(sha, runGit);
|
|
153
|
+
} catch {
|
|
154
|
+
code = "E";
|
|
155
|
+
}
|
|
156
|
+
if (!VALID_SIGNATURE_CODES.has(code)) bad.push({ sha, code });
|
|
157
|
+
}
|
|
158
|
+
return bad;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* Whether this machine's git is configured to sign commits at all
|
|
163
|
+
* (`commit.gpgsign`/`commit.gpgSign` = true, checked at any config scope).
|
|
164
|
+
* `guard-git-push-signed.mjs` uses this as its opt-in switch: a project that
|
|
165
|
+
* hasn't set up commit signing gets no enforcement (nothing to enforce would
|
|
166
|
+
* otherwise block every single push), and enabling signing locally turns the
|
|
167
|
+
* guard on with no other configuration step.
|
|
168
|
+
*
|
|
169
|
+
* @param {(args: string[]) => string} [runGit]
|
|
170
|
+
* @returns {boolean}
|
|
171
|
+
*/
|
|
172
|
+
export function signingEnabled(runGit = defaultRunGit) {
|
|
173
|
+
try {
|
|
174
|
+
return runGit(["config", "--get", "commit.gpgsign"]).trim() === "true";
|
|
175
|
+
} catch {
|
|
176
|
+
return false;
|
|
177
|
+
}
|
|
178
|
+
}
|