@opellen/scaff 0.1.15 → 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 +21 -0
- package/README.ko.md +2 -0
- package/README.md +2 -0
- package/dist/agents-partial.d.ts +2 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +235 -0
- package/dist/extend.d.ts +12 -0
- package/dist/extend.js +242 -0
- package/dist/hook-parser.d.ts +15 -0
- package/dist/hook-parser.js +95 -0
- package/dist/i18n/index.d.ts +5 -0
- package/dist/i18n/index.js +20 -0
- package/dist/i18n/messages.d.ts +28 -0
- package/dist/i18n/messages.js +49 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +4 -0
- package/dist/installer.d.ts +10 -0
- package/dist/installer.js +74 -0
- package/dist/marker.d.ts +11 -0
- package/dist/marker.js +25 -0
- package/dist/platforms/amazon-q.d.ts +2 -0
- package/dist/platforms/amazon-q.js +12 -0
- package/dist/platforms/antigravity.d.ts +2 -0
- package/dist/platforms/antigravity.js +12 -0
- package/dist/platforms/auggie.d.ts +2 -0
- package/dist/platforms/auggie.js +15 -0
- package/dist/platforms/claude.d.ts +2 -0
- package/dist/platforms/claude.js +7 -0
- package/dist/platforms/cline.d.ts +2 -0
- package/dist/platforms/cline.js +11 -0
- package/dist/platforms/codebuddy.d.ts +2 -0
- package/dist/platforms/codebuddy.js +16 -0
- package/dist/platforms/codex.d.ts +2 -0
- package/dist/platforms/codex.js +7 -0
- package/dist/platforms/continue.d.ts +2 -0
- package/dist/platforms/continue.js +16 -0
- package/dist/platforms/costrict.d.ts +2 -0
- package/dist/platforms/costrict.js +15 -0
- package/dist/platforms/crush.d.ts +2 -0
- package/dist/platforms/crush.js +19 -0
- package/dist/platforms/cursor.d.ts +2 -0
- package/dist/platforms/cursor.js +18 -0
- package/dist/platforms/factory.d.ts +2 -0
- package/dist/platforms/factory.js +15 -0
- package/dist/platforms/gemini.d.ts +2 -0
- package/dist/platforms/gemini.js +14 -0
- package/dist/platforms/github-copilot.d.ts +2 -0
- package/dist/platforms/github-copilot.js +12 -0
- package/dist/platforms/iflow.d.ts +2 -0
- package/dist/platforms/iflow.js +18 -0
- package/dist/platforms/index.d.ts +10 -0
- package/dist/platforms/index.js +68 -0
- package/dist/platforms/kilocode.d.ts +2 -0
- package/dist/platforms/kilocode.js +8 -0
- package/dist/platforms/kiro.d.ts +2 -0
- package/dist/platforms/kiro.js +12 -0
- package/dist/platforms/opencode.d.ts +2 -0
- package/dist/platforms/opencode.js +12 -0
- package/dist/platforms/pi.d.ts +2 -0
- package/dist/platforms/pi.js +12 -0
- package/dist/platforms/qoder.d.ts +2 -0
- package/dist/platforms/qoder.js +19 -0
- package/dist/platforms/qwen.d.ts +2 -0
- package/dist/platforms/qwen.js +14 -0
- package/dist/platforms/roocode.d.ts +2 -0
- package/dist/platforms/roocode.js +11 -0
- package/dist/platforms/toml.d.ts +9 -0
- package/dist/platforms/toml.js +18 -0
- package/dist/platforms/trae.d.ts +2 -0
- package/dist/platforms/trae.js +11 -0
- package/dist/platforms/types.d.ts +19 -0
- package/dist/platforms/types.js +1 -0
- package/dist/platforms/windsurf.d.ts +2 -0
- package/dist/platforms/windsurf.js +19 -0
- package/dist/platforms/yaml.d.ts +10 -0
- package/dist/platforms/yaml.js +30 -0
- package/dist/platforms.d.ts +20 -0
- package/dist/platforms.js +139 -0
- package/dist/prompts/platform-select.d.ts +1 -0
- package/dist/prompts/platform-select.js +174 -0
- package/dist/skills/generate.d.ts +13 -0
- package/dist/skills/generate.js +16 -0
- package/dist/templates.d.ts +18 -0
- package/dist/templates.js +96 -0
- package/dist/ui/ascii-logo.d.ts +6 -0
- package/dist/ui/ascii-logo.js +38 -0
- package/dist/ui/colors.d.ts +2 -0
- package/dist/ui/colors.js +3 -0
- package/dist/ui/welcome.d.ts +1 -0
- package/dist/ui/welcome.js +77 -0
- package/package.json +1 -1
- package/templates/commands/context.md +1 -0
- package/templates/commands/goal.md +3 -1
- package/templates/commands/scout.md +3 -1
- package/templates/skills/scaff-flow/SKILL.md +10 -0
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import { readFileSync } from "node:fs";
|
|
2
|
+
import { join, dirname } from "node:path";
|
|
3
|
+
import { fileURLToPath } from "node:url";
|
|
4
|
+
import { renderTemplate } from "./marker.js";
|
|
5
|
+
import { getAdapters } from "./platforms/index.js";
|
|
6
|
+
import { skillInstallPath, formatSkill } from "./skills/generate.js";
|
|
7
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
8
|
+
const TEMPLATE_DIR = join(__dirname, "..", "templates");
|
|
9
|
+
// Installed CLI version — injected into skill metadata.generatedBy.
|
|
10
|
+
// Read once at module load from the shipped package.json.
|
|
11
|
+
const SCAFF_VERSION = (() => {
|
|
12
|
+
try {
|
|
13
|
+
const pkg = JSON.parse(readFileSync(join(__dirname, "..", "package.json"), "utf-8"));
|
|
14
|
+
return String(pkg.version ?? "0.0.0");
|
|
15
|
+
}
|
|
16
|
+
catch {
|
|
17
|
+
return "0.0.0";
|
|
18
|
+
}
|
|
19
|
+
})();
|
|
20
|
+
const COMMAND_TEMPLATES = [
|
|
21
|
+
"scout", "goal", "context", "design",
|
|
22
|
+
"roadmap", "verify", "recap", "overview", "go",
|
|
23
|
+
];
|
|
24
|
+
const SKILL_TEMPLATES = [
|
|
25
|
+
{ src: "skills/scaff-subagent/SKILL.md", skillName: "scaff-subagent" },
|
|
26
|
+
{ src: "skills/scaff-flow/SKILL.md", skillName: "scaff-flow" },
|
|
27
|
+
];
|
|
28
|
+
function parseMeta(content) {
|
|
29
|
+
const normalized = content.replace(/\r\n/g, "\n");
|
|
30
|
+
const fmMatch = normalized.match(/^---\n([\s\S]*?)\n---/);
|
|
31
|
+
if (!fmMatch)
|
|
32
|
+
return { name: "", description: "" };
|
|
33
|
+
const fm = fmMatch[1];
|
|
34
|
+
const nameMatch = fm.match(/name:\s*"?scaff:(\w+)"?/);
|
|
35
|
+
const descMatch = fm.match(/description:\s*"([^"]+)"/);
|
|
36
|
+
const categoryMatch = fm.match(/category:\s*(\S+)/);
|
|
37
|
+
const tagsMatch = fm.match(/tags:\s*\[([^\]]*)\]/);
|
|
38
|
+
const tags = tagsMatch
|
|
39
|
+
? tagsMatch[1]
|
|
40
|
+
.split(",")
|
|
41
|
+
.map((s) => s.trim().replace(/^["']|["']$/g, ""))
|
|
42
|
+
.filter(Boolean)
|
|
43
|
+
: undefined;
|
|
44
|
+
return {
|
|
45
|
+
name: nameMatch?.[1] ?? "",
|
|
46
|
+
description: descMatch?.[1] ?? "",
|
|
47
|
+
category: categoryMatch?.[1],
|
|
48
|
+
tags,
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
export function resolveTemplates(config) {
|
|
52
|
+
const adapters = getAdapters(config.tools);
|
|
53
|
+
const entries = [];
|
|
54
|
+
const commandMetas = COMMAND_TEMPLATES.map((cmd) => {
|
|
55
|
+
const src = `commands/${cmd}.md`;
|
|
56
|
+
const rawContent = readFileSync(join(TEMPLATE_DIR, src), "utf-8");
|
|
57
|
+
return { cmd, src, rawContent, meta: parseMeta(rawContent) };
|
|
58
|
+
});
|
|
59
|
+
const skillContents = SKILL_TEMPLATES.map((skill) => ({
|
|
60
|
+
...skill,
|
|
61
|
+
rawContent: readFileSync(join(TEMPLATE_DIR, skill.src), "utf-8"),
|
|
62
|
+
}));
|
|
63
|
+
for (const adapter of adapters) {
|
|
64
|
+
if (adapter.supportsCommands !== false) {
|
|
65
|
+
for (const { cmd, src, rawContent, meta } of commandMetas) {
|
|
66
|
+
entries.push({ src, dst: adapter.commandPath(cmd), adapter, meta, rawContent });
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
for (const skill of skillContents) {
|
|
70
|
+
entries.push({
|
|
71
|
+
src: skill.src,
|
|
72
|
+
dst: skillInstallPath(adapter, skill.skillName),
|
|
73
|
+
adapter,
|
|
74
|
+
meta: { name: skill.skillName, description: "" },
|
|
75
|
+
rawContent: skill.rawContent,
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
return entries;
|
|
80
|
+
}
|
|
81
|
+
export function renderFile(entry, config) {
|
|
82
|
+
const content = entry.rawContent.replace(/\r\n/g, "\n");
|
|
83
|
+
const rendered = renderTemplate(content, {
|
|
84
|
+
conditions: {
|
|
85
|
+
subagent: config.subagent,
|
|
86
|
+
},
|
|
87
|
+
});
|
|
88
|
+
const substituted = rendered
|
|
89
|
+
.replace(/\$DocsDir/g, config.docsDir)
|
|
90
|
+
.replace(/\$CodebaseDir/g, config.codebaseDir)
|
|
91
|
+
.replace(/\$ScaffVersion/g, SCAFF_VERSION);
|
|
92
|
+
if (entry.src.startsWith("skills/")) {
|
|
93
|
+
return formatSkill(substituted);
|
|
94
|
+
}
|
|
95
|
+
return entry.adapter.formatCommand(substituted, entry.meta);
|
|
96
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export declare const SCAFF_LOGO: {
|
|
2
|
+
readonly frame: readonly [" ████ ████ ", " █ █ ", "█████ █████", " █ █ ", " █ █ ", " █ █ ", " █ █ ", " █ █ ", "█████ █████", " █ █ ", " ████ ████ "];
|
|
3
|
+
readonly inner: readonly [" ", " ", " ██ ████ ", " ▒█ █▒ ", " ▒█ █▒ ", " ▒██▒ ", " ▒█ █▒ ", " ▒█ █▒ ", " ████ ██ ", " ", " "];
|
|
4
|
+
};
|
|
5
|
+
export declare const SCAFF_LOGO_WIDTH: number;
|
|
6
|
+
export declare const SCAFF_LOGO_HEIGHT: 11;
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
// Two-layer logo. Layers are overlaid at render time: any non-space char in
|
|
2
|
+
// `inner` wins over `frame`. This lets the same block char (█, ▒, ...) carry
|
|
3
|
+
// a different color depending on which layer it belongs to, without a parallel
|
|
4
|
+
// mask array to keep in sync.
|
|
5
|
+
//
|
|
6
|
+
// Palette: █ (full), ▓ (dark), ▒ (medium), ░ (light) — shade blocks give a
|
|
7
|
+
// pseudo-anti-aliased gradient on diagonal edges that renders uniformly across
|
|
8
|
+
// monospace fonts, unlike slash/diagonal glyphs (╲ ╱ ╳) which frequently break.
|
|
9
|
+
export const SCAFF_LOGO = {
|
|
10
|
+
frame: [
|
|
11
|
+
" ████ ████ ",
|
|
12
|
+
" █ █ ",
|
|
13
|
+
"█████ █████",
|
|
14
|
+
" █ █ ",
|
|
15
|
+
" █ █ ",
|
|
16
|
+
" █ █ ",
|
|
17
|
+
" █ █ ",
|
|
18
|
+
" █ █ ",
|
|
19
|
+
"█████ █████",
|
|
20
|
+
" █ █ ",
|
|
21
|
+
" ████ ████ ",
|
|
22
|
+
],
|
|
23
|
+
inner: [
|
|
24
|
+
" ",
|
|
25
|
+
" ",
|
|
26
|
+
" ██ ████ ",
|
|
27
|
+
" ▒█ █▒ ",
|
|
28
|
+
" ▒█ █▒ ",
|
|
29
|
+
" ▒██▒ ",
|
|
30
|
+
" ▒█ █▒ ",
|
|
31
|
+
" ▒█ █▒ ",
|
|
32
|
+
" ████ ██ ",
|
|
33
|
+
" ",
|
|
34
|
+
" ",
|
|
35
|
+
],
|
|
36
|
+
};
|
|
37
|
+
export const SCAFF_LOGO_WIDTH = SCAFF_LOGO.frame[0].length;
|
|
38
|
+
export const SCAFF_LOGO_HEIGHT = SCAFF_LOGO.frame.length;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function showWelcome(): Promise<void>;
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import chalk from "chalk";
|
|
2
|
+
import { SCAFF_LOGO, SCAFF_LOGO_HEIGHT, SCAFF_LOGO_WIDTH } from "./ascii-logo.js";
|
|
3
|
+
import { t } from "../i18n/index.js";
|
|
4
|
+
import { brand, brandInner } from "./colors.js";
|
|
5
|
+
export async function showWelcome() {
|
|
6
|
+
if (!process.stdin.isTTY)
|
|
7
|
+
return;
|
|
8
|
+
const cols = process.stdout.columns ?? 80;
|
|
9
|
+
const compact = cols < 60;
|
|
10
|
+
const lines = buildScreen(compact);
|
|
11
|
+
process.stdout.write("\n" + lines.join("\n") + "\n\n");
|
|
12
|
+
await waitForEnter();
|
|
13
|
+
}
|
|
14
|
+
function buildScreen(compact) {
|
|
15
|
+
const rightLines = [
|
|
16
|
+
brand.bold("Scaff"),
|
|
17
|
+
chalk.dim(t("welcome.tagline")),
|
|
18
|
+
"",
|
|
19
|
+
chalk.dim(t("welcome.setsUp")),
|
|
20
|
+
" " + chalk.dim("•") + " " + t("welcome.skills"),
|
|
21
|
+
" " + chalk.dim("•") + " " + t("welcome.commands"),
|
|
22
|
+
"",
|
|
23
|
+
brand(t("welcome.pressEnter")),
|
|
24
|
+
];
|
|
25
|
+
if (compact) {
|
|
26
|
+
return rightLines;
|
|
27
|
+
}
|
|
28
|
+
const gap = " ";
|
|
29
|
+
const totalRows = Math.max(SCAFF_LOGO_HEIGHT, rightLines.length);
|
|
30
|
+
const result = [];
|
|
31
|
+
for (let i = 0; i < totalRows; i++) {
|
|
32
|
+
const logo = i < SCAFF_LOGO_HEIGHT ? colorLogoRow(i) : " ".repeat(SCAFF_LOGO_WIDTH);
|
|
33
|
+
const text = i < rightLines.length ? rightLines[i] : "";
|
|
34
|
+
result.push(logo + gap + text);
|
|
35
|
+
}
|
|
36
|
+
return result;
|
|
37
|
+
}
|
|
38
|
+
function colorLogoRow(row) {
|
|
39
|
+
const frame = SCAFF_LOGO.frame[row];
|
|
40
|
+
const inner = SCAFF_LOGO.inner[row];
|
|
41
|
+
let result = "";
|
|
42
|
+
for (let j = 0; j < SCAFF_LOGO_WIDTH; j++) {
|
|
43
|
+
const innerCh = inner[j];
|
|
44
|
+
if (innerCh !== " ") {
|
|
45
|
+
result += brandInner(innerCh);
|
|
46
|
+
continue;
|
|
47
|
+
}
|
|
48
|
+
const frameCh = frame[j];
|
|
49
|
+
result += frameCh === " " ? " " : brand(frameCh);
|
|
50
|
+
}
|
|
51
|
+
return result;
|
|
52
|
+
}
|
|
53
|
+
function waitForEnter() {
|
|
54
|
+
return new Promise((resolve) => {
|
|
55
|
+
const { stdin } = process;
|
|
56
|
+
const wasRaw = stdin.isRaw ?? false;
|
|
57
|
+
stdin.setRawMode(true);
|
|
58
|
+
stdin.resume();
|
|
59
|
+
const onData = (buf) => {
|
|
60
|
+
const key = buf[0];
|
|
61
|
+
if (key === 0x0d || key === 0x0a) {
|
|
62
|
+
cleanup();
|
|
63
|
+
resolve();
|
|
64
|
+
}
|
|
65
|
+
else if (key === 0x03) {
|
|
66
|
+
cleanup();
|
|
67
|
+
process.exit(0);
|
|
68
|
+
}
|
|
69
|
+
};
|
|
70
|
+
const cleanup = () => {
|
|
71
|
+
stdin.removeListener("data", onData);
|
|
72
|
+
stdin.setRawMode(wasRaw);
|
|
73
|
+
stdin.pause();
|
|
74
|
+
};
|
|
75
|
+
stdin.on("data", onData);
|
|
76
|
+
});
|
|
77
|
+
}
|
package/package.json
CHANGED
|
@@ -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:
|
|
@@ -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.
|
|
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.
|
|
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`
|
|
@@ -92,6 +92,16 @@ When the current goal cannot proceed (discovered bug, missing dependency, blocki
|
|
|
92
92
|
|
|
93
93
|
This preserves "one active GOAL.md at a time" — simpler than in-place stacking schemes.
|
|
94
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
|
+
|
|
95
105
|
## Self-Verification
|
|
96
106
|
|
|
97
107
|
Before checking off a GOAL.md task or ROADMAP.md milestone as "done", verify:
|