@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,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
|
+
}
|