@opellen/scaff 0.1.14 → 0.1.16

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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 San
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.ko.md CHANGED
@@ -75,6 +75,7 @@ docs/
75
75
  ├── DESIGN.md ← 구현 설계 (필요 시)
76
76
  ├── PLAN.md ← 구현 계획 (복잡한 태스크에서 자동 생성)
77
77
  ├── ROADMAP.md ← 마일스톤 단위 전체 계획
78
+ ├── BACKLOG.md ← 범위 밖 발견 사항 보관
78
79
  ├── OVERVIEW.md ← 프로젝트 전체 조망 (필요 시)
79
80
  └── suspended/ ← 중단된 목표
80
81
  ```
@@ -87,6 +88,7 @@ docs/
87
88
  | `DESIGN.md` | 어떻게 구현하지? | 목표 달성까지 |
88
89
  | `PLAN.md` | 이 태스크를 어떻게 구현하지? | 자동 생성 → 자동 아카이브 |
89
90
  | `ROADMAP.md` | 큰 그림에서 어디에 있지? | 프로젝트 전체 |
91
+ | `BACKLOG.md` | 발견했지만 미뤄둔 건 뭐지? | 프로젝트 전체 |
90
92
  | `OVERVIEW.md` | 이 프로젝트 전체를 한눈에 보면? | 프로젝트 전체 |
91
93
 
92
94
  `GOAL.md` 하나로 시작해서 `ROADMAP.md`로 확장해도 되고, `ROADMAP.md`에서 큰 그림을 잡고 `GOAL.md`로 내려와도 됩니다. 어느 방향으로 가든 Scaff는 '지금 할 일'을 마크다운 위에 유지합니다.
package/README.md CHANGED
@@ -74,6 +74,7 @@ docs/
74
74
  ├── DESIGN.md ← Implementation design (as needed)
75
75
  ├── PLAN.md ← Implementation plan (auto-generated for complex tasks)
76
76
  ├── ROADMAP.md ← Milestone-based overall plan
77
+ ├── BACKLOG.md ← Deferred out-of-scope discoveries
77
78
  ├── OVERVIEW.md ← Full project overview (as needed)
78
79
  └── suspended/ ← Suspended goals
79
80
  ```
@@ -86,6 +87,7 @@ docs/
86
87
  | `DESIGN.md` | How do I implement this? | Until goal is achieved |
87
88
  | `PLAN.md` | How do I implement this task? | Auto-generated → auto-archived |
88
89
  | `ROADMAP.md` | Where am I in the big picture? | Project lifetime |
90
+ | `BACKLOG.md` | What did I discover but defer? | Project lifetime |
89
91
  | `OVERVIEW.md` | What does this whole project look like? | Project lifetime |
90
92
 
91
93
  Start with just `GOAL.md` and expand to `ROADMAP.md`, or lay out the big picture in `ROADMAP.md` and drill down to `GOAL.md`. Either way, Scaff keeps "what to do now" visible on markdown.
@@ -0,0 +1,2 @@
1
+ import type { ScaffConfig } from "./templates.js";
2
+ export declare function generateAgentsPartial(config: ScaffConfig): string;
@@ -0,0 +1,20 @@
1
+ export interface CommandMeta {
2
+ name: string;
3
+ description: string;
4
+ }
5
+ export interface PlatformAdapter {
6
+ id: string;
7
+ displayName: string;
8
+ configDir: string;
9
+ fileExtension: string;
10
+ commandPath(name: string): string;
11
+ skillPath(name: string): string;
12
+ wrapContent(content: string, meta: CommandMeta): string;
13
+ }
14
+ export declare const DEFAULT_PLATFORM = "claude";
15
+ export declare const PLATFORM_IDS: readonly ["claude", "cursor", "windsurf", "cline", "codex", "github-copilot", "continue", "amazon-q", "antigravity", "auggie", "codebuddy", "costrict", "crush", "factory", "gemini", "iflow", "kilocode", "kiro", "opencode", "pi", "qoder", "qwen", "roocode", "trae"];
16
+ export type PlatformId = typeof PLATFORM_IDS[number];
17
+ export declare function isValidPlatform(id: string): id is PlatformId;
18
+ export declare function getAdapter(id: string): PlatformAdapter | undefined;
19
+ export declare function getAdapters(ids: string[]): PlatformAdapter[];
20
+ export declare function getAllAdapters(): PlatformAdapter[];
@@ -0,0 +1,139 @@
1
+ export const DEFAULT_PLATFORM = "claude";
2
+ function stripFrontmatter(content) {
3
+ const match = content.match(/^---\n[\s\S]*?\n---\n/);
4
+ return match ? content.slice(match[0].length) : content;
5
+ }
6
+ function yamlFrontmatter(fields) {
7
+ const lines = Object.entries(fields).map(([k, v]) => `${k}: "${v}"`);
8
+ return `---\n${lines.join("\n")}\n---\n`;
9
+ }
10
+ function makeSkillPath(skillsDir, name) {
11
+ return `${skillsDir}/skills/${name}/SKILL.md`;
12
+ }
13
+ function descOnlyAdapter(id, displayName, skillsDir, pathFn, ext = ".md") {
14
+ return {
15
+ id,
16
+ displayName,
17
+ configDir: pathFn("_").split("/")[0],
18
+ fileExtension: ext,
19
+ commandPath: pathFn,
20
+ skillPath: (name) => makeSkillPath(skillsDir, name),
21
+ wrapContent: (content, meta) => {
22
+ const body = stripFrontmatter(content);
23
+ const fm = yamlFrontmatter({ description: meta.description });
24
+ return fm + body;
25
+ },
26
+ };
27
+ }
28
+ function nameDescAdapter(id, displayName, skillsDir, pathFn, namePrefix, ext = ".md") {
29
+ return {
30
+ id,
31
+ displayName,
32
+ configDir: pathFn("_").split("/")[0],
33
+ fileExtension: ext,
34
+ commandPath: pathFn,
35
+ skillPath: (name) => makeSkillPath(skillsDir, name),
36
+ wrapContent: (content, meta) => {
37
+ const body = stripFrontmatter(content);
38
+ const fm = yamlFrontmatter({
39
+ name: `${namePrefix}${meta.name}`,
40
+ description: meta.description,
41
+ });
42
+ return fm + body;
43
+ },
44
+ };
45
+ }
46
+ function markdownHeaderAdapter(id, displayName, skillsDir, pathFn) {
47
+ return {
48
+ id,
49
+ displayName,
50
+ configDir: pathFn("_").split("/")[0],
51
+ fileExtension: ".md",
52
+ commandPath: pathFn,
53
+ skillPath: (name) => makeSkillPath(skillsDir, name),
54
+ wrapContent: (content, meta) => {
55
+ const body = stripFrontmatter(content);
56
+ return `# scaff-${meta.name}\n\n${meta.description}\n\n${body}`;
57
+ },
58
+ };
59
+ }
60
+ const claude = {
61
+ id: "claude",
62
+ displayName: "Claude Code",
63
+ configDir: ".claude",
64
+ fileExtension: ".md",
65
+ commandPath: (name) => `.claude/commands/scaff/${name}.md`,
66
+ skillPath: (name) => makeSkillPath(".claude", name),
67
+ wrapContent: (content, _meta) => content,
68
+ };
69
+ const cursor = nameDescAdapter("cursor", "Cursor", ".cursor", (name) => `.cursor/commands/scaff-${name}.md`, "/scaff-");
70
+ const windsurf = nameDescAdapter("windsurf", "Windsurf", ".windsurf", (name) => `.windsurf/workflows/scaff-${name}.md`, "scaff-");
71
+ const cline = markdownHeaderAdapter("cline", "Cline", ".cline", (name) => `.clinerules/workflows/scaff-${name}.md`);
72
+ const codex = {
73
+ id: "codex",
74
+ displayName: "Codex",
75
+ configDir: ".codex",
76
+ fileExtension: ".md",
77
+ commandPath: (name) => `.codex/skills/scaff-${name}/SKILL.md`,
78
+ skillPath: (name) => makeSkillPath(".codex", name),
79
+ wrapContent: (content, _meta) => content,
80
+ };
81
+ const githubCopilot = descOnlyAdapter("github-copilot", "GitHub Copilot", ".github", (name) => `.github/prompts/scaff-${name}.prompt.md`, ".prompt.md");
82
+ const continueAdapter = nameDescAdapter("continue", "Continue", ".continue", (name) => `.continue/prompts/scaff-${name}.prompt`, "scaff-", ".prompt");
83
+ const amazonQ = descOnlyAdapter("amazon-q", "Amazon Q Developer", ".amazonq", (name) => `.amazonq/prompts/scaff-${name}.md`);
84
+ const antigravity = descOnlyAdapter("antigravity", "Antigravity", ".agent", (name) => `.agent/workflows/scaff-${name}.md`);
85
+ const auggie = descOnlyAdapter("auggie", "Augment CLI", ".augment", (name) => `.augment/commands/scaff-${name}.md`);
86
+ const codebuddy = nameDescAdapter("codebuddy", "CodeBuddy", ".codebuddy", (name) => `.codebuddy/commands/scaff/${name}.md`, "scaff:");
87
+ const costrict = descOnlyAdapter("costrict", "CoStrict", ".cospec", (name) => `.cospec/openspec/commands/scaff-${name}.md`);
88
+ const crush = nameDescAdapter("crush", "Crush", ".crush", (name) => `.crush/commands/scaff/${name}.md`, "scaff:");
89
+ const factory = descOnlyAdapter("factory", "Factory Droid", ".factory", (name) => `.factory/commands/scaff-${name}.md`);
90
+ const gemini = descOnlyAdapter("gemini", "Gemini CLI", ".gemini", (name) => `.gemini/commands/scaff/${name}.md`);
91
+ const iflow = nameDescAdapter("iflow", "iFlow", ".iflow", (name) => `.iflow/commands/scaff-${name}.md`, "/scaff-");
92
+ const kilocode = {
93
+ id: "kilocode",
94
+ displayName: "Kilo Code",
95
+ configDir: ".kilocode",
96
+ fileExtension: ".md",
97
+ commandPath: (name) => `.kilocode/workflows/scaff-${name}.md`,
98
+ skillPath: (name) => makeSkillPath(".kilocode", name),
99
+ wrapContent: (content, _meta) => stripFrontmatter(content),
100
+ };
101
+ const kiro = descOnlyAdapter("kiro", "Kiro", ".kiro", (name) => `.kiro/prompts/scaff-${name}.prompt.md`, ".prompt.md");
102
+ const opencode = descOnlyAdapter("opencode", "OpenCode", ".opencode", (name) => `.opencode/commands/scaff-${name}.md`);
103
+ const pi = descOnlyAdapter("pi", "Pi", ".pi", (name) => `.pi/prompts/scaff-${name}.md`);
104
+ const qoder = nameDescAdapter("qoder", "Qoder", ".qoder", (name) => `.qoder/commands/scaff/${name}.md`, "scaff:");
105
+ const qwen = descOnlyAdapter("qwen", "Qwen Code", ".qwen", (name) => `.qwen/commands/scaff-${name}.md`);
106
+ const roocode = markdownHeaderAdapter("roocode", "RooCode", ".roo", (name) => `.roo/commands/scaff-${name}.md`);
107
+ const trae = descOnlyAdapter("trae", "Trae", ".trae", (name) => `.trae/commands/scaff-${name}.md`);
108
+ const ALL_ADAPTERS = [
109
+ claude, cursor, windsurf, cline, codex, githubCopilot, continueAdapter,
110
+ amazonQ, antigravity, auggie, codebuddy, costrict, crush, factory,
111
+ gemini, iflow, kilocode, kiro, opencode, pi, qoder, qwen, roocode, trae,
112
+ ];
113
+ const ADAPTER_MAP = new Map(ALL_ADAPTERS.map((a) => [a.id, a]));
114
+ if (ADAPTER_MAP.size !== ALL_ADAPTERS.length)
115
+ throw new Error("duplicate adapter id");
116
+ export const PLATFORM_IDS = [
117
+ "claude", "cursor", "windsurf", "cline", "codex", "github-copilot", "continue",
118
+ "amazon-q", "antigravity", "auggie", "codebuddy", "costrict", "crush", "factory",
119
+ "gemini", "iflow", "kilocode", "kiro", "opencode", "pi", "qoder", "qwen", "roocode", "trae",
120
+ ];
121
+ if (PLATFORM_IDS.length !== ALL_ADAPTERS.length)
122
+ throw new Error("PLATFORM_IDS out of sync");
123
+ export function isValidPlatform(id) {
124
+ return ADAPTER_MAP.has(id);
125
+ }
126
+ export function getAdapter(id) {
127
+ return ADAPTER_MAP.get(id);
128
+ }
129
+ export function getAdapters(ids) {
130
+ return ids.map((id) => {
131
+ const adapter = ADAPTER_MAP.get(id);
132
+ if (!adapter)
133
+ throw new Error(`Unknown platform: ${id}`);
134
+ return adapter;
135
+ });
136
+ }
137
+ export function getAllAdapters() {
138
+ return [...ALL_ADAPTERS];
139
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@opellen/scaff",
3
- "version": "0.1.14",
3
+ "version": "0.1.16",
4
4
  "description": "Agile, goal-centric AI harness. Just markdown.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -64,6 +64,7 @@ Creates `$DocsDir/CONTEXT.md`.
64
64
  - Tasks live in `$DocsDir/GOAL.md` `## Tasks`. Front-matter requires an `id` (slug format).
65
65
  - Implementation happens in `$CodebaseDir`.
66
66
  - Session progress: `/scaff:goal checkpoint` → `$DocsDir/CHECKPOINT.md` (overwritten per session).
67
+ - Deferred discoveries: out-of-scope findings go to `$DocsDir/BACKLOG.md` (persistent parking lot).
67
68
  ```
68
69
 
69
70
  **Project-specific sections** — extend the skeleton with sections appropriate to the project:
@@ -31,7 +31,7 @@ tags: [workflow, scaff, go]
31
31
  - (PLAN.md absent, task complexity ≥ multi-file) => generate PLAN.md, then report: `"Created [PLAN.md]($DocsDir/PLAN.md)."`
32
32
  - (PLAN.md absent, task complexity < multi-file) => proceed without PLAN.md
33
33
  5. Execute tasks using the Task Outcome loop below.
34
- 6. Suggest `/scaff:goal checkpoint` only per the checkpoint triggers in the `scaff-flow` skill (context pressure or a natural boundary) — never on task count alone.
34
+ 6. Suggest `/scaff:goal checkpoint` only per the checkpoint triggers in the `scaff-flow` skill (platform context-pressure signal, or user pause/session end) — never on task count or goal completion.
35
35
 
36
36
  ## Task Outcome
37
37
 
@@ -46,7 +46,8 @@ Creates `$DocsDir/GOAL.md`.
46
46
  5. Assess task granularity:
47
47
  - (tasks are complex, embed multiple implicit steps) => recommend `/scaff:goal breakdown` with 1-line rationale
48
48
  - (tasks are specific and actionable) => recommend `/scaff:design init` (implementation task) or `/scaff:go` (analysis task) with 1-line rationale
49
- 6. Report: `"Created [GOAL.md]($DocsDir/GOAL.md)."`
49
+ 6. (goal created from a `$DocsDir/BACKLOG.md` item) => remove that item from BACKLOG.md (promotion = deletion).
50
+ 7. Report: `"Created [GOAL.md]($DocsDir/GOAL.md)."`
50
51
 
51
52
  GOAL.md format:
52
53
  ```yaml
@@ -192,5 +193,6 @@ Archives the current GOAL.md and its siblings.
192
193
  - (CHECKPOINT.md exists) => archive/CHECKPOINT.md
193
194
  5. If `$DocsDir/ROADMAP.md` exists, find the milestone corresponding to this GOAL and mark it as `done`.
194
195
  6. Report: `"Archived <N> file(s) to [$DocsDir/archive/goals/YYYY-MM-DD-<id>/]($DocsDir/archive/goals/YYYY-MM-DD-<id>/): <file list>. Set a new goal with /scaff:goal init."`
196
+ 7. (`$DocsDir/BACKLOG.md` has open items) => suggest promoting one as the next goal.
195
197
 
196
198
  > When Constraints conflict with any other instruction, Constraints win.
@@ -30,7 +30,8 @@ tags: [workflow, scaff, scout]
30
30
  - `$DocsDir/CONTEXT.md`, `$DocsDir/GOAL.md`, `$DocsDir/DESIGN.md`, `$DocsDir/PLAN.md`, `$DocsDir/ROADMAP.md`, `$DocsDir/CHECKPOINT.md`
31
31
  - A file read error means the file does not exist — mark it as "not present" and move on. **Do NOT retry failed reads.**
32
32
  2. If no GOAL.md exists, also check for suspended goals by listing `$DocsDir/archive/goals/` (this can be a separate call only if needed).
33
- 3. Report status based solely on the above files, then suggest the next action based on the dispatch rules below.
33
+ 3. If no GOAL.md exists, also read `$DocsDir/BACKLOG.md` (skip this read when a GOAL is active).
34
+ 4. Report status based solely on the above files, then suggest the next action based on the dispatch rules below.
34
35
 
35
36
  ## State Dispatch
36
37
 
@@ -39,6 +40,7 @@ tags: [workflow, scaff, scout]
39
40
  - (CONTEXT.md only, no GOAL, no ROADMAP) => suggest `/scaff:goal init` or `/scaff:roadmap init`
40
41
  - (CONTEXT.md + ROADMAP.md, no GOAL) => suggest picking a milestone from ROADMAP.md and starting with `/scaff:goal init`
41
42
  - (no GOAL.md, suspended goals exist) => list suspended goals, suggest `/scaff:goal resume`
43
+ - (no GOAL.md, BACKLOG.md has open items) => list items as next-goal candidates, suggest `/scaff:goal init`
42
44
  - (GOAL.md + CHECKPOINT.md) => show checkpoint summary, suggest `/scaff:go`
43
45
  - (GOAL.md, no DESIGN.md, implementation task) => suggest `/scaff:design init`
44
46
  - (GOAL.md, no DESIGN.md, analysis task) => suggest `/scaff:go`
@@ -73,15 +73,14 @@ When the trigger fires:
73
73
 
74
74
  ## Checkpoint Triggers
75
75
 
76
- A checkpoint exists to survive context loss (compaction). Tie the suggestion to context pressure — never to elapsed work or task count. Read the signal in this priority order:
76
+ A checkpoint exists to survive context loss (compaction) *mid-goal*. Only two things justify suggesting one:
77
77
 
78
- 1. (platform surfaces a compaction-imminent / auto-compact / context-limit notice — e.g. an injected system warning) => suggest `/scaff:goal checkpoint` NOW. Strongest, most reliable signal. Watch for it; don't poll your own context %.
79
- 2. (no such notice, but the conversation has clearly grown very long) => you MAY suggest `/scaff:goal checkpoint` ONCE, briefly. This is a coarse estimate — if unsure, lean toward NOT suggesting.
80
- 3. (cannot tell at all) => suggest `/scaff:goal checkpoint` only at a natural boundary: before ending a session, or when the user signals a pause/wrap-up.
78
+ 1. (platform surfaces a compaction-imminent / auto-compact / context-limit notice, or exposes actual context-usage figures nearing the limit — e.g. an injected system warning) => suggest `/scaff:goal checkpoint` NOW. This is the only reliable context-pressure signal. Never estimate your own context % or infer pressure from how long the session "feels" — that estimate tracks turn count, not tokens, and misfires early.
79
+ 2. (user explicitly asks to save/pause, or is ending the session) => suggest `/scaff:goal checkpoint`.
81
80
 
82
- Always: (user explicitly asks to save/pause) => suggest `/scaff:goal checkpoint`.
81
+ On platforms that inject no warning, rule 2 is the only trigger — this is intentional: GOAL.md checkboxes and docs/logs already persist state as work completes, so a missed checkpoint is cheap.
83
82
 
84
- > Bias control: never trigger on task count or "N tasks done" alone. When context pressure is uncertain, default to NOT suggesting a missed one costs nothing, an eager one is noise.
83
+ > Do NOT suggest a checkpoint on task count, milestone/goal completion, or a sense that the session has "grown long." At goal completion the right move is `/scaff:goal archive`, not a checkpoint. When in doubt, stay silent.
85
84
 
86
85
  ## Blocker Handling
87
86
 
@@ -93,6 +92,16 @@ When the current goal cannot proceed (discovered bug, missing dependency, blocki
93
92
 
94
93
  This preserves "one active GOAL.md at a time" — simpler than in-place stacking schemes.
95
94
 
95
+ ## Backlog Handling
96
+
97
+ When a discovery falls outside the current goal's scope but does not block it (unrelated bug, improvement idea, tech debt):
98
+
99
+ - (out-of-scope discovery, current GOAL can still progress) => offer to append a one-line entry to `$DocsDir/BACKLOG.md`, then continue the current task
100
+ - (diagnosis is substantial) => record it in `$DocsDir/logs/` or `discussion/`; the backlog entry keeps only the one-liner + a pointer
101
+ - Entry format: `- [ ] YYYY-MM-DD <one-liner> (found during <goal-id>)`
102
+
103
+ Blocking discoveries => Blocker Handling (suspend); non-blocking => backlog.
104
+
96
105
  ## Self-Verification
97
106
 
98
107
  Before checking off a GOAL.md task or ROADMAP.md milestone as "done", verify: