@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.
Files changed (151) hide show
  1. package/README.md +23 -0
  2. package/bin/m3l-groundwork.mjs +10 -0
  3. package/dist/assets.d.ts +20 -0
  4. package/dist/assets.js +79 -0
  5. package/dist/caps.d.ts +25 -0
  6. package/dist/caps.js +69 -0
  7. package/dist/conflicts.d.ts +12 -0
  8. package/dist/conflicts.js +77 -0
  9. package/dist/emit.d.ts +7 -0
  10. package/dist/emit.js +42 -0
  11. package/dist/git.d.ts +3 -0
  12. package/dist/git.js +9 -0
  13. package/dist/harness/conformance.d.ts +20 -0
  14. package/dist/harness/conformance.js +18 -0
  15. package/dist/harness/frontmatter.d.ts +38 -0
  16. package/dist/harness/frontmatter.js +204 -0
  17. package/dist/harness/grade.d.ts +4 -0
  18. package/dist/harness/grade.js +105 -0
  19. package/dist/harness/rules.d.ts +55 -0
  20. package/dist/harness/rules.js +580 -0
  21. package/dist/harness/types.d.ts +32 -0
  22. package/dist/harness/types.js +9 -0
  23. package/dist/inventory.d.ts +63 -0
  24. package/dist/inventory.js +66 -0
  25. package/dist/jsonc.d.ts +14 -0
  26. package/dist/jsonc.js +83 -0
  27. package/dist/main.d.ts +24 -0
  28. package/dist/main.js +297 -0
  29. package/dist/merge-json.d.ts +74 -0
  30. package/dist/merge-json.js +135 -0
  31. package/dist/mode.d.ts +19 -0
  32. package/dist/mode.js +53 -0
  33. package/dist/packs.d.ts +61 -0
  34. package/dist/packs.js +186 -0
  35. package/dist/plugin.d.ts +23 -0
  36. package/dist/plugin.js +79 -0
  37. package/dist/report.d.ts +4 -0
  38. package/dist/report.js +323 -0
  39. package/dist/survey/fs-walk.d.ts +14 -0
  40. package/dist/survey/fs-walk.js +60 -0
  41. package/dist/survey/survey-docs.d.ts +4 -0
  42. package/dist/survey/survey-docs.js +69 -0
  43. package/dist/survey/survey-harness.d.ts +4 -0
  44. package/dist/survey/survey-harness.js +121 -0
  45. package/dist/survey/survey-shape.d.ts +4 -0
  46. package/dist/survey/survey-shape.js +182 -0
  47. package/dist/survey/survey-toolchain.d.ts +4 -0
  48. package/dist/survey/survey-toolchain.js +217 -0
  49. package/dist/survey/survey.d.ts +5 -0
  50. package/dist/survey/survey.js +21 -0
  51. package/dist/survey/types.d.ts +117 -0
  52. package/dist/survey/types.js +8 -0
  53. package/dist/tokens.d.ts +13 -0
  54. package/dist/tokens.js +13 -0
  55. package/dist/toolchain/conformance.d.ts +20 -0
  56. package/dist/toolchain/conformance.js +30 -0
  57. package/dist/toolchain/grade.d.ts +4 -0
  58. package/dist/toolchain/grade.js +244 -0
  59. package/dist/toolchain/rules.d.ts +118 -0
  60. package/dist/toolchain/rules.js +706 -0
  61. package/dist/toolchain/tsconfig-chain.d.ts +36 -0
  62. package/dist/toolchain/tsconfig-chain.js +116 -0
  63. package/dist/toolchain/types.d.ts +27 -0
  64. package/dist/toolchain/types.js +9 -0
  65. package/package.json +59 -0
  66. package/plugin/skills/customize/SKILL.md +305 -0
  67. package/plugin/src/domain-map.ts +134 -0
  68. package/plugin/src/index.ts +4 -0
  69. package/plugin/src/kind-facet-map.ts +174 -0
  70. package/plugin/src/pack-map.ts +65 -0
  71. package/templates/core/.claude/agents/Explore.md +43 -0
  72. package/templates/core/.claude/agents/code-implementer.md +258 -0
  73. package/templates/core/.claude/agents/code-reviewer.md +163 -0
  74. package/templates/core/.claude/agents/silent-failure-hunter.md +191 -0
  75. package/templates/core/.claude/agents/test-author.md +211 -0
  76. package/templates/core/.claude/hooks/guard-branch-isolation.mjs +123 -0
  77. package/templates/core/.claude/hooks/guard-double-background.mjs +113 -0
  78. package/templates/core/.claude/hooks/guard-git-push-signed.mjs +90 -0
  79. package/templates/core/.claude/hooks/guard-hub-src-writes.mjs +88 -0
  80. package/templates/core/.claude/hooks/guard-js-extension.mjs +66 -0
  81. package/templates/core/.claude/hooks/guard-no-commonjs.mjs +105 -0
  82. package/templates/core/.claude/hooks/guard-protected-paths.mjs +45 -0
  83. package/templates/core/.claude/hooks/guard-secret-writes.mjs +183 -0
  84. package/templates/core/.claude/hooks/inject-decision-gate.mjs +119 -0
  85. package/templates/core/.claude/hooks/post-edit-verify.mjs +150 -0
  86. package/templates/core/.claude/rules/agent-dispatch.md +121 -0
  87. package/templates/core/.claude/rules/refactoring.md +52 -0
  88. package/templates/core/.claude/rules/src.md +114 -0
  89. package/templates/core/.claude/rules/tests.md +129 -0
  90. package/templates/core/.claude/settings.json +111 -0
  91. package/templates/core/.claude/skills/creating-prs/SKILL.md +132 -0
  92. package/templates/core/.claude/skills/finishing-work/SKILL.md +117 -0
  93. package/templates/core/.claude/skills/harness-guidance/SKILL.md +140 -0
  94. package/templates/core/.claude/skills/harness-guidance/references/official-sources.md +58 -0
  95. package/templates/core/.claude/skills/starting-work/SKILL.md +94 -0
  96. package/templates/core/.claude/skills/triaging-ci/SKILL.md +111 -0
  97. package/templates/core/.claude/skills/typescript-guidance/SKILL.md +143 -0
  98. package/templates/core/.claude/skills/typescript-guidance/references/typescript-sources.md +102 -0
  99. package/templates/core/.claude/skills/writing-commits/SKILL.md +248 -0
  100. package/templates/core/.github/workflows/ci.yml +123 -0
  101. package/templates/core/.github/workflows/dependency-review.yml +26 -0
  102. package/templates/core/.github/workflows/security-audit.yml +54 -0
  103. package/templates/core/.node-version +1 -0
  104. package/templates/core/.prettierignore +5 -0
  105. package/templates/core/.prettierrc.json +4 -0
  106. package/templates/core/CLAUDE.md +127 -0
  107. package/templates/core/README.md +24 -0
  108. package/templates/core/_gitignore +19 -0
  109. package/templates/core/_npmrc +1 -0
  110. package/templates/core/bin/check-exports.mjs +92 -0
  111. package/templates/core/bin/check-harness.mjs +27 -0
  112. package/templates/core/bin/check-node-version.mjs +51 -0
  113. package/templates/core/bin/check-toolchain.mjs +20 -0
  114. package/templates/core/bin/lib/agent-roster.mjs +8 -0
  115. package/templates/core/bin/lib/frontmatter.mjs +210 -0
  116. package/templates/core/bin/lib/harness-rules.mjs +916 -0
  117. package/templates/core/bin/lib/protected-paths.mjs +23 -0
  118. package/templates/core/bin/lib/report.mjs +56 -0
  119. package/templates/core/bin/lib/signed-range.mjs +178 -0
  120. package/templates/core/bin/lib/toolchain-rules.mjs +1264 -0
  121. package/templates/core/bin/lib/verify-steps.mjs +131 -0
  122. package/templates/core/bin/lib/verify-steps.packs.json +1 -0
  123. package/templates/core/bin/lint-commit.mjs +50 -0
  124. package/templates/core/bin/strip-claude-trailers.mjs +25 -0
  125. package/templates/core/bin/verify.mjs +64 -0
  126. package/templates/core/commitlint.config.js +11 -0
  127. package/templates/core/docs/research/harness-refresh.md +27 -0
  128. package/templates/core/docs/research/typescript-refresh.md +32 -0
  129. package/templates/core/eslint.config.js +105 -0
  130. package/templates/core/knip.json +6 -0
  131. package/templates/core/lefthook.yml +39 -0
  132. package/templates/core/package.json +58 -0
  133. package/templates/core/pnpm-workspace.yaml +13 -0
  134. package/templates/core/src/index.ts +12 -0
  135. package/templates/core/tests/index.test.ts +8 -0
  136. package/templates/core/tsconfig.base.json +36 -0
  137. package/templates/core/tsconfig.build.json +10 -0
  138. package/templates/core/tsconfig.json +11 -0
  139. package/templates/core/vitest.config.ts +32 -0
  140. package/templates/packs/README.md +81 -0
  141. package/templates/packs/harness-extras/files/.claude/agents/type-design-analyzer.md +188 -0
  142. package/templates/packs/harness-extras/files/.claude/hooks/guard-readonly-bash.mjs +324 -0
  143. package/templates/packs/harness-extras/files/.claude/hooks/reinject-compact-handoff.mjs +197 -0
  144. package/templates/packs/harness-extras/files/.claude/hooks/write-compact-handoff.mjs +180 -0
  145. package/templates/packs/harness-extras/files/bin/check-file-budget.mjs +407 -0
  146. package/templates/packs/harness-extras/files/bin/file-budget-baseline.json +1 -0
  147. package/templates/packs/harness-extras/pack.json +65 -0
  148. package/templates/packs/statusline/files/.claude/hooks/statusline-layout.mjs +365 -0
  149. package/templates/packs/statusline/files/.claude/hooks/statusline.mjs +996 -0
  150. package/templates/packs/statusline/files/.claude/hooks/subagent-statusline.mjs +203 -0
  151. 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
+ }