@haposoft/cafekit 0.11.10 → 0.12.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/bin/install.js +4 -0
- package/bin/lib/context.js +4 -1
- package/bin/lib/i18n.js +33 -0
- package/bin/lib/rtk-run.js +28 -0
- package/bin/phases/setup-rtk.js +159 -0
- package/bin/phases/summary.js +4 -0
- package/package.json +1 -1
- package/src/claude/CLAUDE.md +1 -1
- package/src/claude/skills/develop/SKILL.md +1 -1
- package/src/claude/skills/specs/SKILL.md +115 -44
- package/src/claude/skills/specs/references/translation-mirror.md +83 -0
- package/src/claude/skills/specs/rules/ears-format.md +2 -2
- package/src/claude/skills/specs/templates/spec-state.json +9 -1
package/bin/install.js
CHANGED
|
@@ -37,6 +37,7 @@ const { checkVersions } = require('./lib/version-check');
|
|
|
37
37
|
const { ensureGitignore } = require('./phases/root-config');
|
|
38
38
|
const { runPostInstall } = require('./phases/post-install');
|
|
39
39
|
const { setupSkillDeps } = require('./phases/skills-setup');
|
|
40
|
+
const { setupRtk } = require('./phases/setup-rtk');
|
|
40
41
|
const { printSummary } = require('./phases/summary');
|
|
41
42
|
|
|
42
43
|
/** Install a single platform: payload + runtime + metadata, under one spinner. */
|
|
@@ -91,6 +92,8 @@ Options:
|
|
|
91
92
|
-u, --upgrade, -f, --force Alias of --force-overwrite
|
|
92
93
|
--with-skills-deps Install skill dependencies (Python venv + pip, npm,
|
|
93
94
|
Chromium/Playwright). Otherwise prompted interactively.
|
|
95
|
+
--with-rtk Install the rtk token-saver (binary + Claude Code hook).
|
|
96
|
+
Otherwise prompted interactively.
|
|
94
97
|
-y, --yes Non-interactive: skip prompts, use defaults (CI)
|
|
95
98
|
-h, --help Show this help
|
|
96
99
|
-v, --version Print version
|
|
@@ -166,6 +169,7 @@ async function main() {
|
|
|
166
169
|
ensureGitignore(ctx);
|
|
167
170
|
await runPostInstall(ctx);
|
|
168
171
|
await setupSkillDeps(ctx);
|
|
172
|
+
await setupRtk(ctx);
|
|
169
173
|
printSummary(ctx);
|
|
170
174
|
|
|
171
175
|
if (!ctx.dryRun) backup.prune(3);
|
package/bin/lib/context.js
CHANGED
|
@@ -185,7 +185,8 @@ function parseInstallerArgs(argv) {
|
|
|
185
185
|
forceOverwrite: false,
|
|
186
186
|
dryRun: false,
|
|
187
187
|
yes: false,
|
|
188
|
-
withSkillsDeps: false
|
|
188
|
+
withSkillsDeps: false,
|
|
189
|
+
withRtk: false
|
|
189
190
|
};
|
|
190
191
|
|
|
191
192
|
for (let i = 2; i < argv.length; i++) {
|
|
@@ -199,6 +200,8 @@ function parseInstallerArgs(argv) {
|
|
|
199
200
|
args.yes = true;
|
|
200
201
|
} else if (arg === '--with-skills-deps') {
|
|
201
202
|
args.withSkillsDeps = true;
|
|
203
|
+
} else if (arg === '--with-rtk') {
|
|
204
|
+
args.withRtk = true;
|
|
202
205
|
} else if (arg === '--lang') {
|
|
203
206
|
args.lang = argv[++i];
|
|
204
207
|
} else if (arg.startsWith('--lang=')) {
|
package/bin/lib/i18n.js
CHANGED
|
@@ -59,6 +59,17 @@ const MESSAGES = {
|
|
|
59
59
|
// Skills / deps setup
|
|
60
60
|
skillDepsConfirm: 'Install skill dependencies? (Python venv, pip, npm, Chromium — takes a few minutes)',
|
|
61
61
|
skillsSkipped: 'Skill dependencies skipped.\n Run later: npx @haposoft/cafekit --with-skills-deps',
|
|
62
|
+
|
|
63
|
+
// rtk token-saver setup
|
|
64
|
+
rtkConfirm: 'Install the rtk token-saver? (compacts git/grep/ls/build/test output for Claude Code Bash commands)',
|
|
65
|
+
rtkInstalling: 'Installing rtk via {method}',
|
|
66
|
+
rtkInstalled: 'rtk installed',
|
|
67
|
+
rtkInstallFailed: 'Could not install rtk — skipping (non-fatal). Install manually later: https://github.com/rtk-ai/rtk',
|
|
68
|
+
rtkInitFailed: 'rtk init -g failed: {reason} — hook not registered (non-fatal)',
|
|
69
|
+
rtkNeedsJq: 'rtk hook needs jq to run. Install jq, then the hook activates automatically.',
|
|
70
|
+
rtkFailed: 'rtk setup error: {reason} — skipped (non-fatal)',
|
|
71
|
+
rtkSkipped: 'rtk token-saver skipped.\n Run later: npx @haposoft/cafekit --with-rtk',
|
|
72
|
+
rtkSummary: 'rtk token-saver: hook registered for Claude Code Bash commands',
|
|
62
73
|
venvCreating: 'Setting up Python environment',
|
|
63
74
|
venvReady: 'Python environment ready',
|
|
64
75
|
venvFailed: 'Could not create Python environment — check your Python 3 installation.',
|
|
@@ -165,6 +176,17 @@ const MESSAGES = {
|
|
|
165
176
|
|
|
166
177
|
skillDepsConfirm: 'スキルの依存関係をインストールしますか?(Python venv、pip、npm、Chromium — 数分かかります)',
|
|
167
178
|
skillsSkipped: 'スキルの依存関係をスキップしました。\n 後で実行: npx @haposoft/cafekit --with-skills-deps',
|
|
179
|
+
|
|
180
|
+
// rtk token-saver setup
|
|
181
|
+
rtkConfirm: 'rtk トークンセーバーをインストールしますか?(Claude Code の Bash コマンドの git/grep/ls/build/test 出力を圧縮します)',
|
|
182
|
+
rtkInstalling: '{method} で rtk をインストール中',
|
|
183
|
+
rtkInstalled: 'rtk をインストールしました',
|
|
184
|
+
rtkInstallFailed: 'rtk をインストールできませんでした — スキップします(致命的ではありません)。後で手動でインストール: https://github.com/rtk-ai/rtk',
|
|
185
|
+
rtkInitFailed: 'rtk init -g が失敗しました: {reason} — フックは未登録です(致命的ではありません)',
|
|
186
|
+
rtkNeedsJq: 'rtk フックの実行には jq が必要です。jq をインストールするとフックが自動的に有効になります。',
|
|
187
|
+
rtkFailed: 'rtk セットアップエラー: {reason} — スキップしました(致命的ではありません)',
|
|
188
|
+
rtkSkipped: 'rtk トークンセーバーをスキップしました。\n 後で実行: npx @haposoft/cafekit --with-rtk',
|
|
189
|
+
rtkSummary: 'rtk トークンセーバー: Claude Code の Bash コマンド用にフックを登録しました',
|
|
168
190
|
venvCreating: 'Python 環境を準備しています',
|
|
169
191
|
venvReady: 'Python 環境の準備ができました',
|
|
170
192
|
venvFailed: 'Python 環境を作成できませんでした — Python 3 のインストールを確認してください。',
|
|
@@ -268,6 +290,17 @@ const MESSAGES = {
|
|
|
268
290
|
|
|
269
291
|
skillDepsConfirm: 'Cài dependencies cho skill ngay? (Python venv, pip, npm, Chromium — mất vài phút)',
|
|
270
292
|
skillsSkipped: 'Đã bỏ qua dependencies skill.\n Cài sau bằng lệnh: npx @haposoft/cafekit --with-skills-deps',
|
|
293
|
+
|
|
294
|
+
// rtk token-saver setup
|
|
295
|
+
rtkConfirm: 'Cài rtk token-saver? (rút gọn output git/grep/ls/build/test cho các lệnh Bash của Claude Code)',
|
|
296
|
+
rtkInstalling: 'Đang cài rtk qua {method}',
|
|
297
|
+
rtkInstalled: 'Đã cài rtk',
|
|
298
|
+
rtkInstallFailed: 'Không thể cài rtk — bỏ qua (không nghiêm trọng). Cài thủ công sau: https://github.com/rtk-ai/rtk',
|
|
299
|
+
rtkInitFailed: 'rtk init -g thất bại: {reason} — hook chưa được đăng ký (không nghiêm trọng)',
|
|
300
|
+
rtkNeedsJq: 'Hook của rtk cần jq để chạy. Cài jq thì hook sẽ tự kích hoạt.',
|
|
301
|
+
rtkFailed: 'Lỗi cài rtk: {reason} — đã bỏ qua (không nghiêm trọng)',
|
|
302
|
+
rtkSkipped: 'Đã bỏ qua rtk token-saver.\n Cài sau bằng lệnh: npx @haposoft/cafekit --with-rtk',
|
|
303
|
+
rtkSummary: 'rtk token-saver: đã đăng ký hook cho các lệnh Bash của Claude Code',
|
|
271
304
|
venvCreating: 'Đang thiết lập môi trường Python',
|
|
272
305
|
venvReady: 'Môi trường Python đã sẵn sàng',
|
|
273
306
|
venvFailed: 'Không thể tạo môi trường Python — hãy kiểm tra cài đặt Python 3.',
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Local captured-spawnSync helper for the rtk install phase.
|
|
3
|
+
*
|
|
4
|
+
* Why local: `lib/skill-deps.js` does not export a `run(cmd, args)` helper
|
|
5
|
+
* (it uses `runAsync` internally and never re-exports it). The rtk phase
|
|
6
|
+
* needs a synchronous, captured spawnSync (R2.2, R4.2) that returns
|
|
7
|
+
* `{ ok, status, stdout, stderr }` — same shape as `skill-deps.runAsync` but
|
|
8
|
+
* blocking. KISS: one 20-line module, no dependency on `lib/skill-deps.js`
|
|
9
|
+
* (which is skill-domain and not a generic command runner).
|
|
10
|
+
*
|
|
11
|
+
* @param {string} cmd
|
|
12
|
+
* @param {string[]} args
|
|
13
|
+
* @param {object} [opts] — passed through to spawnSync (e.g. { timeout })
|
|
14
|
+
* @returns {{ ok: boolean, status: number|null, stdout: string, stderr: string }}
|
|
15
|
+
*/
|
|
16
|
+
const { spawnSync } = require('child_process');
|
|
17
|
+
|
|
18
|
+
function run(cmd, args, opts = {}) {
|
|
19
|
+
const r = spawnSync(cmd, args, { encoding: 'utf8', ...opts });
|
|
20
|
+
return {
|
|
21
|
+
ok: !r.error && r.status === 0,
|
|
22
|
+
status: r.status,
|
|
23
|
+
stdout: r.stdout || '',
|
|
24
|
+
stderr: r.stderr || ''
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
module.exports = { run };
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* setup-rtk — opt-in install of the rtk token-saver and its Claude Code hook.
|
|
3
|
+
*
|
|
4
|
+
* Phase order (per specs/rtk-installer-integration/design.md):
|
|
5
|
+
* shouldRunRtk → ensureRtkBinary → `rtk init -g` → jq precheck → manifest
|
|
6
|
+
*
|
|
7
|
+
* R1-01 wired the consent gate (`shouldRunRtk`) and a thin `setupRtk` wrapper.
|
|
8
|
+
* R2-01 adds the orchestrator: `ensureRtkBinary` (detect/install), the
|
|
9
|
+
* `rtk init -g` hook registration, the `jq` precheck, and the manifest write.
|
|
10
|
+
*
|
|
11
|
+
* Non-fatal invariant (R2.7, R4.1): the entire `setupRtk` body is wrapped in
|
|
12
|
+
* try/catch. Any throw / non-zero exit / timeout is logged as a warning via
|
|
13
|
+
* `ctx.t('rtkFailed')` (with `{reason}` interpolation) and the cafekit
|
|
14
|
+
* install continues with its existing exit code. Mirrors the non-fatal
|
|
15
|
+
* contract of `phases/skills-setup.js`.
|
|
16
|
+
*
|
|
17
|
+
* Idempotency (R2.1, R2.6): re-runs see `hasCmd('rtk')` short-circuit the
|
|
18
|
+
* binary install path and re-invoke `rtk init -g` (rtk's own command handles
|
|
19
|
+
* its hook idempotency). The in-memory `ctx.rtkSetupRan` flag records that
|
|
20
|
+
* setup executed, so downstream consumers (R3-01 summary line) can read it.
|
|
21
|
+
*
|
|
22
|
+
* Cross-OS install (validated 2026-06-15): best-effort order
|
|
23
|
+
* (1) official prebuilt install script (pinned source)
|
|
24
|
+
* (2) `cargo install rtk` when `hasCmd('cargo')`
|
|
25
|
+
* (3) skip + warn
|
|
26
|
+
* covers macOS/Linux dev machines (cargo fall-through) and CI (skip+warn).
|
|
27
|
+
*
|
|
28
|
+
* No timeout for `cargo install rtk` (validated 2026-06-15): user accepted
|
|
29
|
+
* default `spawnSync` timeout; non-fatal guarantee holds via try/catch.
|
|
30
|
+
*/
|
|
31
|
+
|
|
32
|
+
const dep = require('../lib/skill-deps');
|
|
33
|
+
const rtkRun = require('../lib/rtk-run');
|
|
34
|
+
const { hasCmd } = dep;
|
|
35
|
+
const { run } = rtkRun;
|
|
36
|
+
|
|
37
|
+
const RTK_INIT_TIMEOUT_MS = 30_000; // `rtk init -g` is a fast global-hook write
|
|
38
|
+
const RTK_PREBUILT_TIMEOUT_MS = 120_000; // prebuilt install: curl download + sh pipe; slow networks need headroom
|
|
39
|
+
const RTK_PREBUILT_INSTALL_URL =
|
|
40
|
+
'https://raw.githubusercontent.com/rtk-ai/rtk/main/install.sh';
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Detect rtk binary, installing it if absent. Pure function over `ctx`.
|
|
44
|
+
*
|
|
45
|
+
* @param {object} ctx
|
|
46
|
+
* @returns {'present'|'installed'|'absent'}
|
|
47
|
+
*/
|
|
48
|
+
function ensureRtkBinary(ctx) {
|
|
49
|
+
if (hasCmd('rtk')) return 'present';
|
|
50
|
+
return installRtkBinary(ctx) ? 'installed' : 'absent';
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Best-effort rtk install. Tries the official prebuilt install script first
|
|
55
|
+
* (R5.2: surface source), then falls back to `cargo install rtk` if cargo is
|
|
56
|
+
* available, otherwise returns false (R2.3 non-fatal skip).
|
|
57
|
+
*
|
|
58
|
+
* @param {object} ctx
|
|
59
|
+
* @returns {boolean} true on success, false on failure or no method available
|
|
60
|
+
*/
|
|
61
|
+
function installRtkBinary(ctx) {
|
|
62
|
+
// Method 1: official prebuilt install script (pinned URL).
|
|
63
|
+
ctx.ui.info(ctx.t('rtkInstalling', { method: `prebuilt script (${RTK_PREBUILT_INSTALL_URL})` }));
|
|
64
|
+
const prebuilt = run('sh', ['-c', `curl -fsSL ${RTK_PREBUILT_INSTALL_URL} | sh`], {
|
|
65
|
+
timeout: RTK_PREBUILT_TIMEOUT_MS
|
|
66
|
+
});
|
|
67
|
+
if (prebuilt.ok) return true;
|
|
68
|
+
|
|
69
|
+
// Method 2: `cargo install rtk` if cargo is on PATH.
|
|
70
|
+
if (hasCmd('cargo')) {
|
|
71
|
+
ctx.ui.info(ctx.t('rtkInstalling', { method: 'cargo install rtk' }));
|
|
72
|
+
const cargo = run('cargo', ['install', 'rtk']);
|
|
73
|
+
if (cargo.ok) return true;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// No method succeeded.
|
|
77
|
+
return false;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Phase entrypoint. R2-01 wires the full install + hook + manifest pipeline.
|
|
82
|
+
* The consent gate is delegated to `shouldRunRtk(ctx)` (R1-01).
|
|
83
|
+
*
|
|
84
|
+
* @param {object} ctx
|
|
85
|
+
* @returns {Promise<object>} the same ctx
|
|
86
|
+
*/
|
|
87
|
+
async function setupRtk(ctx) {
|
|
88
|
+
if (!(await shouldRunRtk(ctx))) {
|
|
89
|
+
// Dry-run: enumerate the action set that would have run (validated 2026-06-15).
|
|
90
|
+
if (ctx.dryRun) {
|
|
91
|
+
ctx.ui.info(
|
|
92
|
+
'[dry-run] rtk setup skipped (would: detect rtk binary, install if missing via prebuilt/cargo, run rtk init -g, check jq)'
|
|
93
|
+
);
|
|
94
|
+
} else {
|
|
95
|
+
ctx.ui.info(ctx.t('rtkSkipped'));
|
|
96
|
+
}
|
|
97
|
+
return ctx;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
try {
|
|
101
|
+
// Step 1: ensure the binary is on PATH (R2.1, R2.2, R2.3).
|
|
102
|
+
const state = ensureRtkBinary(ctx);
|
|
103
|
+
if (state === 'absent') {
|
|
104
|
+
ctx.ui.warn(ctx.t('rtkInstallFailed'));
|
|
105
|
+
return ctx;
|
|
106
|
+
}
|
|
107
|
+
if (state === 'installed') {
|
|
108
|
+
ctx.ui.info(ctx.t('rtkInstalled'));
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// Step 2: register rtk's official Claude Code hook via `rtk init -g` (R2.4).
|
|
112
|
+
const init = run('rtk', ['init', '-g'], { timeout: RTK_INIT_TIMEOUT_MS });
|
|
113
|
+
if (!init.ok) {
|
|
114
|
+
ctx.ui.warn(ctx.t('rtkInitFailed', { reason: init.stderr || `exit ${init.status}` }));
|
|
115
|
+
return ctx;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// Step 3: `jq` precheck (R2.5). Warn if missing; do not fail.
|
|
119
|
+
if (!hasCmd('jq')) {
|
|
120
|
+
ctx.ui.warn(ctx.t('rtkNeedsJq'));
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// Step 4: record the action (R2.6, idempotency). In-memory flag — re-runs
|
|
124
|
+
// observe `hasCmd('rtk')` short-circuit and re-invoke `rtk init -g`,
|
|
125
|
+
// which is itself idempotent. R3-01 reads this flag for the summary line.
|
|
126
|
+
ctx.rtkSetupRan = true;
|
|
127
|
+
return ctx;
|
|
128
|
+
} catch (err) {
|
|
129
|
+
// Non-fatal boundary: any unexpected throw becomes a warning, the
|
|
130
|
+
// installer continues (R2.7, R4.1).
|
|
131
|
+
ctx.ui.warn(ctx.t('rtkFailed', { reason: (err && err.message) || String(err) }));
|
|
132
|
+
return ctx;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Decision matrix for whether the rtk setup phase should run. R1-01.
|
|
138
|
+
*
|
|
139
|
+
* 1. --dry-run → false (R1.4)
|
|
140
|
+
* 2. ctx.options.withRtk === true → true (R1.1)
|
|
141
|
+
* 3. !ctx.interactive → false (R1.3)
|
|
142
|
+
* 4. interactive confirm resolves true → true (R1.2)
|
|
143
|
+
* 5. interactive confirm resolves false → false (R1.2)
|
|
144
|
+
*
|
|
145
|
+
* @param {object} ctx
|
|
146
|
+
* @returns {Promise<boolean>}
|
|
147
|
+
*/
|
|
148
|
+
async function shouldRunRtk(ctx) {
|
|
149
|
+
if (ctx.dryRun) return false;
|
|
150
|
+
if (ctx.options.withRtk) return true;
|
|
151
|
+
if (!ctx.interactive) return false;
|
|
152
|
+
const yes = await ctx.ui.confirm(
|
|
153
|
+
{ message: ctx.t('rtkConfirm'), initialValue: false },
|
|
154
|
+
false
|
|
155
|
+
);
|
|
156
|
+
return yes === true;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
module.exports = { shouldRunRtk, setupRtk, ensureRtkBinary, installRtkBinary };
|
package/bin/phases/summary.js
CHANGED
|
@@ -55,6 +55,10 @@ function printSummary(ctx) {
|
|
|
55
55
|
for (const key of platforms) {
|
|
56
56
|
lines.push(key === 'claude' ? t('nsClaude') : t('nsOpencode'));
|
|
57
57
|
}
|
|
58
|
+
// rtk token-saver result (set by phases/setup-rtk.js when setup completed).
|
|
59
|
+
if (ctx.rtkSetupRan) {
|
|
60
|
+
lines.push(t('rtkSummary'));
|
|
61
|
+
}
|
|
58
62
|
const keySkills = skillsNeedingKeys(platforms);
|
|
59
63
|
if (keySkills.length > 0) {
|
|
60
64
|
lines.push(t('nsKeys', { skills: keySkills.join(', ') }));
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@haposoft/cafekit",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.12.0",
|
|
4
4
|
"description": "Claude Code-first spec-driven workflow for AI coding assistants. Bundles CafeKit hapo: skills, runtime hooks, agents, and installer scaffolding.",
|
|
5
5
|
"author": "Haposoft <nghialt@haposoft.com>",
|
|
6
6
|
"license": "MIT",
|
package/src/claude/CLAUDE.md
CHANGED
|
@@ -116,7 +116,7 @@ Consult these when the task touches the relevant area:
|
|
|
116
116
|
|
|
117
117
|
## Language Consistency
|
|
118
118
|
|
|
119
|
-
|
|
119
|
+
Spec artifacts (`spec.json`, `requirements.md`, `research.md`, `design.md`, `tasks/*.md`) are **canonical in English** regardless of the session's response language. When the configured language is not English, `hapo:specs` may additionally generate a reference-only translation mirror under `specs/<feature>/i18n/<lang>/`. Chat replies and other non-spec output still follow the user's preferred language. Technical terms, code samples, and file paths stay English.
|
|
120
120
|
|
|
121
121
|
## Communication
|
|
122
122
|
|
|
@@ -71,7 +71,7 @@ Enabled by default for all develop modes. Disable only with `--no-notes`.
|
|
|
71
71
|
|
|
72
72
|
<HARD-GATE>
|
|
73
73
|
DO NOT write implementation code until an approved spec exists.
|
|
74
|
-
- If the directory `specs/<feature-name>` DOES NOT EXIST or `spec.json` is not ready, automatically trigger `/hapo:specs <feature-name
|
|
74
|
+
- If the directory `specs/<feature-name>` DOES NOT EXIST or `spec.json` is not ready, automatically trigger `/hapo:specs <feature-name> --auto` first to create the specification end-to-end (non-interactive). Do not improvise.
|
|
75
75
|
</HARD-GATE>
|
|
76
76
|
|
|
77
77
|
<DEFINITION-OF-DONE>
|
|
@@ -5,7 +5,7 @@ user-invocable: true
|
|
|
5
5
|
when_to_use: "Invoke to turn a vague idea into an actionable, gated specification."
|
|
6
6
|
category: utilities
|
|
7
7
|
keywords: [specs, requirements, design, tasks]
|
|
8
|
-
argument-hint: "<feature-description> | status |
|
|
8
|
+
argument-hint: "[<feature-description>] [--auto] | --status | --validate <feature> | --archive"
|
|
9
9
|
metadata:
|
|
10
10
|
author: haposoft
|
|
11
11
|
version: "2.0.0"
|
|
@@ -22,6 +22,8 @@ This skill provides a 10-step workflow to transform ideas into evidence-backed s
|
|
|
22
22
|
Analyze → Dependency Scan → Complexity Assessment → Init → Evidence Gate + Requirements → Design → Tasks → Hydration → Review → Completion
|
|
23
23
|
```
|
|
24
24
|
|
|
25
|
+
An entry/dispatch layer sits in front of the pipeline: four flags (`--auto`, `--validate`, `--status`, `--archive`) plus **Interactive State Discovery** (the no-flag path) decide *which part of the pipeline to run and where to stop* — see **Default Behavior**. The pipeline itself is unchanged regardless of how it is invoked.
|
|
26
|
+
|
|
25
27
|
**CRITICAL:** Before starting, the system MUST:
|
|
26
28
|
1. Scan `specs/` directory for incomplete specs
|
|
27
29
|
2. If any spec is `in_progress` (accept legacy `in-progress` when reading) → ask user whether to continue or create new
|
|
@@ -39,7 +41,7 @@ Analyze → Dependency Scan → Complexity Assessment → Init → Evidence Gate
|
|
|
39
41
|
- Each phase (Init → Requirements → Design → Tasks) must complete before the next begins
|
|
40
42
|
- No skipping — don't write design without requirements
|
|
41
43
|
- Exception: simple tasks may merge requirements + design into one step
|
|
42
|
-
- A
|
|
44
|
+
- A `/hapo:specs <feature-description>` run defaults to **Interactive State Discovery**: it asks the Creation Mode (Auto / Stop after Design / Step by step) before running. `--auto` runs the full pipeline end-to-end without asking. Either way, phases never skip — each completes before the next begins, and Init is never a stop point.
|
|
43
45
|
|
|
44
46
|
### Scope Rules
|
|
45
47
|
- Respect `scope_lock` absolutely once user has confirmed
|
|
@@ -54,6 +56,10 @@ Analyze → Dependency Scan → Complexity Assessment → Init → Evidence Gate
|
|
|
54
56
|
- `ready_for_implementation` is a hard gate, not a convenience flag. Never set it before the finalization audit passes.
|
|
55
57
|
- Non-trivial specs MUST have an evidence trail in `research.md` before requirements, design, or tasks are finalized. Evidence can be codebase scout findings, external/current research, or an explicit skip rationale.
|
|
56
58
|
|
|
59
|
+
### Language & Canonical Rules
|
|
60
|
+
- **English is the canonical language for every spec artifact** (`spec.json`, `requirements.md`, `research.md`, `design.md`, `tasks/*.md`) — write them in English regardless of the session's response language.
|
|
61
|
+
- If the configured language (`.claude/settings.json` → `language`) is not English, you MAY additionally generate a **reference-only translation mirror** under `i18n/<lang>/`. See `references/translation-mirror.md`. The mirror never replaces canonical and is never a source of truth.
|
|
62
|
+
|
|
57
63
|
### Output Criteria
|
|
58
64
|
- Never implement code — only create spec documents
|
|
59
65
|
- Return file paths and a brief summary
|
|
@@ -96,37 +102,62 @@ Forbidden generated artifacts:
|
|
|
96
102
|
|
|
97
103
|
## Default Behavior
|
|
98
104
|
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
105
|
+
> **This section is the entry/dispatch layer ONLY.** It decides *which part of the pipeline to run and where to stop*. It does NOT change any pipeline step, the validator, templates, or rules. The detailed Steps 1–10 below are unchanged.
|
|
106
|
+
|
|
107
|
+
`hapo:specs` exposes exactly **four flags**: `--auto`, `--validate`, `--status`, `--archive`. Everything else (which spec, new vs continue, how far to run) is resolved by **Interactive State Discovery**, never by extra flags.
|
|
108
|
+
|
|
109
|
+
**Back-compat aliases** (accepted silently, not advertised): bare `status` / `archive` / `resume`, and `--validate` as today. Treat them as their `--flag` equivalents.
|
|
110
|
+
|
|
111
|
+
### Dispatch order
|
|
112
|
+
|
|
113
|
+
1. `--status` (or bare `status`) → run the status report (see **Subcommands**), then stop.
|
|
114
|
+
2. `--validate <feature>` → jump to **Step 9** (the `--validate` rules below are unchanged).
|
|
115
|
+
3. `--archive` (or bare `archive`) → run the archive workflow, then stop.
|
|
116
|
+
4. `--auto` → **non-interactive run**. If the argument matches an unfinished spec → resume it from `current_phase` and finish to Tasks. Otherwise create new and run the full pipeline (Step 1→10) end-to-end. Either way: auto-approve and skip the Creation Mode question; if a new description is missing, ask only for it; the hard safety gates still apply.
|
|
117
|
+
5. Otherwise (`/hapo:specs` or `/hapo:specs "<description>"`) → **Interactive State Discovery**.
|
|
118
|
+
|
|
119
|
+
### Interactive State Discovery (no-flag path)
|
|
120
|
+
|
|
121
|
+
Goal: ask the user enough to determine **state** and **intent**, then run the existing pipeline. This only chooses *what to run and where to stop* — it does not alter any step's logic.
|
|
122
|
+
|
|
123
|
+
1. **Detect state.** Scan `specs/` for unfinished specs (`status=in_progress` / `ready_for_implementation=false`) and git-branch match.
|
|
124
|
+
- Found → `AskUserQuestion`: `Continue <spec A> · Continue <spec B> · Create new spec`.
|
|
125
|
+
- None → default to create-new.
|
|
126
|
+
2. **Create-new + no description yet** → ask for the description, then analyze it (logic unchanged):
|
|
127
|
+
- If description is too short (< 20 words) or missing one concrete detail → stop and ask 1-2 clarifying questions
|
|
128
|
+
- If the idea has unresolved architecture choices, unclear acceptance criteria, unclear scope boundaries, or multiple plausible approaches → stop and route to `/hapo:brainstorm <idea>` before creating spec artifacts
|
|
129
|
+
- If task is simple (small bugfix, config change) → suggest "A spec may not be needed for this. Continue anyway?"
|
|
130
|
+
- If task is complex (multi-module, security/migration related) → auto-activate deep research, ask user 3 scope questions
|
|
131
|
+
- For non-trivial specs, execute the Step 5 Evidence Gate before writing final requirements. Do not design from memory when codebase or current external evidence can answer the question.
|
|
132
|
+
3. **Creation Mode Gate** (how far to run) — see the dedicated section below. Options: `Auto (→ Tasks)` · `Stop after Design` · `Step by step`. If `.claude/settings.json` → `language` is not English, also offer the **Translation Mirror** (see below).
|
|
133
|
+
4. **Continue an unfinished spec** → read `spec.json.current_phase`, then `AskUserQuestion` offering the **remaining** stop points (e.g. `→ Design`, `→ Tasks`, `Step by step`) and resume the existing pipeline from that phase. If a message accompanied the call, treat it as added context for the next phase (scope expansion → ask, per `references/ask-user-question-gates.md`).
|
|
134
|
+
5. **Run the chosen scope.** On an early stop, emit the Paused Block (Step 10). Sync `spec.json` per `state-sync.md`.
|
|
135
|
+
|
|
136
|
+
> The pipeline, validator, templates, and rules are unchanged. "Stop after Design" simply halts before Step 7; it runs Steps 1–6 exactly as specified.
|
|
137
|
+
|
|
138
|
+
### Creation Mode Gate
|
|
139
|
+
|
|
140
|
+
Shown once (via `AskUserQuestion`) on the no-flag create path, after the description passes the safety gates. It selects the **stop point only** — every phase that runs uses the unchanged Step logic.
|
|
141
|
+
|
|
142
|
+
| Option | Runs | Stops after | Approvals |
|
|
143
|
+
|---|---|---|---|
|
|
144
|
+
| **Auto (→ Tasks)** | Step 1→10 end-to-end | full (ready gate) | each phase `generated=true` + `approved=true` |
|
|
145
|
+
| **Stop after Design** | Step 1→6 | `design` (before Step 7) | requirements + design `approved=true`; `ready_for_implementation=false` |
|
|
146
|
+
| **Step by step** | one phase at a time | after each phase | per phase `generated=true`, `approved=false` until user approves |
|
|
119
147
|
|
|
120
|
-
|
|
148
|
+
Rules:
|
|
149
|
+
- `--auto` equals choosing **Auto (→ Tasks)** without showing this gate.
|
|
150
|
+
- **Stop after Design** / **Step by step** leave `ready_for_implementation=false` and emit the Paused Block (Step 10).
|
|
151
|
+
- Continuing later: run `/hapo:specs` again → Interactive State Discovery detects the unfinished spec and offers the remaining stop points. No flag or keyword required.
|
|
121
152
|
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
-
|
|
125
|
-
|
|
126
|
-
- If
|
|
127
|
-
-
|
|
128
|
-
-
|
|
129
|
-
-
|
|
153
|
+
### Translation Mirror (optional reference copy)
|
|
154
|
+
|
|
155
|
+
Load `references/translation-mirror.md`. This is the only language feature in the entry layer; canonical artifacts are always English.
|
|
156
|
+
|
|
157
|
+
- Read `.claude/settings.json` → `language`. If it is **not** English and the run is **interactive** (no `--auto`), ask once via `AskUserQuestion`: "Generate a `<language>` reference copy of this spec? (kept in sync, read-only)".
|
|
158
|
+
- On `Yes` → set `spec.json.translation` (`enabled`, `language`, `language_code`, `dir: i18n/<code>`, `sync: always`) and, after each canonical doc is written in Steps 5–7, regenerate the matching file under `i18n/<code>/` with the reference-only marker.
|
|
159
|
+
- `--auto` does not prompt for a mirror, so an `--auto` create makes none. But if the spec already has `translation.enabled = true` (set in an earlier interactive run), always-sync still applies: an `--auto` resume keeps regenerating the mirror after each canonical write.
|
|
160
|
+
- The mirror is reference-only: never validated, never a source of truth, ignored by `hapo:develop`.
|
|
130
161
|
|
|
131
162
|
### When called WITH `--validate` argument
|
|
132
163
|
|
|
@@ -148,9 +179,16 @@ The system MUST NOT execute Steps 1-8. Instead, load `references/review.md` and
|
|
|
148
179
|
|
|
149
180
|
```mermaid
|
|
150
181
|
flowchart TD
|
|
151
|
-
A["Call /hapo:specs"] --> B{
|
|
152
|
-
B -->|
|
|
153
|
-
B -->|
|
|
182
|
+
A["Call /hapo:specs"] --> B{Flag?}
|
|
183
|
+
B -->|"--status"| ST["Status report → stop"]
|
|
184
|
+
B -->|"--archive"| AR["Archive workflow → stop"]
|
|
185
|
+
B -->|"--validate"| W
|
|
186
|
+
B -->|"--auto"| D["Step 1: Analyze description"]
|
|
187
|
+
B -->|none| DISC["Interactive State Discovery"]
|
|
188
|
+
DISC --> DISC1{Unfinished spec?}
|
|
189
|
+
DISC1 -->|Continue| RES["Read current_phase → resume remaining phases"]
|
|
190
|
+
DISC1 -->|New| D
|
|
191
|
+
RES --> CM
|
|
154
192
|
D --> DB{"Needs pre-spec brainstorm?"}
|
|
155
193
|
DB -->|Yes| DB2["Stop: run /hapo:brainstorm with same idea"]
|
|
156
194
|
DB -->|No| E{Clear enough?}
|
|
@@ -174,7 +212,9 @@ flowchart TD
|
|
|
174
212
|
N -->|Yes| P["Step 5B: Requirements — write EARS"]
|
|
175
213
|
P --> Q["Step 6: Design — pick discovery mode"]
|
|
176
214
|
Q --> R["Write design.md"]
|
|
177
|
-
R -->
|
|
215
|
+
R --> CM{"Creation Mode?"}
|
|
216
|
+
CM -->|"Stop after Design"| STOP["Paused at design → emit Paused Block"]
|
|
217
|
+
CM -->|"Auto / Step by step"| S["Step 7: Tasks — split into individual files"]
|
|
178
218
|
S --> T["Create tasks/task-R*.md + task_registry"]
|
|
179
219
|
T --> U["Step 8: Hydrate Claude Tasks if >= 3 task files"]
|
|
180
220
|
U --> V{Review?}
|
|
@@ -233,7 +273,7 @@ Load: `references/scope-inquiry.md`
|
|
|
233
273
|
- `in_scope`: confirmed scope items
|
|
234
274
|
- `out_of_scope`: excluded items
|
|
235
275
|
- `expansion_policy`: `requires-user-approval`
|
|
236
|
-
- Step 4 itself only initializes files.
|
|
276
|
+
- Step 4 itself only initializes files. The Creation Mode Gate (or `--auto`) decides how far the run proceeds; in every mode Step 4 continues into Step 5 — Init is never a stop point.
|
|
237
277
|
|
|
238
278
|
### Step 5: Evidence Gate, Requirements & Research
|
|
239
279
|
- Read `spec.json` — stop if init hasn't completed
|
|
@@ -392,6 +432,7 @@ Load: `references/review.md` + `rules/design-review.md`
|
|
|
392
432
|
Tasks and requirements must reuse the same policy verbatim; mixed policies are invalid.
|
|
393
433
|
- FAIL if `validation.status = "completed"` but `timestamps.validation_done` / `timestamps.review_done`, `updated_at`, and report metadata are not synchronized with the final reviewed state.
|
|
394
434
|
- If `validation_recommended = true` and `validation.status` is not `completed` (or an explicit accepted-risk state recorded by the user), `ready_for_implementation` MUST remain `false`
|
|
435
|
+
- If `translation.enabled = true`, ensure `i18n/<language_code>/` exists and was re-synced after the final canonical write; the mirror itself is excluded from validation and never blocks `ready_for_implementation`.
|
|
395
436
|
- Only after this audit passes may the system mark `progress.tasks = "done"` and `ready_for_implementation = true`
|
|
396
437
|
|
|
397
438
|
### Step 10: Completion — Context Reminder (MANDATORY)
|
|
@@ -407,15 +448,27 @@ After completing the spec, output a short summary of what was generated, then yo
|
|
|
407
448
|
💡 Tip: Run /clear or start a new chat session before implementing to reduce planning context carryover.
|
|
408
449
|
```
|
|
409
450
|
|
|
451
|
+
### Step 10b: Paused Block (early stop)
|
|
452
|
+
|
|
453
|
+
When the run stops before Tasks (Creation Mode = **Stop after Design** or **Step by step**), do NOT print the completion block above and do NOT run the deterministic validator (no tasks exist yet). Instead print:
|
|
454
|
+
|
|
455
|
+
```
|
|
456
|
+
⏸ Spec paused at <phase>: specs/<feature>/
|
|
457
|
+
📌 Continue — run /hapo:specs and choose "Continue <feature>"
|
|
458
|
+
(or /hapo:specs <feature> --auto to finish straight to Tasks)
|
|
459
|
+
```
|
|
460
|
+
|
|
461
|
+
`ready_for_implementation` stays `false`. The next `/hapo:specs` run re-detects this spec via Interactive State Discovery and offers the remaining phases.
|
|
462
|
+
|
|
410
463
|
## Active Spec State
|
|
411
464
|
|
|
412
465
|
When user calls `hapo:specs`, system checks `specs/`:
|
|
413
466
|
|
|
414
467
|
| Situation | Action |
|
|
415
468
|
|---|---|
|
|
416
|
-
| A spec is `in_progress` |
|
|
417
|
-
| A spec matches current git branch |
|
|
418
|
-
| Nothing found | Create new spec
|
|
469
|
+
| A spec is `in_progress` | Interactive State Discovery offers `Continue <name>` (resume from `<phase>`) vs `Create new spec` |
|
|
470
|
+
| A spec matches current git branch | Offer `Continue <X>` vs `Create new` |
|
|
471
|
+
| Nothing found | Create new spec → Creation Mode Gate |
|
|
419
472
|
|
|
420
473
|
**Next step suggestions based on `spec.json`:**
|
|
421
474
|
|
|
@@ -437,8 +490,9 @@ When user calls `hapo:specs`, system checks `specs/`:
|
|
|
437
490
|
**Timestamps:** Each `timestamps.*_done` field MUST use the **actual current time** (ISO 8601 with timezone) when that specific phase completes. This includes `review_done` and `validation_done` after review/validate workflows. Do NOT reuse the `init` timestamp for later phases. If running the full pipeline end-to-end, capture a fresh timestamp at each phase transition.
|
|
438
491
|
|
|
439
492
|
**Approvals (auto-approval behavior):**
|
|
440
|
-
- When running the **full pipeline end-to-end** (init → tasks in one session): set `approvals.{phase}.generated = true` AND `approvals.{phase}.approved = true` for each completed phase before proceeding to the next.
|
|
441
|
-
- When running
|
|
493
|
+
- When running the **full pipeline end-to-end** (`--auto` or Creation Mode "Auto", init → tasks in one session): set `approvals.{phase}.generated = true` AND `approvals.{phase}.approved = true` for each completed phase before proceeding to the next.
|
|
494
|
+
- When running to an **early stop point** (Creation Mode "Stop after Design"): for each phase that ran to reach the stop point, set `generated = true` AND `approved = true`; do not generate later phases; keep `ready_for_implementation = false`.
|
|
495
|
+
- When running a **single phase** (Creation Mode "Step by step"): set `generated = true` but leave `approved = false` — user must explicitly approve before continuing.
|
|
442
496
|
|
|
443
497
|
**Task inventory:** `task_files` MUST be present and MUST list every real task file exactly once using relative paths like `tasks/task-R1-01-example.md`.
|
|
444
498
|
Task paths that omit the `task-` prefix or use non-padded sequence numbers (for example `tasks/R1-1-example.md`) are invalid for new CafeKit specs.
|
|
@@ -447,6 +501,8 @@ Task paths that omit the `task-` prefix or use non-padded sequence numbers (for
|
|
|
447
501
|
|
|
448
502
|
**Validation recommendation:** `design_context.validation_recommended` MUST be `true` for auth, privacy, delete-data, migration, schema-change, browser-extension-permission, external-provider, or 5+ task file specs.
|
|
449
503
|
|
|
504
|
+
**Translation mirror:** `translation` records the optional reference copy (`enabled`, `language`, `language_code`, `dir`, `sync: always`, `last_synced_at`). Canonical artifacts stay English; when `enabled = true`, regenerate `i18n/<language_code>/` after each canonical doc write and stamp `last_synced_at`. The mirror never affects `ready_for_implementation`. See `references/translation-mirror.md`.
|
|
505
|
+
|
|
450
506
|
**`ready_for_implementation`:** This field MUST only be set to `true` when ALL of the following conditions are met:
|
|
451
507
|
1. `approvals.requirements.approved = true`
|
|
452
508
|
2. `approvals.design.approved = true`
|
|
@@ -480,14 +536,28 @@ specs/
|
|
|
480
536
|
└── red-team-report.md
|
|
481
537
|
```
|
|
482
538
|
|
|
539
|
+
When `translation.enabled` is true, a reference-only mirror is added alongside (English stays canonical):
|
|
540
|
+
|
|
541
|
+
```
|
|
542
|
+
specs/<feature-name>/
|
|
543
|
+
└── i18n/<lang-code>/ # reference only — generated from canonical, never validated
|
|
544
|
+
├── requirements.md
|
|
545
|
+
├── design.md
|
|
546
|
+
├── research.md
|
|
547
|
+
└── tasks/task-R*.md
|
|
548
|
+
```
|
|
549
|
+
|
|
483
550
|
## Subcommands
|
|
484
551
|
|
|
552
|
+
The canonical surface is four flags. Bare-verb forms are kept only as silent back-compat aliases.
|
|
553
|
+
|
|
485
554
|
| Command | Purpose | Reference |
|
|
486
555
|
|---|---|---|
|
|
487
|
-
| `/hapo:specs status` | View status of all specs | — |
|
|
488
|
-
| `/hapo:specs resume <feature>` | Continue an active spec | — |
|
|
556
|
+
| `/hapo:specs --status` | View status of all specs (alias: `status`) | — |
|
|
489
557
|
| `/hapo:specs --validate <feature>` | Validate spec (auto: red team + validate based on complexity) | `references/review.md` |
|
|
490
|
-
| `/hapo:specs archive` | Archive completed specs + write journal | `references/archive-workflow.md` |
|
|
558
|
+
| `/hapo:specs --archive` | Archive completed specs + write journal (alias: `archive`) | `references/archive-workflow.md` |
|
|
559
|
+
| `/hapo:specs --auto [<desc>]` | Non-interactive: create (or resume) and run end-to-end to Tasks | — |
|
|
560
|
+
| `/hapo:specs [<desc>]` | Interactive State Discovery (continue unfinished, or create + Creation Mode Gate). Alias `resume` accepted. | — |
|
|
491
561
|
|
|
492
562
|
## Quality Standards
|
|
493
563
|
|
|
@@ -578,3 +648,4 @@ Before finalizing any specification, assert all the following:
|
|
|
578
648
|
- `task-hydration.md` — Task files → Claude Tasks conversion
|
|
579
649
|
- `review.md` — Spec review (auto-decides: red team 8 steps + validate 6 steps)
|
|
580
650
|
- `archive-workflow.md` — Archive workflow (5 steps)
|
|
651
|
+
- `translation-mirror.md` — Optional reference-only translation mirror (detection, layout, sync, fields)
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
# Translation Mirror
|
|
2
|
+
|
|
3
|
+
Optional, reference-only duplicate of a spec in the user's configured language. The English spec is always the canonical source of truth; the mirror exists only for human cross-reading.
|
|
4
|
+
|
|
5
|
+
## When To Offer
|
|
6
|
+
|
|
7
|
+
1. Read the configured language from `.claude/settings.json` → `language` (a freeform label written at install time, e.g. `Tiếng Việt`, `日本語`, `Korean`, `English`).
|
|
8
|
+
2. If the label resolves to English (`English` / `en`) → **do not** offer a mirror; there is nothing to duplicate.
|
|
9
|
+
3. If it is any other language AND the run is interactive (no `--auto`) → ask once via `AskUserQuestion`:
|
|
10
|
+
- "Generate a `<language>` reference copy of this spec? (kept in sync, read-only)" → `Yes` / `No`.
|
|
11
|
+
4. `--auto` runs are non-interactive → **skip** the offer (an `--auto` create makes no mirror). If `translation.enabled` is already `true` from an earlier run, always-sync still applies during an `--auto` resume.
|
|
12
|
+
5. Persist the choice in `spec.json.translation` (see Fields).
|
|
13
|
+
|
|
14
|
+
## Language Code
|
|
15
|
+
|
|
16
|
+
Map the freeform label to a short folder code:
|
|
17
|
+
|
|
18
|
+
| Label | Code |
|
|
19
|
+
|---|---|
|
|
20
|
+
| Tiếng Việt | `vi` |
|
|
21
|
+
| 日本語 / Japanese | `ja` |
|
|
22
|
+
| 한국어 / Korean | `ko` |
|
|
23
|
+
| 中文 / Chinese | `zh` |
|
|
24
|
+
| Français / French | `fr` |
|
|
25
|
+
| (anything else) | kebab-case slug of the label |
|
|
26
|
+
|
|
27
|
+
## Layout
|
|
28
|
+
|
|
29
|
+
The mirror lives under the spec, never replacing canonical files:
|
|
30
|
+
|
|
31
|
+
```
|
|
32
|
+
specs/<feature>/
|
|
33
|
+
├── spec.json # canonical state — NOT mirrored
|
|
34
|
+
├── requirements.md # English canonical
|
|
35
|
+
├── design.md
|
|
36
|
+
├── research.md
|
|
37
|
+
├── tasks/task-R*.md
|
|
38
|
+
└── i18n/<code>/ # mirror (reference only)
|
|
39
|
+
├── requirements.md
|
|
40
|
+
├── design.md
|
|
41
|
+
├── research.md
|
|
42
|
+
└── tasks/task-R*.md
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
- Mirror **only** the human-readable docs: `requirements.md`, `design.md`, `research.md`, and every `tasks/task-*.md`.
|
|
46
|
+
- Do **not** mirror `spec.json` (machine state is language-agnostic and lives only at the root).
|
|
47
|
+
- Keep file names and the `tasks/` substructure identical to canonical so they line up 1:1.
|
|
48
|
+
|
|
49
|
+
## Mirror File Marker
|
|
50
|
+
|
|
51
|
+
Every generated mirror file MUST begin with this HTML comment so no one mistakes it for a source:
|
|
52
|
+
|
|
53
|
+
```
|
|
54
|
+
<!-- TRANSLATION MIRROR — generated from English canonical. Reference only. Do not edit; regenerated on sync. -->
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
EARS trigger keywords and fixed phrases stay in English even inside the mirror (`When`, `If`, `While`, `Where`, `The system shall`); only the variable parts are translated — same rule as `rules/ears-format.md`.
|
|
58
|
+
|
|
59
|
+
## Always-Sync
|
|
60
|
+
|
|
61
|
+
`spec.json.translation.sync` is `always`:
|
|
62
|
+
|
|
63
|
+
- Whenever a canonical doc is written or updated (Step 5 requirements/research, Step 6 design, Step 7 tasks, or any later edit), regenerate the matching mirror file from the new canonical content.
|
|
64
|
+
- After regenerating, set `spec.json.translation.last_synced_at` to the current time.
|
|
65
|
+
- If a canonical task file is added/removed, add/remove the matching mirror file so the two trees stay 1:1.
|
|
66
|
+
|
|
67
|
+
## Not A Source Of Truth
|
|
68
|
+
|
|
69
|
+
- The deterministic validator (`validate-spec-output.cjs`) runs on **canonical only**. The `i18n/` tree is never validated and never blocks `ready_for_implementation`.
|
|
70
|
+
- `task_files` and `task_registry` reference canonical paths only.
|
|
71
|
+
- `hapo:develop` implements from canonical English; it must ignore `i18n/`.
|
|
72
|
+
- If the mirror and canonical ever disagree, canonical wins; re-sync the mirror.
|
|
73
|
+
|
|
74
|
+
## Fields (`spec.json.translation`)
|
|
75
|
+
|
|
76
|
+
| Field | Meaning |
|
|
77
|
+
|---|---|
|
|
78
|
+
| `enabled` | `true` once the user opts in |
|
|
79
|
+
| `language` | freeform label from settings (e.g. `Tiếng Việt`) |
|
|
80
|
+
| `language_code` | folder code (e.g. `vi`) |
|
|
81
|
+
| `dir` | relative mirror dir (e.g. `i18n/vi`) |
|
|
82
|
+
| `sync` | `always` |
|
|
83
|
+
| `last_synced_at` | ISO 8601 timestamp of the last regeneration |
|
|
@@ -4,8 +4,8 @@
|
|
|
4
4
|
EARS (Easy Approach to Requirements Syntax) is the standard format for acceptance criteria in spec-driven development.
|
|
5
5
|
|
|
6
6
|
EARS patterns describe the logical structure of a requirement (condition + subject + response) and are not tied to any particular natural language.
|
|
7
|
-
|
|
8
|
-
Keep EARS trigger keywords and fixed phrases in English (`When`, `If`, `While`, `Where`, `The system shall`, `The [system] shall`)
|
|
7
|
+
Canonical acceptance criteria are written in **English** (`spec.json.language` is `en`). A non-English rendering only appears in the optional translation mirror (`i18n/<lang>/`, see `references/translation-mirror.md`), never in the canonical files.
|
|
8
|
+
Keep EARS trigger keywords and fixed phrases in English (`When`, `If`, `While`, `Where`, `The system shall`, `The [system] shall`). In the mirror, localize only the variable parts (`[event]`, `[precondition]`, `[trigger]`, `[feature is included]`, `[response/action]`); do not interleave translated text inside the trigger or fixed English phrases themselves.
|
|
9
9
|
|
|
10
10
|
## Primary EARS Patterns
|
|
11
11
|
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"feature_name": "{{FEATURE_NAME}}",
|
|
3
3
|
"created_at": "{{TIMESTAMP}}",
|
|
4
4
|
"updated_at": "{{TIMESTAMP}}",
|
|
5
|
-
"language": "
|
|
5
|
+
"language": "en",
|
|
6
6
|
"status": "in_progress",
|
|
7
7
|
"phase": "initialized",
|
|
8
8
|
"current_phase": "init",
|
|
@@ -68,5 +68,13 @@
|
|
|
68
68
|
"questions_asked": 0,
|
|
69
69
|
"last_validated_at": null
|
|
70
70
|
},
|
|
71
|
+
"translation": {
|
|
72
|
+
"enabled": false,
|
|
73
|
+
"language": null,
|
|
74
|
+
"language_code": null,
|
|
75
|
+
"dir": null,
|
|
76
|
+
"sync": "always",
|
|
77
|
+
"last_synced_at": null
|
|
78
|
+
},
|
|
71
79
|
"ready_for_implementation": false
|
|
72
80
|
}
|