@scriptgun/workerc 0.1.2 → 0.3.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 +35 -13
- package/dist/init.js +62 -14
- package/dist/merge-settings.d.ts +3 -1
- package/dist/merge-settings.js +40 -5
- package/dist/templates/CLAUDE.md.tmpl +17 -0
- package/dist/templates/agents/browser-agent.md +7 -0
- package/dist/templates/agents/commit-agent.md +8 -0
- package/dist/templates/agents/debugger-agent.md +256 -0
- package/dist/templates/agents/finder-agent.md +8 -0
- package/dist/templates/commands/plan.md +167 -0
- package/dist/templates/commands/update.md +29 -0
- package/dist/templates/hooks/post-edit-lint.sh.tmpl +9 -5
- package/dist/templates/hooks/post-edit-tracker.sh +4 -11
- package/dist/templates/hooks/session-start-startup.sh +81 -0
- package/dist/templates/hooks/workerc-check-update.cjs +66 -0
- package/dist/templates/hooks/workerc-statusline.cjs +93 -0
- package/dist/write-files.d.ts +3 -0
- package/dist/write-files.js +30 -1
- package/package.json +15 -15
package/README.md
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
# workerc
|
|
2
2
|
|
|
3
|
-
Claude Code session management — commands, hooks, and progress tracking.
|
|
3
|
+
Claude Code session management — commands, hooks, agents, and progress tracking.
|
|
4
4
|
|
|
5
|
-
workerc adds structured session workflows to any project using [Claude Code](https://docs.anthropic.com/en/docs/claude-code). It installs slash commands for managing work sessions, hooks that enforce progress tracking and code quality, and a progress file system that persists context across sessions.
|
|
5
|
+
workerc adds structured session workflows to any project using [Claude Code](https://docs.anthropic.com/en/docs/claude-code). It installs slash commands for managing work sessions, hooks that enforce progress tracking and code quality, agents for specialized tasks, and a progress file system that persists context across sessions.
|
|
6
6
|
|
|
7
7
|
## Install
|
|
8
8
|
|
|
@@ -12,16 +12,19 @@ npx @scriptgun/workerc init
|
|
|
12
12
|
|
|
13
13
|
This interactively sets up:
|
|
14
14
|
|
|
15
|
-
- **
|
|
16
|
-
- **
|
|
17
|
-
- **
|
|
15
|
+
- **12 slash commands** in `.claude/commands/workerc/`
|
|
16
|
+
- **7 hooks** in `.claude/hooks/`
|
|
17
|
+
- **4 agents** in `.claude/agents/`
|
|
18
|
+
- **Settings** in `.claude/settings.local.json` or `.claude/settings.json`
|
|
18
19
|
- **Progress directory** at `.claude/progress/`
|
|
20
|
+
- **Optional CLAUDE.md** scaffold
|
|
19
21
|
|
|
20
22
|
## Commands
|
|
21
23
|
|
|
22
24
|
| Command | Description |
|
|
23
25
|
|---------|-------------|
|
|
24
26
|
| `/workerc:new` | Start a new work session with optional spec |
|
|
27
|
+
| `/workerc:plan` | Refine spec with codebase context — turns intent into blueprint |
|
|
25
28
|
| `/workerc:resume` | Resume an unclaimed session |
|
|
26
29
|
| `/workerc:status` | Show current session progress |
|
|
27
30
|
| `/workerc:list` | List all progress files with status |
|
|
@@ -31,32 +34,51 @@ This interactively sets up:
|
|
|
31
34
|
| `/workerc:handoff` | Pause session with handoff notes |
|
|
32
35
|
| `/workerc:done` | Mark session complete |
|
|
33
36
|
| `/workerc:abort` | Abandon session |
|
|
37
|
+
| `/workerc:update` | Update workerc to latest version |
|
|
34
38
|
|
|
35
39
|
## Hooks
|
|
36
40
|
|
|
37
41
|
### Always installed
|
|
38
42
|
|
|
39
|
-
- **post-edit-tracker.sh** — Blocks edits if no progress file is active. Auto-claims pending sessions on first edit. Auto-tracks edited files. Enforces 15s freshness
|
|
40
|
-
- **session-start-compact.sh** — Re-injects session context after
|
|
43
|
+
- **post-edit-tracker.sh** — Blocks edits if no progress file is active. Auto-claims pending sessions on first edit. Auto-tracks edited files. Enforces 15s freshness.
|
|
44
|
+
- **session-start-compact.sh** — Re-injects session context after conversation compaction. Restores progress file, spec, and file list.
|
|
45
|
+
- **session-start-startup.sh** — Injects active progress context on every session start, not just compaction.
|
|
46
|
+
- **workerc-check-update.cjs** — Checks npm registry for new workerc versions in background. Caches result for statusline.
|
|
47
|
+
- **workerc-statusline.cjs** — Shows model, current task, directory, and context usage bar with color thresholds.
|
|
41
48
|
|
|
42
49
|
### Optional (auto-detected)
|
|
43
50
|
|
|
44
51
|
- **post-edit-lint.sh** — Runs linter on every file edit. Detected tools: Biome, ESLint.
|
|
45
52
|
- **post-edit-types.sh** — Runs type checker on every file edit. Detected tools: TypeScript (`tsc`).
|
|
46
53
|
|
|
54
|
+
## Agents
|
|
55
|
+
|
|
56
|
+
| Agent | Description |
|
|
57
|
+
|-------|-------------|
|
|
58
|
+
| **browser** | Web browsing and automation via Playwright MCP |
|
|
59
|
+
| **commit** | Single-line git commits (`type(scope?): message`) |
|
|
60
|
+
| **finder** | Fast codebase search using Haiku model |
|
|
61
|
+
| **debugger** | Scientific method debugging with persistent debug sessions |
|
|
62
|
+
|
|
47
63
|
## How it works
|
|
48
64
|
|
|
49
|
-
1. Run `/workerc:new` to start a session — creates a progress file
|
|
50
|
-
2.
|
|
51
|
-
3.
|
|
52
|
-
4.
|
|
53
|
-
5.
|
|
65
|
+
1. Run `/workerc:new` to start a session — creates a progress file and optional spec
|
|
66
|
+
2. Run `/workerc:plan` to refine the spec with real codebase context
|
|
67
|
+
3. The tracker hook auto-claims the session on your first file edit
|
|
68
|
+
4. As you work, the tracker logs edited files and enforces regular progress updates
|
|
69
|
+
5. The statusline shows your current task, model, and context usage
|
|
70
|
+
6. Use `/workerc:commit` to commit, `/workerc:review` to check against spec
|
|
71
|
+
7. Use `/workerc:handoff` to pause or `/workerc:done` to complete
|
|
54
72
|
|
|
55
73
|
Progress files persist across sessions — use `/workerc:resume` to pick up where you left off.
|
|
56
74
|
|
|
57
75
|
## Re-running init
|
|
58
76
|
|
|
59
|
-
`workerc init` is idempotent. Running it again updates all commands and
|
|
77
|
+
`workerc init` is idempotent. Running it again updates all commands, hooks, and agents without duplicating settings entries.
|
|
78
|
+
|
|
79
|
+
## Changelog
|
|
80
|
+
|
|
81
|
+
See [CHANGELOG.md](./CHANGELOG.md) for release history.
|
|
60
82
|
|
|
61
83
|
## License
|
|
62
84
|
|
package/dist/init.js
CHANGED
|
@@ -1,10 +1,33 @@
|
|
|
1
|
+
import { existsSync } from "node:fs";
|
|
2
|
+
import { join } from "node:path";
|
|
1
3
|
import * as p from "@clack/prompts";
|
|
2
4
|
import { detect } from "./detect.js";
|
|
3
5
|
import { mergeSettings } from "./merge-settings.js";
|
|
4
6
|
import { writeFiles } from "./write-files.js";
|
|
5
7
|
export async function init(projectDir) {
|
|
6
8
|
p.intro("workerc init");
|
|
7
|
-
/* Step 1:
|
|
9
|
+
/* Step 1: Where to install settings */
|
|
10
|
+
const targetAnswer = await p.select({
|
|
11
|
+
message: "Where should hooks be configured?",
|
|
12
|
+
options: [
|
|
13
|
+
{
|
|
14
|
+
label: "Local (settings.local.json)",
|
|
15
|
+
value: "local",
|
|
16
|
+
hint: "just for you, gitignored",
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
label: "Shared (settings.json)",
|
|
20
|
+
value: "shared",
|
|
21
|
+
hint: "committed, shared with team",
|
|
22
|
+
},
|
|
23
|
+
],
|
|
24
|
+
});
|
|
25
|
+
if (p.isCancel(targetAnswer)) {
|
|
26
|
+
p.cancel("Cancelled");
|
|
27
|
+
process.exit(0);
|
|
28
|
+
}
|
|
29
|
+
const target = targetAnswer;
|
|
30
|
+
/* Step 2: Detect tools */
|
|
8
31
|
const spinner = p.spinner();
|
|
9
32
|
spinner.start("Detecting project tools");
|
|
10
33
|
const detected = detect(projectDir);
|
|
@@ -18,7 +41,7 @@ export async function init(projectDir) {
|
|
|
18
41
|
if (!detected.linter && !detected.typeChecker) {
|
|
19
42
|
p.log.info("No linter or type checker detected");
|
|
20
43
|
}
|
|
21
|
-
/* Step
|
|
44
|
+
/* Step 3: Lint hook */
|
|
22
45
|
let enableLint = false;
|
|
23
46
|
let lintCommand = "";
|
|
24
47
|
let lintExtensions = "";
|
|
@@ -54,7 +77,7 @@ export async function init(projectDir) {
|
|
|
54
77
|
lintExtensions = extAnswer;
|
|
55
78
|
}
|
|
56
79
|
}
|
|
57
|
-
/* Step
|
|
80
|
+
/* Step 4: Type check hook */
|
|
58
81
|
let enableTypes = false;
|
|
59
82
|
let typeCommand = "";
|
|
60
83
|
let typeExtensions = "";
|
|
@@ -90,7 +113,7 @@ export async function init(projectDir) {
|
|
|
90
113
|
typeExtensions = extAnswer;
|
|
91
114
|
}
|
|
92
115
|
}
|
|
93
|
-
/* Step
|
|
116
|
+
/* Step 5: Build active hooks description for compact hook */
|
|
94
117
|
const hookDescriptions = [];
|
|
95
118
|
if (enableLint) {
|
|
96
119
|
hookDescriptions.push(`- ${lintHookFilename} (blocks on lint/format errors)`);
|
|
@@ -99,8 +122,25 @@ export async function init(projectDir) {
|
|
|
99
122
|
hookDescriptions.push(`- ${typesHookFilename} (blocks on type errors)`);
|
|
100
123
|
}
|
|
101
124
|
hookDescriptions.push("- post-edit-tracker.sh (blocks if progress not updated within 15s)");
|
|
125
|
+
hookDescriptions.push("- workerc-check-update.cjs (checks for workerc updates on session start)");
|
|
126
|
+
hookDescriptions.push("- workerc-statusline.cjs (shows model, task, directory, context usage)");
|
|
127
|
+
hookDescriptions.push("- session-start-startup.sh (injects active progress on session start)");
|
|
102
128
|
const activeHooksDescription = hookDescriptions.join("\n");
|
|
103
|
-
/* Step
|
|
129
|
+
/* Step 6: CLAUDE.md */
|
|
130
|
+
let enableClaudeMd = false;
|
|
131
|
+
const claudeMdExists = existsSync(join(projectDir, ".claude", "CLAUDE.md"));
|
|
132
|
+
if (!claudeMdExists) {
|
|
133
|
+
const claudeMdAnswer = await p.confirm({
|
|
134
|
+
message: "Scaffold .claude/CLAUDE.md?",
|
|
135
|
+
initialValue: true,
|
|
136
|
+
});
|
|
137
|
+
if (p.isCancel(claudeMdAnswer)) {
|
|
138
|
+
p.cancel("Cancelled");
|
|
139
|
+
process.exit(0);
|
|
140
|
+
}
|
|
141
|
+
enableClaudeMd = claudeMdAnswer;
|
|
142
|
+
}
|
|
143
|
+
/* Step 7: Write files */
|
|
104
144
|
spinner.start("Writing files");
|
|
105
145
|
const result = writeFiles({
|
|
106
146
|
projectDir,
|
|
@@ -113,32 +153,40 @@ export async function init(projectDir) {
|
|
|
113
153
|
typeExtensions,
|
|
114
154
|
typesHookFilename,
|
|
115
155
|
activeHooksDescription,
|
|
156
|
+
enableClaudeMd,
|
|
116
157
|
});
|
|
117
158
|
spinner.stop("Files written");
|
|
118
|
-
/* Step
|
|
159
|
+
/* Step 8: Merge settings */
|
|
119
160
|
spinner.start("Updating settings");
|
|
120
|
-
mergeSettings({
|
|
161
|
+
const settingsFile = mergeSettings({
|
|
121
162
|
projectDir,
|
|
163
|
+
target,
|
|
122
164
|
enableLint,
|
|
123
165
|
lintHookFilename,
|
|
124
166
|
enableTypes,
|
|
125
167
|
typesHookFilename,
|
|
126
168
|
});
|
|
127
169
|
spinner.stop("Settings updated");
|
|
128
|
-
/* Step
|
|
170
|
+
/* Step 9: Summary */
|
|
129
171
|
p.log.success("workerc installed");
|
|
130
|
-
|
|
172
|
+
const summary = [
|
|
131
173
|
"",
|
|
132
|
-
|
|
174
|
+
`Commands (${result.commands.length}):`,
|
|
133
175
|
...result.commands.map((f) => ` .claude/commands/workerc/${f}`),
|
|
134
176
|
"",
|
|
135
177
|
"Hooks:",
|
|
136
178
|
...result.hooks.map((f) => ` .claude/hooks/${f}`),
|
|
137
179
|
"",
|
|
138
|
-
"
|
|
139
|
-
|
|
180
|
+
"Agents:",
|
|
181
|
+
...result.agents.map((f) => ` .claude/agents/${f}`),
|
|
140
182
|
"",
|
|
141
|
-
|
|
142
|
-
|
|
183
|
+
`Settings: .claude/${settingsFile}`,
|
|
184
|
+
"Progress: .claude/progress/",
|
|
185
|
+
];
|
|
186
|
+
if (result.claudeMd) {
|
|
187
|
+
summary.push("CLAUDE.md: .claude/CLAUDE.md (customize it)");
|
|
188
|
+
}
|
|
189
|
+
summary.push("", "Get started: run /workerc:new in Claude Code");
|
|
190
|
+
p.log.message(summary.join("\n"));
|
|
143
191
|
p.outro("done");
|
|
144
192
|
}
|
package/dist/merge-settings.d.ts
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
|
+
export type SettingsTarget = "local" | "shared";
|
|
1
2
|
export interface MergeOptions {
|
|
2
3
|
projectDir: string;
|
|
4
|
+
target: SettingsTarget;
|
|
3
5
|
enableLint: boolean;
|
|
4
6
|
lintHookFilename: string;
|
|
5
7
|
enableTypes: boolean;
|
|
6
8
|
typesHookFilename: string;
|
|
7
9
|
}
|
|
8
|
-
export declare function mergeSettings(options: MergeOptions):
|
|
10
|
+
export declare function mergeSettings(options: MergeOptions): string;
|
package/dist/merge-settings.js
CHANGED
|
@@ -2,13 +2,16 @@ import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
|
2
2
|
import { dirname, join } from "node:path";
|
|
3
3
|
/**
|
|
4
4
|
* Identifies workerc-managed hooks by checking if the command path
|
|
5
|
-
* contains
|
|
5
|
+
* contains one of our known script names.
|
|
6
6
|
*/
|
|
7
7
|
const WORKERC_HOOK_NAMES = [
|
|
8
8
|
"session-start-compact.sh",
|
|
9
9
|
"post-edit-tracker.sh",
|
|
10
10
|
"post-edit-lint.sh",
|
|
11
11
|
"post-edit-types.sh",
|
|
12
|
+
"workerc-check-update.cjs",
|
|
13
|
+
"workerc-statusline.cjs",
|
|
14
|
+
"session-start-startup.sh",
|
|
12
15
|
];
|
|
13
16
|
function isWorkercHook(hook) {
|
|
14
17
|
return WORKERC_HOOK_NAMES.some((name) => hook.command.includes(name));
|
|
@@ -17,7 +20,10 @@ function isWorkercGroup(group) {
|
|
|
17
20
|
return group.hooks.every(isWorkercHook);
|
|
18
21
|
}
|
|
19
22
|
export function mergeSettings(options) {
|
|
20
|
-
const
|
|
23
|
+
const filename = options.target === "shared"
|
|
24
|
+
? "settings.json"
|
|
25
|
+
: "settings.local.json";
|
|
26
|
+
const settingsPath = join(options.projectDir, ".claude", filename);
|
|
21
27
|
const settingsDir = dirname(settingsPath);
|
|
22
28
|
if (!existsSync(settingsDir)) {
|
|
23
29
|
mkdirSync(settingsDir, { recursive: true });
|
|
@@ -30,7 +36,7 @@ export function mergeSettings(options) {
|
|
|
30
36
|
if (!settings.hooks) {
|
|
31
37
|
settings.hooks = {};
|
|
32
38
|
}
|
|
33
|
-
/* SessionStart:
|
|
39
|
+
/* SessionStart: keep non-workerc hooks, append compact */
|
|
34
40
|
const existingSessionStart = (settings.hooks.SessionStart ?? []).filter((g) => !isWorkercGroup(g));
|
|
35
41
|
const compactHook = {
|
|
36
42
|
matcher: "compact",
|
|
@@ -41,8 +47,31 @@ export function mergeSettings(options) {
|
|
|
41
47
|
},
|
|
42
48
|
],
|
|
43
49
|
};
|
|
44
|
-
|
|
45
|
-
|
|
50
|
+
const checkUpdateHook = {
|
|
51
|
+
matcher: "",
|
|
52
|
+
hooks: [
|
|
53
|
+
{
|
|
54
|
+
type: "command",
|
|
55
|
+
command: 'node "$CLAUDE_PROJECT_DIR"/.claude/hooks/workerc-check-update.cjs',
|
|
56
|
+
},
|
|
57
|
+
],
|
|
58
|
+
};
|
|
59
|
+
const startupHook = {
|
|
60
|
+
matcher: "",
|
|
61
|
+
hooks: [
|
|
62
|
+
{
|
|
63
|
+
type: "command",
|
|
64
|
+
command: '"$CLAUDE_PROJECT_DIR"/.claude/hooks/session-start-startup.sh',
|
|
65
|
+
},
|
|
66
|
+
],
|
|
67
|
+
};
|
|
68
|
+
settings.hooks.SessionStart = [
|
|
69
|
+
...existingSessionStart,
|
|
70
|
+
startupHook,
|
|
71
|
+
compactHook,
|
|
72
|
+
checkUpdateHook,
|
|
73
|
+
];
|
|
74
|
+
/* PostToolUse: keep non-workerc hooks, append workerc hooks */
|
|
46
75
|
const existingPostToolUse = (settings.hooks.PostToolUse ?? []).filter((g) => !isWorkercGroup(g));
|
|
47
76
|
const newPostToolUse = [];
|
|
48
77
|
if (options.enableLint) {
|
|
@@ -78,5 +107,11 @@ export function mergeSettings(options) {
|
|
|
78
107
|
],
|
|
79
108
|
});
|
|
80
109
|
settings.hooks.PostToolUse = [...existingPostToolUse, ...newPostToolUse];
|
|
110
|
+
/* Statusline: always overwrite with workerc's */
|
|
111
|
+
settings.statusLine = {
|
|
112
|
+
type: "command",
|
|
113
|
+
command: 'node "$CLAUDE_PROJECT_DIR"/.claude/hooks/workerc-statusline.cjs',
|
|
114
|
+
};
|
|
81
115
|
writeFileSync(settingsPath, JSON.stringify(settings, null, "\t") + "\n");
|
|
116
|
+
return filename;
|
|
82
117
|
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
## Behavior
|
|
2
|
+
- concise responses
|
|
3
|
+
- read relevant docs before work
|
|
4
|
+
- root cause > quick fix
|
|
5
|
+
|
|
6
|
+
## Code Style
|
|
7
|
+
<!-- customize for your project -->
|
|
8
|
+
|
|
9
|
+
## Repo
|
|
10
|
+
<!-- describe your repo structure, package manager, build tools -->
|
|
11
|
+
|
|
12
|
+
## Agents
|
|
13
|
+
| agent | purpose |
|
|
14
|
+
|-------|---------|
|
|
15
|
+
| browser | web automation via Playwright MCP |
|
|
16
|
+
| commit | git commits |
|
|
17
|
+
| finder | fast codebase search |
|
|
@@ -0,0 +1,256 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: debugger
|
|
3
|
+
description: Investigates bugs using scientific method with persistent debug sessions
|
|
4
|
+
tools: Read, Write, Edit, Bash, Grep, Glob, WebSearch
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
<role>
|
|
8
|
+
You investigate bugs using systematic scientific method. You maintain a persistent debug file that survives context resets.
|
|
9
|
+
|
|
10
|
+
User = Reporter (knows symptoms), You = Investigator (find the cause).
|
|
11
|
+
|
|
12
|
+
Never ask the user what's causing the bug or which file has the problem. Ask about their experience. Investigate the cause yourself.
|
|
13
|
+
</role>
|
|
14
|
+
|
|
15
|
+
<philosophy>
|
|
16
|
+
|
|
17
|
+
## Meta-Debugging: Your Own Code
|
|
18
|
+
|
|
19
|
+
When debugging code you wrote, you're fighting your own mental model.
|
|
20
|
+
|
|
21
|
+
- **Treat your code as foreign** — read it as if someone else wrote it
|
|
22
|
+
- **Question your design decisions** — they're hypotheses, not facts
|
|
23
|
+
- **Admit your mental model might be wrong** — code behavior is truth, your model is a guess
|
|
24
|
+
- **Prioritize code you touched** — if you modified 100 lines and something breaks, those are prime suspects
|
|
25
|
+
|
|
26
|
+
The hardest admission: "I implemented this wrong."
|
|
27
|
+
|
|
28
|
+
## Foundation Principles
|
|
29
|
+
|
|
30
|
+
- **What do you know for certain?** Observable facts, not assumptions
|
|
31
|
+
- **What are you assuming?** Have you verified it?
|
|
32
|
+
- **Strip away everything you think you know.** Build from observable facts.
|
|
33
|
+
|
|
34
|
+
## Cognitive Biases
|
|
35
|
+
|
|
36
|
+
| Bias | Trap | Antidote |
|
|
37
|
+
|------|------|----------|
|
|
38
|
+
| Confirmation | Only seek supporting evidence | "What would prove me wrong?" |
|
|
39
|
+
| Anchoring | First explanation becomes your anchor | Generate 3+ hypotheses before investigating any |
|
|
40
|
+
| Sunk Cost | 2 hours on one path, keep going | Every 30 min: "If I started fresh, would I take this path?" |
|
|
41
|
+
|
|
42
|
+
## Core Disciplines
|
|
43
|
+
|
|
44
|
+
- **Change one variable** at a time. Multiple changes = no idea what mattered.
|
|
45
|
+
- **Complete reading.** Read entire functions, not just "relevant" lines.
|
|
46
|
+
- **Embrace not knowing.** "I don't know" = good. "It must be X" = dangerous.
|
|
47
|
+
|
|
48
|
+
</philosophy>
|
|
49
|
+
|
|
50
|
+
<hypothesis_testing>
|
|
51
|
+
|
|
52
|
+
## Falsifiability
|
|
53
|
+
|
|
54
|
+
A good hypothesis can be proven wrong. If you can't design an experiment to disprove it, it's not useful.
|
|
55
|
+
|
|
56
|
+
**Bad:** "Something is wrong with the state"
|
|
57
|
+
**Good:** "User state resets because component remounts on route change"
|
|
58
|
+
|
|
59
|
+
The difference: specificity.
|
|
60
|
+
|
|
61
|
+
## Process
|
|
62
|
+
|
|
63
|
+
For each hypothesis:
|
|
64
|
+
1. **Prediction:** If H is true, I will observe X
|
|
65
|
+
2. **Test:** Execute one specific test
|
|
66
|
+
3. **Observe:** Record what actually happened
|
|
67
|
+
4. **Conclude:** Support or refute?
|
|
68
|
+
|
|
69
|
+
**One hypothesis at a time.** If you change three things and it works, you don't know which fixed it.
|
|
70
|
+
|
|
71
|
+
## Evidence Quality
|
|
72
|
+
|
|
73
|
+
**Strong:** Directly observable, repeatable, unambiguous, independent
|
|
74
|
+
**Weak:** Hearsay, non-repeatable, ambiguous, confounded
|
|
75
|
+
|
|
76
|
+
Act when you can answer YES to all:
|
|
77
|
+
1. Understand the mechanism? (not just "what" but "why")
|
|
78
|
+
2. Reproduce reliably?
|
|
79
|
+
3. Have evidence, not just theory?
|
|
80
|
+
4. Ruled out alternatives?
|
|
81
|
+
|
|
82
|
+
## When Disproven
|
|
83
|
+
|
|
84
|
+
1. Acknowledge explicitly with evidence
|
|
85
|
+
2. Extract the learning — what did this rule out?
|
|
86
|
+
3. Form new hypotheses based on what you now know
|
|
87
|
+
4. Don't get attached — being wrong quickly beats being wrong slowly
|
|
88
|
+
|
|
89
|
+
</hypothesis_testing>
|
|
90
|
+
|
|
91
|
+
<investigation_techniques>
|
|
92
|
+
|
|
93
|
+
## Binary Search
|
|
94
|
+
|
|
95
|
+
**When:** Large codebase, many possible failure points.
|
|
96
|
+
|
|
97
|
+
Cut problem space in half repeatedly:
|
|
98
|
+
1. Identify boundaries (where works, where fails)
|
|
99
|
+
2. Test midpoint
|
|
100
|
+
3. Determine which half contains the bug
|
|
101
|
+
4. Repeat until exact line
|
|
102
|
+
|
|
103
|
+
## Minimal Reproduction
|
|
104
|
+
|
|
105
|
+
**When:** Complex system, many moving parts.
|
|
106
|
+
|
|
107
|
+
1. Copy failing code to new file
|
|
108
|
+
2. Remove one piece at a time
|
|
109
|
+
3. Still reproduces? Keep it removed. Doesn't? Put it back.
|
|
110
|
+
4. Repeat until bare minimum
|
|
111
|
+
5. Bug is now obvious
|
|
112
|
+
|
|
113
|
+
## Working Backwards
|
|
114
|
+
|
|
115
|
+
**When:** You know correct output, don't know why you're not getting it.
|
|
116
|
+
|
|
117
|
+
1. Define desired output precisely
|
|
118
|
+
2. What function produces this? Test with expected input.
|
|
119
|
+
3. Correct output? Bug is earlier. Wrong output? Bug is here.
|
|
120
|
+
4. Repeat backwards through call stack
|
|
121
|
+
|
|
122
|
+
## Differential Debugging
|
|
123
|
+
|
|
124
|
+
**When:** Used to work, now doesn't. Works in one env but not another.
|
|
125
|
+
|
|
126
|
+
List differences (code, env, config, data), test each in isolation.
|
|
127
|
+
|
|
128
|
+
## Git Bisect
|
|
129
|
+
|
|
130
|
+
**When:** Feature worked in past, broke at unknown commit.
|
|
131
|
+
|
|
132
|
+
```bash
|
|
133
|
+
git bisect start
|
|
134
|
+
git bisect bad
|
|
135
|
+
git bisect good abc123
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
100 commits → ~7 tests to find breaking commit.
|
|
139
|
+
|
|
140
|
+
## Technique Selection
|
|
141
|
+
|
|
142
|
+
| Situation | Technique |
|
|
143
|
+
|-----------|-----------|
|
|
144
|
+
| Large codebase | Binary search |
|
|
145
|
+
| Confused | Rubber duck, add logging first |
|
|
146
|
+
| Complex system | Minimal reproduction |
|
|
147
|
+
| Know desired output | Working backwards |
|
|
148
|
+
| Used to work | Differential debugging, git bisect |
|
|
149
|
+
| Many possible causes | Comment out everything |
|
|
150
|
+
|
|
151
|
+
</investigation_techniques>
|
|
152
|
+
|
|
153
|
+
<debug_file>
|
|
154
|
+
|
|
155
|
+
## Location
|
|
156
|
+
|
|
157
|
+
Debug files live in `.claude/progress/debug-{slug}.md`
|
|
158
|
+
|
|
159
|
+
## Structure
|
|
160
|
+
|
|
161
|
+
```markdown
|
|
162
|
+
# Debug: {title}
|
|
163
|
+
<!-- session: {SESSION_ID} -->
|
|
164
|
+
<!-- spec: None -->
|
|
165
|
+
|
|
166
|
+
## Current Focus
|
|
167
|
+
hypothesis: {current theory}
|
|
168
|
+
test: {how testing it}
|
|
169
|
+
next: {immediate next step}
|
|
170
|
+
|
|
171
|
+
## Symptoms
|
|
172
|
+
expected: {what should happen}
|
|
173
|
+
actual: {what actually happens}
|
|
174
|
+
errors: {error messages}
|
|
175
|
+
reproduction: {how to trigger}
|
|
176
|
+
|
|
177
|
+
## Eliminated
|
|
178
|
+
- {theory}: {evidence that disproved it}
|
|
179
|
+
|
|
180
|
+
## Evidence
|
|
181
|
+
- {what examined}: {what observed} → {implication}
|
|
182
|
+
|
|
183
|
+
## Log
|
|
184
|
+
- [ ] ({timestamp}) (debug) {action taken}
|
|
185
|
+
|
|
186
|
+
## Files
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
## Rules
|
|
190
|
+
|
|
191
|
+
| Section | Rule |
|
|
192
|
+
|---------|------|
|
|
193
|
+
| Current Focus | OVERWRITE before every action |
|
|
194
|
+
| Symptoms | IMMUTABLE after gathering |
|
|
195
|
+
| Eliminated | APPEND only |
|
|
196
|
+
| Evidence | APPEND only |
|
|
197
|
+
| Log | APPEND only |
|
|
198
|
+
|
|
199
|
+
Update the file BEFORE taking action, not after. If context resets, the file shows what was about to happen.
|
|
200
|
+
|
|
201
|
+
</debug_file>
|
|
202
|
+
|
|
203
|
+
<execution_flow>
|
|
204
|
+
|
|
205
|
+
**Step 1: Start or resume**
|
|
206
|
+
|
|
207
|
+
Check `.claude/progress/debug-*.md` for active sessions matching this session ID.
|
|
208
|
+
|
|
209
|
+
If resuming: read file, announce status, continue from Current Focus.
|
|
210
|
+
If new: create debug file immediately.
|
|
211
|
+
|
|
212
|
+
**Step 2: Gather symptoms**
|
|
213
|
+
|
|
214
|
+
Ask about: expected behavior, actual behavior, error messages, when it started.
|
|
215
|
+
Update file after EACH answer.
|
|
216
|
+
|
|
217
|
+
**Step 3: Investigate**
|
|
218
|
+
|
|
219
|
+
1. Gather initial evidence (search codebase, read files, run tests)
|
|
220
|
+
2. Form SPECIFIC, FALSIFIABLE hypothesis
|
|
221
|
+
3. Test ONE hypothesis at a time
|
|
222
|
+
4. Append to Evidence and Eliminated as you go
|
|
223
|
+
5. Repeat until root cause confirmed
|
|
224
|
+
|
|
225
|
+
**Step 4: Fix**
|
|
226
|
+
|
|
227
|
+
- Make SMALLEST change that addresses root cause
|
|
228
|
+
- Update debug file with fix details
|
|
229
|
+
|
|
230
|
+
**Step 5: Verify**
|
|
231
|
+
|
|
232
|
+
- Test against original Symptoms
|
|
233
|
+
- If fails: back to Step 3
|
|
234
|
+
- If passes: log resolution
|
|
235
|
+
|
|
236
|
+
**Step 6: Done**
|
|
237
|
+
|
|
238
|
+
Log completion in debug file. Print root cause and fix summary.
|
|
239
|
+
|
|
240
|
+
</execution_flow>
|
|
241
|
+
|
|
242
|
+
<when_to_restart>
|
|
243
|
+
|
|
244
|
+
Consider starting over when:
|
|
245
|
+
1. 2+ hours with no progress — you're tunnel-visioned
|
|
246
|
+
2. 3+ "fixes" that didn't work — your mental model is wrong
|
|
247
|
+
3. You can't explain the current behavior — don't add changes on top of confusion
|
|
248
|
+
4. The fix works but you don't know why — this isn't fixed, this is luck
|
|
249
|
+
|
|
250
|
+
Restart protocol:
|
|
251
|
+
1. Write down what you know for certain
|
|
252
|
+
2. Write down what you've ruled out
|
|
253
|
+
3. List new hypotheses (different from before)
|
|
254
|
+
4. Begin again from evidence gathering
|
|
255
|
+
|
|
256
|
+
</when_to_restart>
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: workerc:plan
|
|
3
|
+
description: Refine spec with codebase context — turns intent into implementation blueprint
|
|
4
|
+
allowed-tools:
|
|
5
|
+
- Read
|
|
6
|
+
- Edit
|
|
7
|
+
- Glob
|
|
8
|
+
- Grep
|
|
9
|
+
- Bash
|
|
10
|
+
- AskUserQuestion
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
<objective>
|
|
14
|
+
Read the current spec + progress, explore the codebase around relevant areas, present recommendations with options to the user, then rewrite the spec with concrete implementation details.
|
|
15
|
+
Turns vague intent into a precise blueprint with real file paths, patterns, types, and edge cases.
|
|
16
|
+
</objective>
|
|
17
|
+
|
|
18
|
+
<process>
|
|
19
|
+
|
|
20
|
+
**Step 1: Find session and spec**
|
|
21
|
+
|
|
22
|
+
Read all `.claude/progress/*.md`. Find the one with `<!-- session: CURRENT_SESSION_ID -->` on line 2.
|
|
23
|
+
|
|
24
|
+
If none found: print "No active progress file for this session." and STOP.
|
|
25
|
+
|
|
26
|
+
Read line 3 for spec path.
|
|
27
|
+
|
|
28
|
+
If spec is "None" or missing:
|
|
29
|
+
- Print: "No spec linked to this session. Create one with /workerc:new or /workerc:scope first."
|
|
30
|
+
- STOP
|
|
31
|
+
|
|
32
|
+
Read the full spec file. Read the full progress file.
|
|
33
|
+
|
|
34
|
+
**Step 2: Understand the goal**
|
|
35
|
+
|
|
36
|
+
From the spec's `## Goal` and `## Scope` sections, identify:
|
|
37
|
+
- What the user wants to build/change
|
|
38
|
+
- What's explicitly in/out of scope
|
|
39
|
+
- Any decisions already made (in `## Decisions`)
|
|
40
|
+
|
|
41
|
+
Print a one-line summary of the goal to confirm understanding.
|
|
42
|
+
|
|
43
|
+
**Step 3: Explore the codebase**
|
|
44
|
+
|
|
45
|
+
Based on the goal and scope, explore the codebase to gather real context:
|
|
46
|
+
|
|
47
|
+
- **Find relevant files:** Use Glob and Grep to find files related to the goal. Look for existing patterns, types, utilities, middleware, routes, tests — anything the implementation should follow or integrate with.
|
|
48
|
+
- **Read key files:** Read the most relevant 3-5 files to understand existing patterns, naming conventions, directory structure, and coding style.
|
|
49
|
+
- **Map dependencies:** Identify what the new code needs to import, extend, or integrate with.
|
|
50
|
+
- **Check for prior art:** Look for similar features already implemented that can serve as a template.
|
|
51
|
+
|
|
52
|
+
**Step 4: Present recommendations**
|
|
53
|
+
|
|
54
|
+
Based on what you discovered, present a structured recommendation:
|
|
55
|
+
|
|
56
|
+
```
|
|
57
|
+
## Recommendation
|
|
58
|
+
|
|
59
|
+
### Approach
|
|
60
|
+
{1-3 sentences describing the recommended approach}
|
|
61
|
+
|
|
62
|
+
### Key files
|
|
63
|
+
- `path/to/file.ts` — {why this file matters}
|
|
64
|
+
- `path/to/pattern.ts` — {pattern to follow}
|
|
65
|
+
|
|
66
|
+
### Implementation steps
|
|
67
|
+
1. {step 1}
|
|
68
|
+
2. {step 2}
|
|
69
|
+
...
|
|
70
|
+
|
|
71
|
+
### Patterns to follow
|
|
72
|
+
- {existing pattern from codebase}
|
|
73
|
+
|
|
74
|
+
### Edge cases
|
|
75
|
+
- {edge case 1}
|
|
76
|
+
- {edge case 2}
|
|
77
|
+
|
|
78
|
+
### Open questions
|
|
79
|
+
- {anything unclear that needs user input}
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
Then ask the user:
|
|
83
|
+
|
|
84
|
+
```
|
|
85
|
+
AskUserQuestion(
|
|
86
|
+
header: "Plan",
|
|
87
|
+
question: "How should we proceed with this plan?",
|
|
88
|
+
options: [
|
|
89
|
+
{ label: "Looks good", description: "Write this into the spec" },
|
|
90
|
+
{ label: "Adjust", description: "I want to change some things" },
|
|
91
|
+
{ label: "Different approach", description: "I have a different idea" }
|
|
92
|
+
],
|
|
93
|
+
multiSelect: false
|
|
94
|
+
)
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
**If "Adjust":**
|
|
98
|
+
- Wait for user's adjustments
|
|
99
|
+
- Incorporate their feedback
|
|
100
|
+
- Re-present the updated recommendation
|
|
101
|
+
- Ask again
|
|
102
|
+
|
|
103
|
+
**If "Different approach":**
|
|
104
|
+
- Wait for user's alternative
|
|
105
|
+
- Re-explore codebase with new direction
|
|
106
|
+
- Go back to Step 4
|
|
107
|
+
|
|
108
|
+
**If "Looks good":**
|
|
109
|
+
- Proceed to Step 5
|
|
110
|
+
|
|
111
|
+
**Step 5: Rewrite the spec**
|
|
112
|
+
|
|
113
|
+
Update the spec file with concrete implementation details. Preserve existing sections and add/expand:
|
|
114
|
+
|
|
115
|
+
```markdown
|
|
116
|
+
# {Title}
|
|
117
|
+
|
|
118
|
+
## Goal
|
|
119
|
+
{original goal, unchanged}
|
|
120
|
+
|
|
121
|
+
## Scope
|
|
122
|
+
{original scope, unchanged}
|
|
123
|
+
|
|
124
|
+
## Implementation Plan
|
|
125
|
+
|
|
126
|
+
### Approach
|
|
127
|
+
{the approved approach}
|
|
128
|
+
|
|
129
|
+
### Steps
|
|
130
|
+
- [ ] {step 1} — `path/to/file.ts`
|
|
131
|
+
- [ ] {step 2} — `path/to/file.ts`
|
|
132
|
+
...
|
|
133
|
+
|
|
134
|
+
### Patterns
|
|
135
|
+
{existing patterns to follow, with file:line references}
|
|
136
|
+
|
|
137
|
+
### Types / Interfaces
|
|
138
|
+
{key types the implementation needs, with references to where they're defined}
|
|
139
|
+
|
|
140
|
+
### Edge Cases
|
|
141
|
+
{edge cases to handle}
|
|
142
|
+
|
|
143
|
+
## Decisions
|
|
144
|
+
{any new decisions from the planning process, appended to existing}
|
|
145
|
+
|
|
146
|
+
## Discovered
|
|
147
|
+
{anything discovered during exploration — gotchas, constraints, existing bugs}
|
|
148
|
+
|
|
149
|
+
## Rejected
|
|
150
|
+
{approaches considered but rejected, with reasons}
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
**Step 6: Log and confirm**
|
|
154
|
+
|
|
155
|
+
Add to progress log:
|
|
156
|
+
`- [x] ({YYYY-MM-DD HH:MM}) (plan) Refined spec with implementation details`
|
|
157
|
+
|
|
158
|
+
Print:
|
|
159
|
+
```
|
|
160
|
+
Spec updated: {spec path}
|
|
161
|
+
{N} implementation steps planned
|
|
162
|
+
Ready to build — the spec is now your blueprint.
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
STOP.
|
|
166
|
+
|
|
167
|
+
</process>
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: workerc:update
|
|
3
|
+
description: Update workerc to the latest version
|
|
4
|
+
allowed-tools:
|
|
5
|
+
- Bash
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
<objective>
|
|
9
|
+
Update workerc to the latest version and re-run init.
|
|
10
|
+
</objective>
|
|
11
|
+
|
|
12
|
+
<process>
|
|
13
|
+
|
|
14
|
+
**Step 1: Run update**
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
npx @scriptgun/workerc@latest init
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
This will re-detect tools, re-write hooks, and update settings.
|
|
21
|
+
The init wizard is interactive — follow the prompts.
|
|
22
|
+
|
|
23
|
+
**Step 2: Confirm**
|
|
24
|
+
|
|
25
|
+
Print: "workerc updated. Restart Claude Code to pick up new hooks."
|
|
26
|
+
|
|
27
|
+
STOP.
|
|
28
|
+
|
|
29
|
+
</process>
|
|
@@ -15,13 +15,17 @@ echo "$FILE" | grep -qE '\.({{{EXTENSIONS}}})$' || exit 0
|
|
|
15
15
|
echo "$FILE" | grep -qE '/\.claude/' && exit 0
|
|
16
16
|
|
|
17
17
|
LINT_OUTPUT=$({{{LINT_COMMAND}}} "$FILE" 2>&1)
|
|
18
|
-
|
|
19
|
-
jq -n --arg reason "Lint issues in $FILE:
|
|
20
|
-
$LINT_OUTPUT
|
|
18
|
+
EXIT_CODE=$?
|
|
21
19
|
|
|
22
|
-
|
|
20
|
+
# Success or file excluded from linter config
|
|
21
|
+
if [ $EXIT_CODE -eq 0 ]; then
|
|
22
|
+
echo '{}'
|
|
23
23
|
exit 0
|
|
24
24
|
fi
|
|
25
|
+
echo "$LINT_OUTPUT" | grep -q "No files were processed" && { echo '{}'; exit 0; }
|
|
25
26
|
|
|
26
|
-
|
|
27
|
+
jq -n --arg reason "Lint issues in $FILE:
|
|
28
|
+
$LINT_OUTPUT
|
|
29
|
+
|
|
30
|
+
FIX THESE BEFORE RESPONDING." '{"decision":"block","reason":$reason}'
|
|
27
31
|
exit 0
|
|
@@ -22,9 +22,9 @@ echo "$FILE" | grep -qE '/\.claude/' && exit 0
|
|
|
22
22
|
|
|
23
23
|
TRACKER_DIR="$(cd "$(dirname "$0")/../progress" 2>/dev/null && pwd)"
|
|
24
24
|
|
|
25
|
-
# No progress dir =
|
|
25
|
+
# No progress dir = pass through
|
|
26
26
|
if [ -z "$TRACKER_DIR" ] || [ ! -d "$TRACKER_DIR" ]; then
|
|
27
|
-
|
|
27
|
+
echo '{}'
|
|
28
28
|
exit 0
|
|
29
29
|
fi
|
|
30
30
|
|
|
@@ -109,13 +109,6 @@ if [ -n "$PENDING_TRACKER" ]; then
|
|
|
109
109
|
exit 0
|
|
110
110
|
fi
|
|
111
111
|
|
|
112
|
-
# Case 3: No tracker —
|
|
113
|
-
|
|
114
|
-
if [ -n "$UNCLAIMED" ]; then
|
|
115
|
-
REASON="$REASON
|
|
116
|
-
Unclaimed trackers you could claim:$UNCLAIMED
|
|
117
|
-
|
|
118
|
-
To claim: set line 2 to <!-- session: $SESSION_ID -->"
|
|
119
|
-
fi
|
|
120
|
-
jq -n --arg reason "$REASON" '{"decision":"block","reason":$reason}'
|
|
112
|
+
# Case 3: No tracker — pass through
|
|
113
|
+
echo '{}'
|
|
121
114
|
exit 0
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# SessionStart hook: Inject active progress context on fresh session start
|
|
3
|
+
# Fires on: every session start (matcher "" in settings)
|
|
4
|
+
# Complements session-start-compact.sh which only fires on compaction
|
|
5
|
+
|
|
6
|
+
INPUT=$(cat)
|
|
7
|
+
SESSION_ID=$(echo "$INPUT" | jq -r '.session_id // empty')
|
|
8
|
+
|
|
9
|
+
[ -z "$SESSION_ID" ] && exit 0
|
|
10
|
+
|
|
11
|
+
TRACKER_DIR="$(cd "$(dirname "$0")/../progress" 2>/dev/null && pwd)"
|
|
12
|
+
[ -z "$TRACKER_DIR" ] || [ ! -d "$TRACKER_DIR" ] && exit 0
|
|
13
|
+
|
|
14
|
+
PROJECT_DIR="$(cd "$(dirname "$0")/../.." 2>/dev/null && pwd)"
|
|
15
|
+
|
|
16
|
+
# Find this session's active tracker
|
|
17
|
+
MY_TRACKER=""
|
|
18
|
+
for t in "$TRACKER_DIR"/*.md; do
|
|
19
|
+
[ -f "$t" ] || continue
|
|
20
|
+
LINE1=$(head -1 "$t")
|
|
21
|
+
echo "$LINE1" | grep -q "\[DONE\]" && continue
|
|
22
|
+
echo "$LINE1" | grep -q "\[ABORTED\]" && continue
|
|
23
|
+
LINE2=$(sed -n '2p' "$t")
|
|
24
|
+
if echo "$LINE2" | grep -q "<!-- session: $SESSION_ID -->"; then
|
|
25
|
+
MY_TRACKER="$t"
|
|
26
|
+
break
|
|
27
|
+
fi
|
|
28
|
+
done
|
|
29
|
+
|
|
30
|
+
[ -z "$MY_TRACKER" ] && exit 0
|
|
31
|
+
|
|
32
|
+
# Read tracker
|
|
33
|
+
TRACKER_CONTENT=$(cat "$MY_TRACKER")
|
|
34
|
+
TRACKER_NAME=$(basename "$MY_TRACKER")
|
|
35
|
+
TRACKER_REL=".claude/progress/$TRACKER_NAME"
|
|
36
|
+
|
|
37
|
+
# Extract spec path (line 3: <!-- spec: path -->)
|
|
38
|
+
SPEC_PATH=""
|
|
39
|
+
SPEC_CONTENT=""
|
|
40
|
+
SPEC_LINE=$(sed -n '3p' "$MY_TRACKER")
|
|
41
|
+
if echo "$SPEC_LINE" | grep -q "<!-- spec:"; then
|
|
42
|
+
SPEC_PATH=$(echo "$SPEC_LINE" | sed -n 's/<!-- spec: \(.*\) -->/\1/p')
|
|
43
|
+
fi
|
|
44
|
+
|
|
45
|
+
if [ -n "$SPEC_PATH" ] && [ "$SPEC_PATH" != "None" ]; then
|
|
46
|
+
FULL_SPEC="$PROJECT_DIR/$SPEC_PATH"
|
|
47
|
+
if [ -f "$FULL_SPEC" ]; then
|
|
48
|
+
SPEC_CONTENT=$(cat "$FULL_SPEC")
|
|
49
|
+
fi
|
|
50
|
+
fi
|
|
51
|
+
|
|
52
|
+
DONE_COUNT=$(grep -c '^\- \[x\]' "$MY_TRACKER" 2>/dev/null || echo "0")
|
|
53
|
+
|
|
54
|
+
CONTEXT="# ACTIVE SESSION CONTEXT
|
|
55
|
+
|
|
56
|
+
You have an active work session. Session ID: $SESSION_ID
|
|
57
|
+
|
|
58
|
+
## Progress: $TRACKER_REL ($DONE_COUNT entries completed)
|
|
59
|
+
$TRACKER_CONTENT
|
|
60
|
+
|
|
61
|
+
## REQUIRED: Read your context files before working:
|
|
62
|
+
1. Read file $PROJECT_DIR/$TRACKER_REL"
|
|
63
|
+
|
|
64
|
+
if [ -n "$SPEC_PATH" ] && [ "$SPEC_PATH" != "None" ]; then
|
|
65
|
+
CONTEXT="$CONTEXT
|
|
66
|
+
2. Read file $PROJECT_DIR/$SPEC_PATH"
|
|
67
|
+
fi
|
|
68
|
+
|
|
69
|
+
CONTEXT="$CONTEXT
|
|
70
|
+
3. Review ## Files to rebuild code context
|
|
71
|
+
4. Continue working from where you left off"
|
|
72
|
+
|
|
73
|
+
if [ -n "$SPEC_CONTENT" ]; then
|
|
74
|
+
CONTEXT="$CONTEXT
|
|
75
|
+
|
|
76
|
+
## Spec: $SPEC_PATH
|
|
77
|
+
$SPEC_CONTENT"
|
|
78
|
+
fi
|
|
79
|
+
|
|
80
|
+
jq -n --arg ctx "$CONTEXT" '{"hookSpecificOutput":{"hookEventName":"SessionStart","additionalContext":$ctx}}'
|
|
81
|
+
exit 0
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/* SessionStart hook: check for workerc updates in background */
|
|
3
|
+
/* Spawns a detached child so the hook exits immediately */
|
|
4
|
+
|
|
5
|
+
const fs = require("fs")
|
|
6
|
+
const path = require("path")
|
|
7
|
+
const os = require("os")
|
|
8
|
+
const { spawn } = require("child_process")
|
|
9
|
+
|
|
10
|
+
const homeDir = os.homedir()
|
|
11
|
+
const cacheDir = path.join(homeDir, ".claude", "cache")
|
|
12
|
+
const cacheFile = path.join(cacheDir, "workerc-update-check.json")
|
|
13
|
+
|
|
14
|
+
if (!fs.existsSync(cacheDir)) {
|
|
15
|
+
fs.mkdirSync(cacheDir, { recursive: true })
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const child = spawn(
|
|
19
|
+
process.execPath,
|
|
20
|
+
[
|
|
21
|
+
"-e",
|
|
22
|
+
`
|
|
23
|
+
const fs = require("fs")
|
|
24
|
+
const { execSync } = require("child_process")
|
|
25
|
+
|
|
26
|
+
const cacheFile = ${JSON.stringify(cacheFile)}
|
|
27
|
+
|
|
28
|
+
let installed = "0.0.0"
|
|
29
|
+
try {
|
|
30
|
+
const out = execSync("npm ls @scriptgun/workerc --json 2>/dev/null || npx workerc --version 2>/dev/null", {
|
|
31
|
+
encoding: "utf8",
|
|
32
|
+
timeout: 10000,
|
|
33
|
+
windowsHide: true,
|
|
34
|
+
}).trim()
|
|
35
|
+
/* Try JSON parse (npm ls), fall back to raw version string */
|
|
36
|
+
try {
|
|
37
|
+
const json = JSON.parse(out)
|
|
38
|
+
installed = json.dependencies?.["@scriptgun/workerc"]?.version || "0.0.0"
|
|
39
|
+
} catch (_) {
|
|
40
|
+
if (/^\\d+\\.\\d+/.test(out)) installed = out
|
|
41
|
+
}
|
|
42
|
+
} catch (_) {}
|
|
43
|
+
|
|
44
|
+
let latest = null
|
|
45
|
+
try {
|
|
46
|
+
latest = execSync("npm view @scriptgun/workerc version", {
|
|
47
|
+
encoding: "utf8",
|
|
48
|
+
timeout: 10000,
|
|
49
|
+
windowsHide: true,
|
|
50
|
+
}).trim()
|
|
51
|
+
} catch (_) {}
|
|
52
|
+
|
|
53
|
+
const result = {
|
|
54
|
+
update_available: latest && installed !== latest,
|
|
55
|
+
installed,
|
|
56
|
+
latest: latest || "unknown",
|
|
57
|
+
checked: Math.floor(Date.now() / 1000),
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
fs.writeFileSync(cacheFile, JSON.stringify(result))
|
|
61
|
+
`,
|
|
62
|
+
],
|
|
63
|
+
{ stdio: "ignore", windowsHide: true }
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
child.unref()
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/* Statusline hook: model | task | directory | context usage */
|
|
3
|
+
/* Reads current task from workerc progress files */
|
|
4
|
+
|
|
5
|
+
const fs = require("fs")
|
|
6
|
+
const path = require("path")
|
|
7
|
+
const os = require("os")
|
|
8
|
+
|
|
9
|
+
let input = ""
|
|
10
|
+
process.stdin.setEncoding("utf8")
|
|
11
|
+
process.stdin.on("data", (chunk) => (input += chunk))
|
|
12
|
+
process.stdin.on("end", () => {
|
|
13
|
+
try {
|
|
14
|
+
const data = JSON.parse(input)
|
|
15
|
+
const model = data.model?.display_name || "Claude"
|
|
16
|
+
const dir = data.workspace?.current_dir || process.cwd()
|
|
17
|
+
const session = data.session_id || ""
|
|
18
|
+
const remaining = data.context_window?.remaining_percentage
|
|
19
|
+
|
|
20
|
+
/* Context window bar (scaled to 80% real limit) */
|
|
21
|
+
let ctx = ""
|
|
22
|
+
if (remaining != null) {
|
|
23
|
+
const rem = Math.round(remaining)
|
|
24
|
+
const rawUsed = Math.max(0, Math.min(100, 100 - rem))
|
|
25
|
+
const used = Math.min(100, Math.round((rawUsed / 80) * 100))
|
|
26
|
+
const filled = Math.floor(used / 10)
|
|
27
|
+
const bar = "\u2588".repeat(filled) + "\u2591".repeat(10 - filled)
|
|
28
|
+
|
|
29
|
+
if (used < 63) {
|
|
30
|
+
ctx = ` \x1b[32m${bar} ${used}%\x1b[0m`
|
|
31
|
+
} else if (used < 81) {
|
|
32
|
+
ctx = ` \x1b[33m${bar} ${used}%\x1b[0m`
|
|
33
|
+
} else if (used < 95) {
|
|
34
|
+
ctx = ` \x1b[38;5;208m${bar} ${used}%\x1b[0m`
|
|
35
|
+
} else {
|
|
36
|
+
ctx = ` \x1b[5;31m\uD83D\uDC80 ${bar} ${used}%\x1b[0m`
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/* Current task from workerc progress files */
|
|
41
|
+
let task = ""
|
|
42
|
+
const projectDir = data.workspace?.current_dir || process.cwd()
|
|
43
|
+
const progressDir = path.join(projectDir, ".claude", "progress")
|
|
44
|
+
|
|
45
|
+
if (session && fs.existsSync(progressDir)) {
|
|
46
|
+
const files = fs.readdirSync(progressDir).filter((f) => f.endsWith(".md"))
|
|
47
|
+
|
|
48
|
+
for (const file of files) {
|
|
49
|
+
const filePath = path.join(progressDir, file)
|
|
50
|
+
try {
|
|
51
|
+
const content = fs.readFileSync(filePath, "utf8")
|
|
52
|
+
const lines = content.split("\n")
|
|
53
|
+
|
|
54
|
+
/* Line 1: title, skip if DONE/ABORTED */
|
|
55
|
+
if (/\[DONE\]|\[ABORTED\]/.test(lines[0] || "")) continue
|
|
56
|
+
/* Line 2: <!-- session: ID --> */
|
|
57
|
+
if (!(lines[1] || "").includes(`<!-- session: ${session} -->`)) continue
|
|
58
|
+
|
|
59
|
+
/* Extract title from "# Title" */
|
|
60
|
+
const titleMatch = (lines[0] || "").match(/^#\s+(.+)/)
|
|
61
|
+
if (titleMatch) task = titleMatch[1].replace(/\s*\[.*?\]\s*/g, "").trim()
|
|
62
|
+
break
|
|
63
|
+
} catch (_) {}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/* workerc update available? */
|
|
68
|
+
let updateNotice = ""
|
|
69
|
+
const cacheFile = path.join(os.homedir(), ".claude", "cache", "workerc-update-check.json")
|
|
70
|
+
if (fs.existsSync(cacheFile)) {
|
|
71
|
+
try {
|
|
72
|
+
const cache = JSON.parse(fs.readFileSync(cacheFile, "utf8"))
|
|
73
|
+
if (cache.update_available) {
|
|
74
|
+
updateNotice = "\x1b[33m\u2B06 workerc update\x1b[0m \u2502 "
|
|
75
|
+
}
|
|
76
|
+
} catch (_) {}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/* Output */
|
|
80
|
+
const dirname = path.basename(dir)
|
|
81
|
+
if (task) {
|
|
82
|
+
process.stdout.write(
|
|
83
|
+
`${updateNotice}\x1b[2m${model}\x1b[0m \u2502 \x1b[1m${task}\x1b[0m \u2502 \x1b[2m${dirname}\x1b[0m${ctx}`
|
|
84
|
+
)
|
|
85
|
+
} else {
|
|
86
|
+
process.stdout.write(
|
|
87
|
+
`${updateNotice}\x1b[2m${model}\x1b[0m \u2502 \x1b[2m${dirname}\x1b[0m${ctx}`
|
|
88
|
+
)
|
|
89
|
+
}
|
|
90
|
+
} catch (_) {
|
|
91
|
+
/* Silent fail */
|
|
92
|
+
}
|
|
93
|
+
})
|
package/dist/write-files.d.ts
CHANGED
|
@@ -9,8 +9,11 @@ export interface WriteOptions {
|
|
|
9
9
|
typeExtensions: string;
|
|
10
10
|
typesHookFilename: string;
|
|
11
11
|
activeHooksDescription: string;
|
|
12
|
+
enableClaudeMd: boolean;
|
|
12
13
|
}
|
|
13
14
|
export declare function writeFiles(options: WriteOptions): {
|
|
14
15
|
commands: string[];
|
|
15
16
|
hooks: string[];
|
|
17
|
+
agents: string[];
|
|
18
|
+
claudeMd: boolean;
|
|
16
19
|
};
|
package/dist/write-files.js
CHANGED
|
@@ -28,9 +28,11 @@ export function writeFiles(options) {
|
|
|
28
28
|
const claudeDir = join(options.projectDir, ".claude");
|
|
29
29
|
const commandsTarget = join(claudeDir, "commands", "workerc");
|
|
30
30
|
const hooksTarget = join(claudeDir, "hooks");
|
|
31
|
+
const agentsTarget = join(claudeDir, "agents");
|
|
31
32
|
const progressDir = join(claudeDir, "progress");
|
|
32
33
|
ensureDir(commandsTarget);
|
|
33
34
|
ensureDir(hooksTarget);
|
|
35
|
+
ensureDir(agentsTarget);
|
|
34
36
|
ensureDir(progressDir);
|
|
35
37
|
/* Copy command templates verbatim */
|
|
36
38
|
const commandsSrc = join(templateDir, "commands");
|
|
@@ -38,12 +40,30 @@ export function writeFiles(options) {
|
|
|
38
40
|
for (const file of commandFiles) {
|
|
39
41
|
cpSync(join(commandsSrc, file), join(commandsTarget, file));
|
|
40
42
|
}
|
|
43
|
+
/* Copy agent templates verbatim */
|
|
44
|
+
const agentsSrc = join(templateDir, "agents");
|
|
45
|
+
const agentFiles = readdirSync(agentsSrc).filter((f) => f.endsWith(".md"));
|
|
46
|
+
for (const file of agentFiles) {
|
|
47
|
+
cpSync(join(agentsSrc, file), join(agentsTarget, file));
|
|
48
|
+
}
|
|
41
49
|
/* Copy/template hook files */
|
|
42
50
|
const writtenHooks = [];
|
|
43
51
|
/* Tracker — verbatim copy */
|
|
44
52
|
cpSync(join(templateDir, "hooks", "post-edit-tracker.sh"), join(hooksTarget, "post-edit-tracker.sh"));
|
|
45
53
|
chmodSync(join(hooksTarget, "post-edit-tracker.sh"), 0o755);
|
|
46
54
|
writtenHooks.push("post-edit-tracker.sh");
|
|
55
|
+
/* Check-update — verbatim copy */
|
|
56
|
+
cpSync(join(templateDir, "hooks", "workerc-check-update.cjs"), join(hooksTarget, "workerc-check-update.cjs"));
|
|
57
|
+
chmodSync(join(hooksTarget, "workerc-check-update.cjs"), 0o755);
|
|
58
|
+
writtenHooks.push("workerc-check-update.cjs");
|
|
59
|
+
/* Statusline — verbatim copy */
|
|
60
|
+
cpSync(join(templateDir, "hooks", "workerc-statusline.cjs"), join(hooksTarget, "workerc-statusline.cjs"));
|
|
61
|
+
chmodSync(join(hooksTarget, "workerc-statusline.cjs"), 0o755);
|
|
62
|
+
writtenHooks.push("workerc-statusline.cjs");
|
|
63
|
+
/* Startup — verbatim copy */
|
|
64
|
+
cpSync(join(templateDir, "hooks", "session-start-startup.sh"), join(hooksTarget, "session-start-startup.sh"));
|
|
65
|
+
chmodSync(join(hooksTarget, "session-start-startup.sh"), 0o755);
|
|
66
|
+
writtenHooks.push("session-start-startup.sh");
|
|
47
67
|
/* Session start compact — template {{ACTIVE_HOOKS}} */
|
|
48
68
|
const compactSrc = readFileSync(join(templateDir, "hooks", "session-start-compact.sh"), "utf-8");
|
|
49
69
|
const compactRendered = renderTemplate(compactSrc, {
|
|
@@ -74,5 +94,14 @@ export function writeFiles(options) {
|
|
|
74
94
|
chmodSync(join(hooksTarget, options.typesHookFilename), 0o755);
|
|
75
95
|
writtenHooks.push(options.typesHookFilename);
|
|
76
96
|
}
|
|
77
|
-
|
|
97
|
+
/* CLAUDE.md — optional scaffold */
|
|
98
|
+
let claudeMd = false;
|
|
99
|
+
if (options.enableClaudeMd) {
|
|
100
|
+
const claudeMdPath = join(options.projectDir, ".claude", "CLAUDE.md");
|
|
101
|
+
if (!existsSync(claudeMdPath)) {
|
|
102
|
+
cpSync(join(templateDir, "CLAUDE.md.tmpl"), claudeMdPath);
|
|
103
|
+
claudeMd = true;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
return { commands: commandFiles, hooks: writtenHooks, agents: agentFiles, claudeMd };
|
|
78
107
|
}
|
package/package.json
CHANGED
|
@@ -1,21 +1,18 @@
|
|
|
1
1
|
{
|
|
2
|
-
"name": "@scriptgun/workerc",
|
|
3
|
-
"version": "0.1.2",
|
|
4
|
-
"description": "Claude Code session management CLI — commands, hooks, and progress tracking",
|
|
5
|
-
"type": "module",
|
|
6
2
|
"bin": {
|
|
7
3
|
"workerc": "dist/bin.js"
|
|
8
4
|
},
|
|
9
|
-
"
|
|
10
|
-
|
|
5
|
+
"dependencies": {
|
|
6
|
+
"@clack/prompts": "1.0.0"
|
|
7
|
+
},
|
|
8
|
+
"description": "Claude Code session management CLI — commands, hooks, and progress tracking",
|
|
9
|
+
"devDependencies": {
|
|
10
|
+
"typescript": "^5.9.3"
|
|
11
|
+
},
|
|
11
12
|
"files": [
|
|
12
13
|
"dist",
|
|
13
14
|
"README.md"
|
|
14
15
|
],
|
|
15
|
-
"scripts": {
|
|
16
|
-
"build": "tsc && cp -r src/templates dist/templates && chmod +x dist/bin.js",
|
|
17
|
-
"type:check": "tsc --noEmit"
|
|
18
|
-
},
|
|
19
16
|
"keywords": [
|
|
20
17
|
"claude",
|
|
21
18
|
"claude-code",
|
|
@@ -25,14 +22,17 @@
|
|
|
25
22
|
"workerc"
|
|
26
23
|
],
|
|
27
24
|
"license": "MIT",
|
|
25
|
+
"main": "./dist/init.js",
|
|
26
|
+
"name": "@scriptgun/workerc",
|
|
28
27
|
"repository": {
|
|
29
28
|
"type": "git",
|
|
30
29
|
"url": "git+https://github.com/lovrozagar/workerc.git"
|
|
31
30
|
},
|
|
32
|
-
"
|
|
33
|
-
"
|
|
31
|
+
"scripts": {
|
|
32
|
+
"build": "tsc && cp -r src/templates dist/templates && chmod +x dist/bin.js",
|
|
33
|
+
"type:check": "tsc --noEmit"
|
|
34
34
|
},
|
|
35
|
-
"
|
|
36
|
-
|
|
37
|
-
|
|
35
|
+
"type": "module",
|
|
36
|
+
"types": "./dist/init.d.ts",
|
|
37
|
+
"version": "0.3.0"
|
|
38
38
|
}
|