@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,203 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * subagentStatusLine: renders a custom row body for each subagent shown in
4
+ * the agent panel (code.claude.com/docs/en/statusline#subagent-status-lines).
5
+ *
6
+ * The command receives one JSON object on stdin per refresh tick: the base
7
+ * hook fields, a `columns` field (usable row width), and a `tasks` array.
8
+ * Each task may carry `id`, `name`, `type`, `status`, `description`, `label`,
9
+ * `startTime`, `model`, `effort`, `contextWindowSize`, `tokenCount`,
10
+ * `tokenSamples`, `cwd` -- `model`/`contextWindowSize` require Claude Code
11
+ * v2.1.205+ and `effort` v2.1.214+. On an older version those fields are
12
+ * simply absent and the row omits them.
13
+ *
14
+ * Output is one JSON line per row to override: `{"id": "<task id>", "content":
15
+ * "<row body>"}`. A task is left with Claude Code's own default rendering
16
+ * (name · description · token count) by omitting it from the output entirely
17
+ * -- this happens whenever `id` or `name` is missing/unusable, rather than
18
+ * guessing at a row.
19
+ *
20
+ * The elapsed-time color thresholds (15/30 minutes) mark a subagent worth a
21
+ * glance and one that has probably stalled.
22
+ *
23
+ * Invariant: **no subprocess, no network** -- same as `statusline.mjs`: this
24
+ * is a plain local `node` invocation with no I/O beyond stdin/stdout.
25
+ *
26
+ * Advisory-only: any parse failure or malformed payload exits 0 with no
27
+ * output, leaving every row at Claude Code's own default rendering.
28
+ */
29
+ import { realpathSync } from "node:fs";
30
+ import process from "node:process";
31
+ import { fileURLToPath } from "node:url";
32
+ import {
33
+ DIM,
34
+ GREEN,
35
+ MAGENTA,
36
+ RED,
37
+ RESET,
38
+ YELLOW,
39
+ formatTokenCount,
40
+ truncateToWidth,
41
+ } from "./statusline-layout.mjs";
42
+
43
+ /** Elapsed-time threshold, in seconds, past which a row turns yellow. */
44
+ export const ELAPSED_WARN_THRESHOLD_SEC = 15 * 60;
45
+ /** Elapsed-time threshold, in seconds, past which a row turns red. */
46
+ export const ELAPSED_HIGH_THRESHOLD_SEC = 30 * 60;
47
+
48
+ /**
49
+ * @param {unknown} value a task's `startTime` field. The documented
50
+ * `subagentStatusLine` payload shape does not pin down whether this is an
51
+ * epoch-millisecond number or an ISO 8601 string, so both are accepted.
52
+ * @returns {number | null} epoch milliseconds, or null when unparseable.
53
+ */
54
+ export function parseStartTime(value) {
55
+ if (typeof value === "number" && Number.isFinite(value)) return value;
56
+ if (typeof value === "string") {
57
+ const parsed = Date.parse(value);
58
+ if (!Number.isNaN(parsed)) return parsed;
59
+ }
60
+ return null;
61
+ }
62
+
63
+ /**
64
+ * @param {number} elapsedSec seconds elapsed, always non-negative when
65
+ * called from {@link formatSubagentRow}.
66
+ * @returns {string} `"0m"`, `"NNm"`, or `"NhMMm"`.
67
+ */
68
+ export function formatElapsed(elapsedSec) {
69
+ const clamped = Math.max(0, Math.floor(elapsedSec));
70
+ const h = Math.floor(clamped / 3600);
71
+ const m = Math.floor((clamped % 3600) / 60);
72
+ return h > 0 ? `${h}h${String(m).padStart(2, "0")}m` : `${m}m`;
73
+ }
74
+
75
+ /**
76
+ * @param {number} elapsedSec
77
+ * @returns {string} GREEN under 15 minutes, YELLOW at 15-30, RED at 30+.
78
+ */
79
+ export function elapsedColor(elapsedSec) {
80
+ if (elapsedSec >= ELAPSED_HIGH_THRESHOLD_SEC) return RED;
81
+ if (elapsedSec >= ELAPSED_WARN_THRESHOLD_SEC) return YELLOW;
82
+ return GREEN;
83
+ }
84
+
85
+ /**
86
+ * @param {unknown} tokenCount
87
+ * @param {unknown} contextWindowSize
88
+ * @returns {string | null} `"12k/200k (6%)"`, or null when either field is
89
+ * absent/non-finite/non-positive — both require Claude Code v2.1.205+ and
90
+ * are omitted for a task whose model isn't resolved yet.
91
+ */
92
+ export function formatTokenFraction(tokenCount, contextWindowSize) {
93
+ if (typeof tokenCount !== "number" || !Number.isFinite(tokenCount))
94
+ return null;
95
+ if (
96
+ typeof contextWindowSize !== "number" ||
97
+ !Number.isFinite(contextWindowSize) ||
98
+ contextWindowSize <= 0
99
+ )
100
+ return null;
101
+ const pct = Math.round((tokenCount / contextWindowSize) * 100);
102
+ return `${formatTokenCount(tokenCount)}/${formatTokenCount(contextWindowSize)} (${pct}%)`;
103
+ }
104
+
105
+ /**
106
+ * @param {unknown} effort a task's `effort` field — one of the effort level
107
+ * strings, or a numeric token budget.
108
+ * @returns {string | null}
109
+ */
110
+ export function formatEffort(effort) {
111
+ if (typeof effort === "string" && effort.length > 0) return effort;
112
+ if (typeof effort === "number" && Number.isFinite(effort))
113
+ return formatTokenCount(effort);
114
+ return null;
115
+ }
116
+
117
+ /**
118
+ * @param {unknown} task one entry of the `tasks` array on the
119
+ * `subagentStatusLine` payload.
120
+ * @param {{ now?: unknown } | undefined} env `now` overrides `Date.now()`
121
+ * for deterministic tests.
122
+ * @returns {{ id: string, content: string } | null} an override line for
123
+ * this task, or null to leave Claude Code's default row rendering.
124
+ */
125
+ export function formatSubagentRow(task, env) {
126
+ if (typeof task !== "object" || task === null) return null;
127
+ const { id, name, effort, startTime, tokenCount, contextWindowSize } =
128
+ /** @type {Record<string, unknown>} */ (task);
129
+ if (typeof id !== "string" || id.length === 0) return null;
130
+ if (typeof name !== "string" || name.length === 0) return null;
131
+
132
+ const segments = [name];
133
+
134
+ const effortText = formatEffort(effort);
135
+ if (effortText !== null) segments.push(`${MAGENTA}${effortText}${RESET}`);
136
+
137
+ const tokenText = formatTokenFraction(tokenCount, contextWindowSize);
138
+ if (tokenText !== null) segments.push(tokenText);
139
+
140
+ const startMs = parseStartTime(startTime);
141
+ if (startMs !== null) {
142
+ const nowMs = typeof env?.now === "number" ? env.now : Date.now();
143
+ const elapsedSec = Math.max(0, (nowMs - startMs) / 1000);
144
+ const color = elapsedColor(elapsedSec);
145
+ segments.push(`${color}${formatElapsed(elapsedSec)}${RESET}`);
146
+ }
147
+
148
+ return { id, content: segments.join(`${DIM} · ${RESET}`) };
149
+ }
150
+
151
+ /**
152
+ * Whether this module is the process's entry point (as opposed to being
153
+ * imported by a test). `import.meta.url` is always the symlink-resolved path,
154
+ * but `process.argv[1]` is kept exactly as typed, so comparing them directly
155
+ * is false for any project under a symlinked directory -- macOS's `/tmp` and
156
+ * `/var`, a symlinked home or volume, a symlinked workspace on Linux -- and the
157
+ * script would then exit 0 having printed nothing at all.
158
+ *
159
+ * @returns {boolean}
160
+ */
161
+ function isEntryPoint() {
162
+ try {
163
+ return realpathSync(process.argv[1]) === fileURLToPath(import.meta.url);
164
+ } catch {
165
+ return false;
166
+ }
167
+ }
168
+
169
+ async function readStdin() {
170
+ const chunks = [];
171
+ for await (const chunk of process.stdin) chunks.push(chunk);
172
+ return Buffer.concat(chunks).toString("utf8");
173
+ }
174
+
175
+ // Only run when invoked directly, not when imported for testing.
176
+ if (isEntryPoint()) {
177
+ const raw = await readStdin();
178
+ try {
179
+ const payload = JSON.parse(raw);
180
+ const tasks = Array.isArray(payload?.tasks) ? payload.tasks : [];
181
+ const columns =
182
+ typeof payload?.columns === "number" && payload.columns > 0
183
+ ? payload.columns
184
+ : null;
185
+ const env = { now: Date.now() };
186
+
187
+ const lines = [];
188
+ for (const task of tasks) {
189
+ const row = formatSubagentRow(task, env);
190
+ if (row === null) continue;
191
+ const content =
192
+ columns === null ? row.content : truncateToWidth(row.content, columns);
193
+ lines.push(JSON.stringify({ id: row.id, content }));
194
+ }
195
+
196
+ if (lines.length > 0) process.stdout.write(`${lines.join("\n")}\n`);
197
+ } catch {
198
+ // Advisory-only: any parse failure or unexpected payload shape exits 0
199
+ // with no output, leaving every row at Claude Code's own default
200
+ // rendering — matches the file header's documented contract.
201
+ }
202
+ process.exit(0);
203
+ }
@@ -0,0 +1,31 @@
1
+ {
2
+ "schemaVersion": 1,
3
+ "name": "statusline",
4
+ "description": "A five-row Claude Code statusLine -- session, model, context, quota, work -- plus a per-subagent row renderer, both width-fit to the real terminal. statusLine is the only documented surface carrying live context-window pressure; no hook event receives token data.",
5
+ "modes": ["fresh", "adopt"],
6
+ "budget": {
7
+ "agents": 0,
8
+ "skills": 0,
9
+ "hooks": 3,
10
+ "workflows": 0,
11
+ "scripts": 0
12
+ },
13
+ "wiring": {
14
+ "settings": {},
15
+ "settingsTopLevel": {
16
+ "statusLine": {
17
+ "type": "command",
18
+ "command": "node \"$CLAUDE_PROJECT_DIR/.claude/hooks/statusline.mjs\"",
19
+ "refreshInterval": 30,
20
+ "padding": 1
21
+ },
22
+ "subagentStatusLine": {
23
+ "type": "command",
24
+ "command": "node \"$CLAUDE_PROJECT_DIR/.claude/hooks/subagent-statusline.mjs\""
25
+ }
26
+ },
27
+ "packageScripts": {},
28
+ "verifySteps": []
29
+ },
30
+ "adoptNotes": "The three scripts read only the stdin payload, .git/HEAD (via node:fs, never a git subprocess) and process.availableMemory()/os.totalmem(), so they install anywhere a .claude/ directory exists -- no dependency on the baseline's file layout, and no gate to wire. The one real adopt risk is the settings keys: a project that already defines a top-level statusLine or subagentStatusLine collides, and its existing value must be shown and decided on, never overwritten. A .claude/settings.local.json or the user's own ~/.claude/settings.json statusLine also shadows the project one -- check both before concluding the pack is wired. rate_limits.spend_limit and prompt_cache need Claude Code v2.1.251+, and the per-subagent model/contextWindowSize/effort fields need v2.1.205+/v2.1.214+; each renders only when present, so an older Claude Code degrades to fewer segments rather than breaking. The scripts behave the same on macOS and Linux; the memory segment uses process.availableMemory() (Node 22+), and on macOS with an older Node it is hidden rather than shown from os.freemem(), which undercounts there. Below roughly 40 columns the layout drops segments by priority rather than wrapping."
31
+ }