stonecut 1.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.
@@ -0,0 +1,127 @@
1
+ ---
2
+ name: stonecut-prd
3
+ description: Write a PRD through structured user interview, codebase exploration, and module design. Saves the result as a local file or GitHub issue. Use when the user wants to create a product requirements document or plan a new feature.
4
+ ---
5
+
6
+ You are writing a PRD as part of the Stonecut workflow. Follow these steps, skipping any that aren't necessary for the situation.
7
+
8
+ ## Process
9
+
10
+ ### 1. Gather context
11
+
12
+ Ask the user for a detailed description of the problem they want to solve and any ideas they have for the solution.
13
+
14
+ ### 2. Explore the codebase
15
+
16
+ Explore the repo to verify the user's assertions and understand the current state of the code.
17
+
18
+ ### 3. Interview
19
+
20
+ Interview the user relentlessly about every aspect of the plan until you reach a shared understanding. Walk down each branch of the design tree, resolving dependencies between decisions one-by-one.
21
+
22
+ ### 4. Design modules
23
+
24
+ Sketch out the major modules that need to be built or modified. Look for opportunities to extract deep modules — modules that encapsulate significant functionality behind a simple, testable interface that rarely changes.
25
+
26
+ Check with the user that these modules match their expectations. Ask which modules they want tests written for.
27
+
28
+ ### 5. Choose a destination
29
+
30
+ Ask the user where to save the PRD:
31
+
32
+ - **Local file** — Save as `.stonecut/<name>/prd.md` in the project. Ask the user: "What should I name this spec?" The name can be anything — a ticket ID (e.g., `ASC-1`), a descriptive slug (e.g., `auth-refactor`), or whatever fits. Create the `.stonecut/<name>/` directory if it doesn't exist.
33
+ - **GitHub issue** — Create a GitHub issue using `gh issue create --label prd`. Before creating, ensure the `prd` label exists:
34
+
35
+ ```bash
36
+ # Only create the label if it doesn't already exist
37
+ if ! gh label list --search "prd" --json name --jq '.[].name' | grep -qx "prd"; then
38
+ gh label create prd --description "Product Requirements Document" --color "0052CC"
39
+ fi
40
+ ```
41
+
42
+ If the project already has a `.stonecut/` directory, default to suggesting local. Otherwise, just ask.
43
+
44
+ ### 6. Documentation impact check
45
+
46
+ Before writing the PRD, ensure that documentation impact has been explicitly addressed. Based on everything you've learned from the interview and codebase exploration, analyze which user-facing documentation artifacts (README, CLI help text, docs/ content) would be affected by these changes.
47
+
48
+ Present your assessment to the user for confirmation:
49
+
50
+ - If changes are needed, list the specific artifacts and what would need updating.
51
+ - If no changes are needed, state why (e.g., "Internal refactor — no user-facing behavior changes") and ask the user to confirm.
52
+
53
+ Record the confirmed answer for inclusion in the PRD's Documentation Impact section.
54
+
55
+ This step is a gate — do not proceed to writing the PRD until documentation impact is resolved.
56
+
57
+ ### 7. Write the PRD
58
+
59
+ Once you have a complete understanding of the problem and solution, write the PRD using the template below and save it to the chosen destination.
60
+
61
+ <prd-template>
62
+
63
+ ## Problem Statement
64
+
65
+ The problem that the user is facing, from the user's perspective.
66
+
67
+ ## Solution
68
+
69
+ The solution to the problem, from the user's perspective.
70
+
71
+ ## User Stories
72
+
73
+ A LONG, numbered list of user stories. Each user story should be in the format of:
74
+
75
+ 1. As an <actor>, I want a <feature>, so that <benefit>
76
+
77
+ <user-story-example>
78
+ 1. As a mobile bank customer, I want to see balance on my accounts, so that I can make better informed decisions about my spending
79
+ </user-story-example>
80
+
81
+ This list of user stories should be extremely extensive and cover all aspects of the feature.
82
+
83
+ ## Implementation Decisions
84
+
85
+ A list of implementation decisions that were made. This can include:
86
+
87
+ - The modules that will be built/modified
88
+ - The interfaces of those modules that will be modified
89
+ - Technical clarifications from the developer
90
+ - Architectural decisions
91
+ - Schema changes
92
+ - API contracts
93
+ - Specific interactions
94
+
95
+ Do NOT include specific file paths or code snippets. They may end up being outdated very quickly.
96
+
97
+ ## Testing Decisions
98
+
99
+ A list of testing decisions that were made. Include:
100
+
101
+ - A description of what makes a good test (only test external behavior, not implementation details)
102
+ - Which modules will be tested
103
+ - Prior art for the tests (i.e. similar types of tests in the codebase)
104
+
105
+ ## Documentation Impact
106
+
107
+ Which user-facing documentation artifacts are affected by these changes:
108
+
109
+ - **README** — Which sections need adding or updating?
110
+ - **CLI help text** — Are commands, flags, or usage examples changing?
111
+ - **docs/ content** — Are there deeper documentation files that need updating?
112
+
113
+ If no documentation changes are needed, state why (e.g., "Internal refactor — no user-facing behavior changes"). This assessment should be provided by the model based on its analysis and confirmed by the user during the interview.
114
+
115
+ ## Out of Scope
116
+
117
+ A description of the things that are out of scope for this PRD.
118
+
119
+ ## Further Notes
120
+
121
+ Any further notes about the feature.
122
+
123
+ </prd-template>
124
+
125
+ ## Next Step
126
+
127
+ Once the PRD is saved, ask the user: "Ready to break this into issues? I can run `/stonecut-issues` next."
package/src/skills.ts ADDED
@@ -0,0 +1,163 @@
1
+ /**
2
+ * Skills management — setup and removal of Claude Code skill symlinks.
3
+ *
4
+ * Exports pure functions; CLI integration is in cli.ts.
5
+ */
6
+
7
+ import {
8
+ existsSync,
9
+ lstatSync,
10
+ statSync,
11
+ mkdirSync,
12
+ readlinkSync,
13
+ realpathSync,
14
+ symlinkSync,
15
+ unlinkSync,
16
+ } from "node:fs";
17
+ import { join, resolve } from "node:path";
18
+ import { homedir } from "node:os";
19
+
20
+ export const SKILL_NAMES = ["stonecut-interview", "stonecut-prd", "stonecut-issues"];
21
+
22
+ /**
23
+ * Return the path to the skills/ directory shipped with this package.
24
+ */
25
+ export function getSkillsSourceDir(): string {
26
+ return resolve(import.meta.dir, "skills");
27
+ }
28
+
29
+ /**
30
+ * Return the skills target directory, optionally creating it.
31
+ */
32
+ export function getSkillsTargetDir(
33
+ opts: {
34
+ create?: boolean;
35
+ claudeRoot?: string;
36
+ } = {},
37
+ ): string {
38
+ const { create = true, claudeRoot } = opts;
39
+ const target = claudeRoot ? join(claudeRoot, "skills") : join(homedir(), ".claude", "skills");
40
+ if (create) {
41
+ mkdirSync(target, { recursive: true });
42
+ }
43
+ return target;
44
+ }
45
+
46
+ /** Check if path exists as a symlink (without following it). */
47
+ function isSymlink(path: string): boolean {
48
+ try {
49
+ return lstatSync(path).isSymbolicLink();
50
+ } catch {
51
+ return false;
52
+ }
53
+ }
54
+
55
+ /** Check if something exists at path (symlink or real). */
56
+ function pathExists(path: string): boolean {
57
+ // existsSync follows symlinks, so also check lstat for dangling symlinks
58
+ if (existsSync(path)) return true;
59
+ return isSymlink(path);
60
+ }
61
+
62
+ export interface SkillsOutput {
63
+ messages: string[];
64
+ warnings: string[];
65
+ }
66
+
67
+ /**
68
+ * Install Stonecut skills as symlinks into the target skills directory.
69
+ */
70
+ export function setupSkills(claudeRoot?: string): SkillsOutput {
71
+ const sourceDir = getSkillsSourceDir();
72
+ const output: SkillsOutput = { messages: [], warnings: [] };
73
+
74
+ if (!existsSync(sourceDir) || !statSync(sourceDir).isDirectory()) {
75
+ throw new Error(`Skills directory not found at ${sourceDir}`);
76
+ }
77
+
78
+ const targetDir = getSkillsTargetDir({ claudeRoot });
79
+
80
+ for (const name of SKILL_NAMES) {
81
+ const source = join(sourceDir, name);
82
+ const target = join(targetDir, name);
83
+
84
+ if (!existsSync(source) || !statSync(source).isDirectory()) {
85
+ output.warnings.push(`Warning: skill source not found: ${source}`);
86
+ continue;
87
+ }
88
+
89
+ if (isSymlink(target)) {
90
+ try {
91
+ const existing = realpathSync(target);
92
+ if (existing === realpathSync(source)) {
93
+ // Already points to the right place — skip silently
94
+ continue;
95
+ }
96
+ } catch {
97
+ // Dangling symlink — remove it and re-create below
98
+ unlinkSync(target);
99
+ symlinkSync(source, target);
100
+ output.messages.push(`Replaced dangling link ${name} -> ${source}`);
101
+ continue;
102
+ }
103
+ const linkTarget = readlinkSync(target);
104
+ output.warnings.push(
105
+ `Warning: ${target} already exists as symlink -> ${linkTarget}. Skipping.`,
106
+ );
107
+ continue;
108
+ }
109
+
110
+ if (pathExists(target)) {
111
+ // Regular file or directory
112
+ output.warnings.push(`Warning: ${target} already exists (not a symlink). Skipping.`);
113
+ continue;
114
+ }
115
+
116
+ symlinkSync(source, target);
117
+ output.messages.push(`Linked ${name} -> ${source}`);
118
+ }
119
+
120
+ return output;
121
+ }
122
+
123
+ /**
124
+ * Remove Stonecut skill symlinks from the target skills directory.
125
+ */
126
+ export function removeSkills(claudeRoot?: string): SkillsOutput {
127
+ const sourceDir = getSkillsSourceDir();
128
+ const targetDir = getSkillsTargetDir({ create: false, claudeRoot });
129
+ const output: SkillsOutput = { messages: [], warnings: [] };
130
+
131
+ if (!existsSync(targetDir) || !statSync(targetDir).isDirectory()) {
132
+ return output;
133
+ }
134
+
135
+ for (const name of SKILL_NAMES) {
136
+ const target = join(targetDir, name);
137
+
138
+ if (!isSymlink(target)) {
139
+ continue;
140
+ }
141
+
142
+ // Only remove if it points into the Stonecut package
143
+ let resolved: string;
144
+ try {
145
+ resolved = realpathSync(target);
146
+ } catch {
147
+ // Dangling symlink — safe to remove
148
+ unlinkSync(target);
149
+ output.messages.push(`Removed dangling link ${name}`);
150
+ continue;
151
+ }
152
+
153
+ const expectedPath = join(sourceDir, name);
154
+ if (!existsSync(expectedPath) || resolved !== realpathSync(expectedPath)) {
155
+ continue;
156
+ }
157
+
158
+ unlinkSync(target);
159
+ output.messages.push(`Removed ${name}`);
160
+ }
161
+
162
+ return output;
163
+ }
File without changes
@@ -0,0 +1,28 @@
1
+ You are executing a single task from {task_source}.
2
+
3
+ ## Instructions
4
+
5
+ 1. Read the PRD below — it contains the full context, architecture, and constraints.
6
+ 2. Read the issue spec below — it contains exactly what to build and the acceptance criteria.
7
+ 3. Implement everything described in the issue spec.
8
+ 4. Verify your work compiles and passes any validation described in the issue.
9
+ 5. Ensure `.gitignore` is updated for any generated artifacts (build outputs, dependencies, etc.).
10
+
11
+ IMPORTANT:
12
+
13
+ - Do ONLY this one issue. Stop after verifying.
14
+ - Do NOT commit — the orchestrator handles git operations.
15
+ - Do NOT modify files outside the scope of this issue unless fixing an import path that changed.
16
+ - Scope lint to specific files — do not run project-wide lint that auto-fixes unrelated files.
17
+
18
+ ---
19
+
20
+ ## PRD
21
+
22
+ {prd_content}
23
+
24
+ ---
25
+
26
+ ## Issue {issue_number}: {issue_filename}
27
+
28
+ {issue_content}
package/src/types.ts ADDED
@@ -0,0 +1,83 @@
1
+ /**
2
+ * Core types and interfaces shared across the Stonecut CLI.
3
+ */
4
+
5
+ /** Structured result from a single runner execution. */
6
+ export interface RunResult {
7
+ success: boolean;
8
+ exitCode: number;
9
+ durationSeconds: number;
10
+ output?: string;
11
+ error?: string;
12
+ }
13
+
14
+ /** Result of a single afk iteration. */
15
+ export interface IterationResult {
16
+ issueNumber: number;
17
+ issueFilename: string;
18
+ success: boolean;
19
+ elapsedSeconds: number;
20
+ error?: string;
21
+ }
22
+
23
+ /** Protocol that all runner adapters must satisfy. */
24
+ export interface Runner {
25
+ run(prompt: string): Promise<RunResult>;
26
+ }
27
+
28
+ /** Snapshot of the working tree state before a runner session. */
29
+ export interface WorkingTreeSnapshot {
30
+ untracked: Set<string>;
31
+ }
32
+
33
+ /** Git operations used by the runner loop, injectable for testing. */
34
+ export interface GitOps {
35
+ snapshotWorkingTree(): WorkingTreeSnapshot;
36
+ stageChanges(snapshot: WorkingTreeSnapshot): boolean;
37
+ commitChanges(message: string): [boolean, string];
38
+ revertUncommitted(snapshot: WorkingTreeSnapshot): void;
39
+ }
40
+
41
+ /** A single issue from a local spec. */
42
+ export interface Issue {
43
+ number: number;
44
+ filename: string;
45
+ path: string;
46
+ content: string;
47
+ }
48
+
49
+ /** Structured metadata for a GitHub-backed PRD issue. */
50
+ export interface GitHubPrd {
51
+ number: number;
52
+ title: string;
53
+ body: string;
54
+ }
55
+
56
+ /** A single sub-issue from a GitHub PRD. */
57
+ export interface GitHubIssue {
58
+ number: number;
59
+ title: string;
60
+ body: string;
61
+ }
62
+
63
+ /** Source interface for reading issues from any backend (local or GitHub). */
64
+ export interface Source<T> {
65
+ getNextIssue(): Promise<T | null>;
66
+ completeIssue(issue: T): Promise<void>;
67
+ getRemainingCount(): Promise<[number, number]>;
68
+ getPrdContent(): Promise<string>;
69
+ }
70
+
71
+ /** Logger interface for session-scoped logging. */
72
+ export interface LogWriter {
73
+ log(message: string): void;
74
+ close(): void;
75
+ }
76
+
77
+ /** Session context threaded through a stonecut run. */
78
+ export interface Session {
79
+ logger: LogWriter;
80
+ git: GitOps;
81
+ runner: Runner;
82
+ runnerName: string;
83
+ }