@haposoft/cafekit 0.8.16 → 0.9.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 +63 -67
- package/bin/install.js +141 -194
- package/bin/lib/copy-utils.js +61 -0
- package/bin/lib/opencode-install.js +866 -0
- package/package.json +4 -1
- package/src/claude/CLAUDE.md +2 -2
- package/src/claude/migration-manifest.json +1 -0
- package/src/claude/rules/skill-domain-routing.md +2 -0
- package/src/claude/rules/skill-workflow-routing.md +3 -1
- package/src/claude/skills/qs/SKILL.md +177 -0
- package/src/claude/skills/qs/templates/qs.md +30 -0
- package/src/opencode/AGENTS.md +140 -0
- package/src/opencode/plugins/docs-sync.ts +181 -0
- package/src/opencode/plugins/inspect-block.ts +164 -0
- package/src/opencode/plugins/privacy-block.ts +182 -0
- package/src/opencode/plugins/rules.ts +163 -0
- package/src/opencode/plugins/session.ts +179 -0
- package/src/opencode/plugins/state.ts +253 -0
- package/src/opencode/plugins/usage.ts +178 -0
- package/src/antigravity/GEMINI.md +0 -162
- package/src/antigravity/workflows/code.md +0 -16
- package/src/antigravity/workflows/docs-init.md +0 -432
- package/src/antigravity/workflows/docs-update.md +0 -245
- package/src/antigravity/workflows/impact-analysis-output-example.md +0 -313
- package/src/antigravity/workflows/impact-analysis.md +0 -730
- package/src/antigravity/workflows/review.md +0 -15
- package/src/antigravity/workflows/spec-design.md +0 -220
- package/src/antigravity/workflows/spec-init.md +0 -78
- package/src/antigravity/workflows/spec-requirements.md +0 -122
- package/src/antigravity/workflows/spec-status.md +0 -95
- package/src/antigravity/workflows/spec-tasks.md +0 -156
- package/src/antigravity/workflows/spec-validate.md +0 -106
- package/src/antigravity/workflows/test.md +0 -13
|
@@ -0,0 +1,253 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) 2026 Haposoft. MIT License.
|
|
3
|
+
*
|
|
4
|
+
* OpenCode Plugin — state.ts
|
|
5
|
+
*
|
|
6
|
+
* Port of .claude/hooks/state.cjs (partial).
|
|
7
|
+
*
|
|
8
|
+
* Behavior:
|
|
9
|
+
* - On session.created: read .opencode/session-state/latest.md (if fresh)
|
|
10
|
+
* and surface it via stderr + banner file so the assistant sees prior
|
|
11
|
+
* execution context.
|
|
12
|
+
* - On tool.execute.after for state-shaping tools (todowrite, task,
|
|
13
|
+
* taskcreate, taskupdate): refresh snapshot.
|
|
14
|
+
* - On session.idle (≈ Claude Stop): persist snapshot + archive.
|
|
15
|
+
*
|
|
16
|
+
* Dropped:
|
|
17
|
+
* - SubagentStop branch (no equivalent in OpenCode).
|
|
18
|
+
* - Transcript-based todo extraction (OpenCode plugin context lacks
|
|
19
|
+
* transcript_path). Snapshot now captures modifiedFiles only.
|
|
20
|
+
*
|
|
21
|
+
* Storage: .opencode/session-state/latest.md + archive/.
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
import type { Plugin } from "@opencode-ai/plugin";
|
|
25
|
+
import {
|
|
26
|
+
existsSync,
|
|
27
|
+
readFileSync,
|
|
28
|
+
writeFileSync,
|
|
29
|
+
mkdirSync,
|
|
30
|
+
copyFileSync,
|
|
31
|
+
readdirSync,
|
|
32
|
+
unlinkSync,
|
|
33
|
+
renameSync,
|
|
34
|
+
appendFileSync,
|
|
35
|
+
} from "node:fs";
|
|
36
|
+
import { join, dirname } from "node:path";
|
|
37
|
+
import { fileURLToPath } from "node:url";
|
|
38
|
+
import { execSync } from "node:child_process";
|
|
39
|
+
|
|
40
|
+
const PLUGIN_DIR = dirname(fileURLToPath(import.meta.url));
|
|
41
|
+
const EXPIRY_DAYS = 7;
|
|
42
|
+
const MAX_ARCHIVES = 5;
|
|
43
|
+
const STATE_REFRESH_TOOLS = new Set(["todowrite", "task", "taskcreate", "taskupdate"]);
|
|
44
|
+
const STATE_SECTION_START = "<!-- CAFEKIT STATE START -->";
|
|
45
|
+
const STATE_SECTION_END = "<!-- CAFEKIT STATE END -->";
|
|
46
|
+
|
|
47
|
+
interface SnapshotData {
|
|
48
|
+
timestamp: string;
|
|
49
|
+
branch: string;
|
|
50
|
+
modifiedFiles: string[];
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function stateDir(cwd: string): string | null {
|
|
54
|
+
try {
|
|
55
|
+
const dir = join(cwd, ".opencode", "session-state");
|
|
56
|
+
if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
|
|
57
|
+
return dir;
|
|
58
|
+
} catch {
|
|
59
|
+
return null;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function loadLatest(dir: string): string | null {
|
|
64
|
+
try {
|
|
65
|
+
const file = join(dir, "latest.md");
|
|
66
|
+
if (!existsSync(file)) return null;
|
|
67
|
+
const text = readFileSync(file, "utf8");
|
|
68
|
+
const match = text.match(/<!-- Generated: (.+?) -->/);
|
|
69
|
+
if (match) {
|
|
70
|
+
const generatedAt = new Date(match[1]).getTime();
|
|
71
|
+
if (Number.isNaN(generatedAt)) return null;
|
|
72
|
+
if (Date.now() - generatedAt > EXPIRY_DAYS * 24 * 60 * 60 * 1000) return null;
|
|
73
|
+
}
|
|
74
|
+
return text;
|
|
75
|
+
} catch {
|
|
76
|
+
return null;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function writeAtomic(filePath: string, content: string): void {
|
|
81
|
+
const tempFile = `${filePath}.${process.pid}.${Math.random().toString(36).slice(2)}.tmp`;
|
|
82
|
+
writeFileSync(tempFile, content);
|
|
83
|
+
renameSync(tempFile, filePath);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function archive(dir: string): void {
|
|
87
|
+
try {
|
|
88
|
+
const latestFile = join(dir, "latest.md");
|
|
89
|
+
if (!existsSync(latestFile)) return;
|
|
90
|
+
|
|
91
|
+
const archiveDir = join(dir, "archive");
|
|
92
|
+
if (!existsSync(archiveDir)) mkdirSync(archiveDir);
|
|
93
|
+
|
|
94
|
+
const now = new Date();
|
|
95
|
+
const pad = (value: number) => String(value).padStart(2, "0");
|
|
96
|
+
const stamp = `${now.getFullYear()}${pad(now.getMonth() + 1)}${pad(now.getDate())}-${pad(now.getHours())}${pad(now.getMinutes())}`;
|
|
97
|
+
|
|
98
|
+
copyFileSync(latestFile, join(archiveDir, `${stamp}.md`));
|
|
99
|
+
|
|
100
|
+
const files = readdirSync(archiveDir)
|
|
101
|
+
.filter((file) => file.endsWith(".md"))
|
|
102
|
+
.sort();
|
|
103
|
+
while (files.length > MAX_ARCHIVES) {
|
|
104
|
+
const oldest = files.shift();
|
|
105
|
+
if (oldest) {
|
|
106
|
+
try {
|
|
107
|
+
unlinkSync(join(archiveDir, oldest));
|
|
108
|
+
} catch {
|
|
109
|
+
// fail-open
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
} catch {
|
|
114
|
+
// fail-open
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
function extractSnapshot(cwd: string): SnapshotData {
|
|
119
|
+
const data: SnapshotData = {
|
|
120
|
+
timestamp: new Date().toISOString(),
|
|
121
|
+
branch: "",
|
|
122
|
+
modifiedFiles: [],
|
|
123
|
+
};
|
|
124
|
+
|
|
125
|
+
try {
|
|
126
|
+
data.branch = execSync("git branch --show-current", {
|
|
127
|
+
cwd,
|
|
128
|
+
encoding: "utf8",
|
|
129
|
+
timeout: 3000,
|
|
130
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
131
|
+
}).trim();
|
|
132
|
+
} catch {
|
|
133
|
+
// fail-open
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
try {
|
|
137
|
+
const diff = execSync("git diff --name-only HEAD", {
|
|
138
|
+
cwd,
|
|
139
|
+
encoding: "utf8",
|
|
140
|
+
timeout: 3000,
|
|
141
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
142
|
+
}).trim();
|
|
143
|
+
if (diff) data.modifiedFiles = diff.split("\n").slice(0, 20);
|
|
144
|
+
} catch {
|
|
145
|
+
// fail-open
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
return data;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
function buildStateContent(data: SnapshotData): string {
|
|
152
|
+
return [
|
|
153
|
+
"# Session State",
|
|
154
|
+
`<!-- Generated: ${data.timestamp} -->`,
|
|
155
|
+
`<!-- Branch: ${data.branch || "unknown"} -->`,
|
|
156
|
+
"",
|
|
157
|
+
"## Key Files Modified",
|
|
158
|
+
...(data.modifiedFiles.length
|
|
159
|
+
? data.modifiedFiles.map((file) => `- ${file}`)
|
|
160
|
+
: ["- (No file changes detected)"]),
|
|
161
|
+
"",
|
|
162
|
+
].join("\n");
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
function writeBannerSection(cwd: string, content: string | null): void {
|
|
166
|
+
const target = join(cwd, ".opencode", "session-banner.md");
|
|
167
|
+
const dir = dirname(target);
|
|
168
|
+
if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
|
|
169
|
+
|
|
170
|
+
const existing = existsSync(target) ? readFileSync(target, "utf8") : "";
|
|
171
|
+
const pattern = new RegExp(`${STATE_SECTION_START}[\\s\\S]*?${STATE_SECTION_END}\\n?`);
|
|
172
|
+
const cleaned = existing.replace(pattern, "").trimEnd();
|
|
173
|
+
|
|
174
|
+
if (!content) {
|
|
175
|
+
if (cleaned !== existing.trimEnd()) {
|
|
176
|
+
writeFileSync(target, cleaned ? `${cleaned}\n` : "");
|
|
177
|
+
}
|
|
178
|
+
return;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
const block = [STATE_SECTION_START, content, STATE_SECTION_END].join("\n");
|
|
182
|
+
const next = cleaned ? `${cleaned}\n\n${block}\n` : `${block}\n`;
|
|
183
|
+
writeFileSync(target, next);
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
function persistSnapshot(dir: string, data: SnapshotData, options: { archive?: boolean } = {}): void {
|
|
187
|
+
const file = join(dir, "latest.md");
|
|
188
|
+
writeAtomic(file, buildStateContent(data));
|
|
189
|
+
if (options.archive) archive(dir);
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
function logCrash(error: unknown): void {
|
|
193
|
+
try {
|
|
194
|
+
const logDir = join(PLUGIN_DIR, ".logs");
|
|
195
|
+
if (!existsSync(logDir)) mkdirSync(logDir, { recursive: true });
|
|
196
|
+
appendFileSync(
|
|
197
|
+
join(logDir, "hook-log.jsonl"),
|
|
198
|
+
JSON.stringify({
|
|
199
|
+
ts: new Date().toISOString(),
|
|
200
|
+
hook: "state",
|
|
201
|
+
status: "crash",
|
|
202
|
+
error: error instanceof Error ? error.message : String(error),
|
|
203
|
+
}) + "\n",
|
|
204
|
+
);
|
|
205
|
+
} catch {
|
|
206
|
+
// fail-open
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
export const State: Plugin = async ({ directory }) => ({
|
|
211
|
+
event: async ({ event }) => {
|
|
212
|
+
try {
|
|
213
|
+
const dir = stateDir(directory);
|
|
214
|
+
if (!dir) return;
|
|
215
|
+
|
|
216
|
+
if (event.type === "session.created") {
|
|
217
|
+
const previous = loadLatest(dir);
|
|
218
|
+
if (previous) {
|
|
219
|
+
const banner = [
|
|
220
|
+
"=== Prior Execution Context ===",
|
|
221
|
+
previous.trim(),
|
|
222
|
+
"=== End of Prior Context ===",
|
|
223
|
+
].join("\n");
|
|
224
|
+
writeBannerSection(directory, banner);
|
|
225
|
+
console.error(banner);
|
|
226
|
+
} else {
|
|
227
|
+
writeBannerSection(directory, null);
|
|
228
|
+
}
|
|
229
|
+
return;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
if (event.type === "session.idle") {
|
|
233
|
+
const snapshot = extractSnapshot(directory);
|
|
234
|
+
persistSnapshot(dir, snapshot, { archive: true });
|
|
235
|
+
return;
|
|
236
|
+
}
|
|
237
|
+
} catch (error) {
|
|
238
|
+
logCrash(error);
|
|
239
|
+
}
|
|
240
|
+
},
|
|
241
|
+
|
|
242
|
+
"tool.execute.after": async (input) => {
|
|
243
|
+
try {
|
|
244
|
+
if (!STATE_REFRESH_TOOLS.has(input.tool)) return;
|
|
245
|
+
const dir = stateDir(directory);
|
|
246
|
+
if (!dir) return;
|
|
247
|
+
const snapshot = extractSnapshot(directory);
|
|
248
|
+
persistSnapshot(dir, snapshot);
|
|
249
|
+
} catch (error) {
|
|
250
|
+
logCrash(error);
|
|
251
|
+
}
|
|
252
|
+
},
|
|
253
|
+
});
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) 2026 Haposoft. MIT License.
|
|
3
|
+
*
|
|
4
|
+
* OpenCode Plugin — usage.ts
|
|
5
|
+
*
|
|
6
|
+
* Port of .claude/hooks/usage.cjs adapted to OpenCode's plugin surface.
|
|
7
|
+
*
|
|
8
|
+
* Claude's version polls the Anthropic OAuth API for plan usage so the
|
|
9
|
+
* statusline can render quota. OpenCode has no statusline injection point
|
|
10
|
+
* and no OAuth plumbing, so this port instead tracks per-session token
|
|
11
|
+
* usage by inspecting `message.updated` payloads and writing the rolling
|
|
12
|
+
* totals to `.opencode/session-state/usage.json` for downstream tooling.
|
|
13
|
+
*
|
|
14
|
+
* Disable via .opencode/runtime.json: { "usage": { "enabled": false } }.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import type { Plugin } from "@opencode-ai/plugin";
|
|
18
|
+
import {
|
|
19
|
+
existsSync,
|
|
20
|
+
readFileSync,
|
|
21
|
+
writeFileSync,
|
|
22
|
+
mkdirSync,
|
|
23
|
+
} from "node:fs";
|
|
24
|
+
import { join, dirname } from "node:path";
|
|
25
|
+
import { fileURLToPath } from "node:url";
|
|
26
|
+
|
|
27
|
+
const PLUGIN_DIR = dirname(fileURLToPath(import.meta.url));
|
|
28
|
+
|
|
29
|
+
interface UsageTotals {
|
|
30
|
+
inputTokens: number;
|
|
31
|
+
outputTokens: number;
|
|
32
|
+
cacheReadTokens: number;
|
|
33
|
+
cacheWriteTokens: number;
|
|
34
|
+
messages: number;
|
|
35
|
+
lastUpdated: string;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
interface UsageFile {
|
|
39
|
+
sessionId: string;
|
|
40
|
+
startedAt: string;
|
|
41
|
+
totals: UsageTotals;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function resolveProjectDir(directory?: string): string {
|
|
45
|
+
if (directory && existsSync(directory)) return directory;
|
|
46
|
+
// Plugin installed under <project>/.opencode/plugins/, so go up twice.
|
|
47
|
+
return dirname(dirname(PLUGIN_DIR));
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function readRuntime(cwd: string): Record<string, any> {
|
|
51
|
+
try {
|
|
52
|
+
const file = join(cwd, ".opencode", "runtime.json");
|
|
53
|
+
if (existsSync(file)) {
|
|
54
|
+
return JSON.parse(readFileSync(file, "utf8"));
|
|
55
|
+
}
|
|
56
|
+
} catch {
|
|
57
|
+
/* fail-open */
|
|
58
|
+
}
|
|
59
|
+
return {};
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function isEnabled(cwd: string): boolean {
|
|
63
|
+
const runtime = readRuntime(cwd);
|
|
64
|
+
return runtime?.usage?.enabled !== false;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function usagePath(cwd: string): string {
|
|
68
|
+
return join(cwd, ".opencode", "session-state", "usage.json");
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function readUsage(file: string): UsageFile | null {
|
|
72
|
+
try {
|
|
73
|
+
if (!existsSync(file)) return null;
|
|
74
|
+
return JSON.parse(readFileSync(file, "utf8"));
|
|
75
|
+
} catch {
|
|
76
|
+
return null;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function writeUsage(file: string, payload: UsageFile): void {
|
|
81
|
+
try {
|
|
82
|
+
mkdirSync(dirname(file), { recursive: true });
|
|
83
|
+
writeFileSync(file, JSON.stringify(payload, null, 2));
|
|
84
|
+
} catch {
|
|
85
|
+
/* fail-open */
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// Defensive extractor: OpenCode message payloads may expose usage either
|
|
90
|
+
// directly on the message or nested under metadata/usage. Accept both.
|
|
91
|
+
function extractUsage(message: any): {
|
|
92
|
+
input: number;
|
|
93
|
+
output: number;
|
|
94
|
+
cacheRead: number;
|
|
95
|
+
cacheWrite: number;
|
|
96
|
+
} | null {
|
|
97
|
+
const candidates = [
|
|
98
|
+
message?.usage,
|
|
99
|
+
message?.metadata?.usage,
|
|
100
|
+
message?.tokens,
|
|
101
|
+
message?.metadata?.tokens,
|
|
102
|
+
].filter(Boolean);
|
|
103
|
+
|
|
104
|
+
for (const c of candidates) {
|
|
105
|
+
const input =
|
|
106
|
+
Number(c.input_tokens ?? c.inputTokens ?? c.prompt_tokens ?? 0) || 0;
|
|
107
|
+
const output =
|
|
108
|
+
Number(c.output_tokens ?? c.outputTokens ?? c.completion_tokens ?? 0) ||
|
|
109
|
+
0;
|
|
110
|
+
const cacheRead =
|
|
111
|
+
Number(
|
|
112
|
+
c.cache_read_input_tokens ??
|
|
113
|
+
c.cacheReadInputTokens ??
|
|
114
|
+
c.cache_read_tokens ??
|
|
115
|
+
0,
|
|
116
|
+
) || 0;
|
|
117
|
+
const cacheWrite =
|
|
118
|
+
Number(
|
|
119
|
+
c.cache_creation_input_tokens ??
|
|
120
|
+
c.cacheCreationInputTokens ??
|
|
121
|
+
c.cache_write_tokens ??
|
|
122
|
+
0,
|
|
123
|
+
) || 0;
|
|
124
|
+
if (input || output || cacheRead || cacheWrite) {
|
|
125
|
+
return { input, output, cacheRead, cacheWrite };
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
return null;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
export const UsagePlugin: Plugin = async ({ directory }) => {
|
|
132
|
+
const cwd = resolveProjectDir(directory);
|
|
133
|
+
|
|
134
|
+
return {
|
|
135
|
+
event: async ({ event }) => {
|
|
136
|
+
if (event.type !== "message.updated") return;
|
|
137
|
+
if (!isEnabled(cwd)) return;
|
|
138
|
+
|
|
139
|
+
const message = (event as any).properties?.info ?? (event as any).properties?.message;
|
|
140
|
+
if (!message) return;
|
|
141
|
+
|
|
142
|
+
const tokens = extractUsage(message);
|
|
143
|
+
if (!tokens) return;
|
|
144
|
+
|
|
145
|
+
const sessionId = message.sessionID ?? message.sessionId ?? "default";
|
|
146
|
+
const file = usagePath(cwd);
|
|
147
|
+
const now = new Date().toISOString();
|
|
148
|
+
const existing = readUsage(file);
|
|
149
|
+
|
|
150
|
+
const sameSession = existing && existing.sessionId === sessionId;
|
|
151
|
+
const totals: UsageTotals = sameSession
|
|
152
|
+
? existing!.totals
|
|
153
|
+
: {
|
|
154
|
+
inputTokens: 0,
|
|
155
|
+
outputTokens: 0,
|
|
156
|
+
cacheReadTokens: 0,
|
|
157
|
+
cacheWriteTokens: 0,
|
|
158
|
+
messages: 0,
|
|
159
|
+
lastUpdated: now,
|
|
160
|
+
};
|
|
161
|
+
|
|
162
|
+
totals.inputTokens += tokens.input;
|
|
163
|
+
totals.outputTokens += tokens.output;
|
|
164
|
+
totals.cacheReadTokens += tokens.cacheRead;
|
|
165
|
+
totals.cacheWriteTokens += tokens.cacheWrite;
|
|
166
|
+
totals.messages += 1;
|
|
167
|
+
totals.lastUpdated = now;
|
|
168
|
+
|
|
169
|
+
writeUsage(file, {
|
|
170
|
+
sessionId,
|
|
171
|
+
startedAt: sameSession ? existing!.startedAt : now,
|
|
172
|
+
totals,
|
|
173
|
+
});
|
|
174
|
+
},
|
|
175
|
+
};
|
|
176
|
+
};
|
|
177
|
+
|
|
178
|
+
export default UsagePlugin;
|
|
@@ -1,162 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
trigger: always_on
|
|
3
|
-
---
|
|
4
|
-
|
|
5
|
-
# GEMINI.md - Antigravity Kit
|
|
6
|
-
|
|
7
|
-
> This file defines how the AI behaves in this workspace.
|
|
8
|
-
|
|
9
|
-
---
|
|
10
|
-
|
|
11
|
-
## CRITICAL: AGENT & SKILL PROTOCOL (START HERE)
|
|
12
|
-
|
|
13
|
-
> **MANDATORY:** You MUST read the appropriate agent file and its skills BEFORE performing any implementation. This is the highest priority rule.
|
|
14
|
-
|
|
15
|
-
### 1. Modular Skill Loading Protocol
|
|
16
|
-
|
|
17
|
-
Agent activated → Check frontmatter "skills:" → Read SKILL.md (INDEX) → Read specific sections.
|
|
18
|
-
|
|
19
|
-
- **Selective Reading:** DO NOT read ALL files in a skill folder. Read `SKILL.md` first, then only read sections matching the user's request.
|
|
20
|
-
- **Rule Priority:** P0 (GEMINI.md) > P1 (Agent .md) > P2 (SKILL.md). All rules are binding.
|
|
21
|
-
|
|
22
|
-
### 2. Enforcement Protocol
|
|
23
|
-
|
|
24
|
-
1. **When agent is activated:**
|
|
25
|
-
- ✅ Activate: Read Rules → Check Frontmatter → Load SKILL.md → Apply All.
|
|
26
|
-
2. **Forbidden:** Never skip reading agent rules or skill instructions. "Read → Understand → Apply" is mandatory.
|
|
27
|
-
3. **Artifact Ban:** DO NOT create "Implementation Plan" artifacts (sidebar artifacts) unless the user EXPLICITLY asks for them. Use standard markdown files in `docs/` or text responses instead.
|
|
28
|
-
|
|
29
|
-
---
|
|
30
|
-
|
|
31
|
-
## 📥 REQUEST CLASSIFIER (STEP 1)
|
|
32
|
-
|
|
33
|
-
**Before ANY action, classify the request:**
|
|
34
|
-
|
|
35
|
-
| Request Type | Trigger Keywords | Active Tiers | Result |
|
|
36
|
-
| ---------------- | ------------------------------------------ | ------------------------------ | --------------------------- |
|
|
37
|
-
| **QUESTION** | "what is", "how does", "explain" | TIER 0 only | Text Response |
|
|
38
|
-
| **SURVEY/INTEL** | "analyze", "list files", "overview" | TIER 0 + Explorer | Session Intel (No File) |
|
|
39
|
-
| **SIMPLE CODE** | "fix", "add", "change" (single file) | TIER 0 + TIER 1 (lite) | Inline Edit |
|
|
40
|
-
| **COMPLEX CODE** | "build", "create", "implement", "refactor" | TIER 0 + TIER 1 (full) + Agent | **{task-slug}.md Required** |
|
|
41
|
-
| **DESIGN/UI** | "design", "UI", "page", "dashboard" | TIER 0 + TIER 1 + Agent | **{task-slug}.md Required** |
|
|
42
|
-
| **SLASH CMD** | /spec-init, /spec-requirements, /spec-design, /spec-tasks, /spec-status, /code, /test, /review, /docs-init, /docs-update | Command-specific flow | Variable |
|
|
43
|
-
|
|
44
|
-
---
|
|
45
|
-
|
|
46
|
-
## TIER 0: UNIVERSAL RULES (Always Active)
|
|
47
|
-
|
|
48
|
-
### 💬 Response Format
|
|
49
|
-
|
|
50
|
-
All responses must generally follow this structure:
|
|
51
|
-
1. **Explanation**: Brief context/problem/solution.
|
|
52
|
-
2. **Implementation**: Code/Action with clear comments.
|
|
53
|
-
3. **Usage/Caveats**: (If applicable)
|
|
54
|
-
4. **Next Action:** (MANDATORY) Specific proposed next step.
|
|
55
|
-
- Example: "Use `/spec-init` to start", "Review `docs/PLAN-x.md`", "Run `pytest`".
|
|
56
|
-
|
|
57
|
-
### 🌐 Language Handling
|
|
58
|
-
|
|
59
|
-
When user's prompt is NOT in English:
|
|
60
|
-
|
|
61
|
-
1. **Internally translate** for better comprehension
|
|
62
|
-
2. **Respond in user's language** - match their communication
|
|
63
|
-
3. **Code comments/variables** remain in English
|
|
64
|
-
|
|
65
|
-
### 🧹 Clean Code (Global Mandatory)
|
|
66
|
-
|
|
67
|
-
- **Code**: Concise, direct, no over-engineering. Self-documenting.
|
|
68
|
-
- **Testing**: Mandatory. Pyramid (Unit > Int > E2E) + AAA Pattern.
|
|
69
|
-
- **Performance**: Measure first. Adhere to 2025 standards (Core Web Vitals).
|
|
70
|
-
- **Infra/Safety**: 5-Phase Deployment. Verify secrets security.
|
|
71
|
-
- Concise, direct, solution-focused
|
|
72
|
-
- No verbose explanations
|
|
73
|
-
- No over-commenting
|
|
74
|
-
- No over-engineering
|
|
75
|
-
- **Artifact Control:** DO NOT create "Implementation Plan" artifacts (sidebar artifacts) unless the user EXPLICITLY asks for them. Use standard markdown files in `docs/` or text responses instead.
|
|
76
|
-
- **Self-Documentation:** Every agent is responsible for documenting their own changes in relevant `.md` files.
|
|
77
|
-
|
|
78
|
-
### 🗺️ System Map Read
|
|
79
|
-
|
|
80
|
-
**Path Awareness:**
|
|
81
|
-
|
|
82
|
-
- Skills: `.agent/skills/` (Project)
|
|
83
|
-
- Workflows: `.agent/workflows/` (Project)
|
|
84
|
-
- Models: `.agent/models/` (Project)
|
|
85
|
-
- Specs: `.specs/` (Project)
|
|
86
|
-
|
|
87
|
-
### 🧠 Read → Understand → Apply
|
|
88
|
-
|
|
89
|
-
```
|
|
90
|
-
❌ WRONG: Read agent file → Start coding
|
|
91
|
-
✅ CORRECT: Read → Understand WHY → Apply PRINCIPLES → Code
|
|
92
|
-
```
|
|
93
|
-
|
|
94
|
-
**Before coding, answer:**
|
|
95
|
-
|
|
96
|
-
1. What is the GOAL of this agent/skill?
|
|
97
|
-
2. What PRINCIPLES must I apply?
|
|
98
|
-
3. How does this DIFFER from generic output?
|
|
99
|
-
|
|
100
|
-
---
|
|
101
|
-
|
|
102
|
-
## TIER 1: CODE RULES (When Writing Code)
|
|
103
|
-
|
|
104
|
-
### 🛑 GLOBAL SOCRATIC GATE (TIER 0)
|
|
105
|
-
|
|
106
|
-
**MANDATORY: Every user request must pass through the Socratic Gate before ANY tool use or implementation.**
|
|
107
|
-
|
|
108
|
-
| Request Type | Strategy | Required Action |
|
|
109
|
-
| ----------------------- | -------------- | ----------------------------------------------------------------- |
|
|
110
|
-
| **New Feature / Build** | Deep Discovery | ASK minimum 3 strategic questions |
|
|
111
|
-
| **Code Edit / Bug Fix** | Context Check | Confirm understanding + ask impact questions |
|
|
112
|
-
| **Vague / Simple** | Clarification | Ask Purpose, Users, and Scope |
|
|
113
|
-
| **Full Orchestration** | Gatekeeper | **STOP** subagents until user confirms plan details |
|
|
114
|
-
| **Direct "Proceed"** | Validation | **STOP** → Even if answers are given, ask 2 "Edge Case" questions |
|
|
115
|
-
|
|
116
|
-
**Protocol:**
|
|
117
|
-
|
|
118
|
-
1. **Never Assume:** If even 1% is unclear, ASK.
|
|
119
|
-
2. **Handle Spec-heavy Requests:** When user gives a list (Answers 1, 2, 3...), do NOT skip the gate. Instead, ask about **Trade-offs** or **Edge Cases** (e.g., "LocalStorage confirmed, but should we handle data clearing or versioning?") before starting.
|
|
120
|
-
3. **Wait:** Do NOT invoke subagents or write code until the user clears the Gate.
|
|
121
|
-
|
|
122
|
-
---
|
|
123
|
-
|
|
124
|
-
## 📁 AVAILABLE WORKFLOWS
|
|
125
|
-
|
|
126
|
-
### Spec-Driven Development
|
|
127
|
-
|
|
128
|
-
| Command | Mô tả |
|
|
129
|
-
|---------|-------|
|
|
130
|
-
| `/spec-init <mô tả>` | Khởi tạo spec mới từ ý tưởng |
|
|
131
|
-
| `/spec-requirements <feature>` | Sinh requirements (EARS format) |
|
|
132
|
-
| `/spec-design <feature>` | Sinh design doc + research |
|
|
133
|
-
| `/spec-tasks <feature>` | Sinh task list |
|
|
134
|
-
| `/code <feature>` | Implement task từ spec và chuyển qua test/review |
|
|
135
|
-
| `/spec-status <feature>` | Xem trạng thái hiện tại |
|
|
136
|
-
|
|
137
|
-
### Documentation
|
|
138
|
-
|
|
139
|
-
| Command | Mô tả |
|
|
140
|
-
|---------|-------|
|
|
141
|
-
| `/docs-init` | Initialize project documentation |
|
|
142
|
-
| `/docs-update` | Update existing documentation |
|
|
143
|
-
|
|
144
|
-
**Usage:**
|
|
145
|
-
- Read `.agent/skills/specs/SKILL.md` for detailed workflow information
|
|
146
|
-
- Each workflow has its own file in `.agent/workflows/` with specific instructions
|
|
147
|
-
|
|
148
|
-
---
|
|
149
|
-
|
|
150
|
-
## 📁 QUICK REFERENCE
|
|
151
|
-
|
|
152
|
-
### Skills
|
|
153
|
-
|
|
154
|
-
- **specs**: Complete SDD workflow from idea to implementation
|
|
155
|
-
|
|
156
|
-
### Workflows Location
|
|
157
|
-
|
|
158
|
-
- Workflows: `.agent/workflows/`
|
|
159
|
-
- Skill definitions: `.agent/skills/`
|
|
160
|
-
- Spec data: `.specs/<feature-name>/`
|
|
161
|
-
|
|
162
|
-
---
|
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
description: Implement approved work from specification tasks and then hand off to test and review.
|
|
3
|
-
allowed-tools: Read, Glob, Grep, Edit, Write, Bash
|
|
4
|
-
argument-hint: <feature-name>
|
|
5
|
-
---
|
|
6
|
-
|
|
7
|
-
# /code - Implement from spec tasks
|
|
8
|
-
|
|
9
|
-
Use this workflow after `/spec-tasks`.
|
|
10
|
-
|
|
11
|
-
1. Read `.specs/$ARGUMENTS/tasks.md` and identify the next pending task.
|
|
12
|
-
2. Implement only that task following project standards.
|
|
13
|
-
3. Run `/test`.
|
|
14
|
-
4. Run `/review`.
|
|
15
|
-
|
|
16
|
-
Preferred flow: `/spec-init` - `/spec-requirements` - `/spec-design` - `/spec-tasks` - `/code` - `/test` - `/review`
|