@nick848/fet 1.1.11 → 1.1.12
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/dist/cli/index.js +554 -11
- package/dist/cli/index.js.map +1 -1
- package/package.json +1 -1
package/dist/cli/index.js
CHANGED
|
@@ -3000,6 +3000,390 @@ When the artifact is \`specs/<capability>/spec.md\` (or you edit spec files in t
|
|
|
3000
3000
|
3. \u82F1\u6587\u89C4\u8303\u53E5\u6709\u4EFB\u4F55\u53D8\u52A8\u65F6\uFF0C**\u540C\u4E00\u6B21\u7F16\u8F91**\u5FC5\u987B\u540C\u6B65\u66F4\u65B0\u5BF9\u5E94\u4E2D\u6587\u6CE8\u91CA\u3002${uiBlock}`;
|
|
3001
3001
|
}
|
|
3002
3002
|
|
|
3003
|
+
// src/templates/write-boundary.ts
|
|
3004
|
+
var WRITE_BOUNDARY_ALLOW_PREFIXES = ["src/", "openspec/"];
|
|
3005
|
+
var WRITE_BOUNDARY_ROOT_CONFIG_EXACT = [
|
|
3006
|
+
".gitignore",
|
|
3007
|
+
".gitattributes",
|
|
3008
|
+
".stylelintignore",
|
|
3009
|
+
".eslintignore",
|
|
3010
|
+
".prettierignore",
|
|
3011
|
+
".editorconfig",
|
|
3012
|
+
".npmrc",
|
|
3013
|
+
".nvmrc",
|
|
3014
|
+
".node-version",
|
|
3015
|
+
".checkrc.js",
|
|
3016
|
+
"package.json",
|
|
3017
|
+
"package-lock.json",
|
|
3018
|
+
"pnpm-lock.yaml",
|
|
3019
|
+
"yarn.lock",
|
|
3020
|
+
"bun.lockb",
|
|
3021
|
+
"npm-shrinkwrap.json"
|
|
3022
|
+
];
|
|
3023
|
+
var WRITE_BOUNDARY_ROOT_CONFIG_BASENAME_PREFIXES = [
|
|
3024
|
+
".eslintrc",
|
|
3025
|
+
".stylelintrc",
|
|
3026
|
+
".prettierrc",
|
|
3027
|
+
".stylelint",
|
|
3028
|
+
"eslint.config",
|
|
3029
|
+
"stylelint.config",
|
|
3030
|
+
"prettier.config",
|
|
3031
|
+
"tsconfig",
|
|
3032
|
+
"vitest.config",
|
|
3033
|
+
"vite.config",
|
|
3034
|
+
"tsup.config",
|
|
3035
|
+
"jest.config",
|
|
3036
|
+
"rollup.config",
|
|
3037
|
+
"webpack.config",
|
|
3038
|
+
"babel.config",
|
|
3039
|
+
"biome.json",
|
|
3040
|
+
"deno.json",
|
|
3041
|
+
"deno.jsonc"
|
|
3042
|
+
];
|
|
3043
|
+
function renderRootConfigPathList(language) {
|
|
3044
|
+
const samples = language === "en" ? "`.gitignore`, `.stylelintignore`, `.stylelintrc*`, `.eslintrc*`, `eslint.config.*`, `.checkrc.js`, `package.json`, `package-lock.json`, `tsconfig*.json`, `.prettierrc*`, tool configs at repo root (`vitest.config.*`, `vite.config.*`, \u2026)" : "`.gitignore`\u3001`.stylelintignore`\u3001`.stylelintrc*`\u3001`.eslintrc*`\u3001`eslint.config.*`\u3001`.checkrc.js`\u3001`package.json`\u3001`package-lock.json`\u3001`tsconfig*.json`\u3001`.prettierrc*`\u3001\u4EE5\u53CA\u4ED3\u5E93\u6839\u76EE\u5F55\u4E0B\u7684 `vitest.config.*`\u3001`vite.config.*` \u7B49\u5DE5\u5177\u914D\u7F6E";
|
|
3045
|
+
return samples;
|
|
3046
|
+
}
|
|
3047
|
+
function renderWriteBoundaryGuardrail(language) {
|
|
3048
|
+
const rootList = renderRootConfigPathList(language);
|
|
3049
|
+
if (language === "en") {
|
|
3050
|
+
return `- Default write scope: \`src/**\`, \`openspec/**\`, and any \`**/.fet/**\`. All other paths need explicit user approval first.
|
|
3051
|
+
- **Repo-root config is high risk**: ${rootList} \u2014 list each file and why before editing; never change these silently.`;
|
|
3052
|
+
}
|
|
3053
|
+
return `- \u9ED8\u8BA4\u53EA\u5141\u8BB8\u4FEE\u6539 \`src/**\`\u3001\`openspec/**\` \u53CA\u4EFB\u610F \`**/.fet/**\`\uFF1B\u5176\u4F59\u8DEF\u5F84\u987B\u5148\u83B7\u7528\u6237\u660E\u786E\u540C\u610F\u3002
|
|
3054
|
+
- **\u4ED3\u5E93\u6839\u76EE\u5F55\u914D\u7F6E\u6587\u4EF6\u9AD8\u98CE\u9669**\uFF1A${rootList} \u2014 \u4FEE\u6539\u524D\u987B\u5217\u51FA\u6587\u4EF6\u4E0E\u539F\u56E0\uFF0C\u7981\u6B62\u64C5\u81EA\u6539\u52A8\u3002`;
|
|
3055
|
+
}
|
|
3056
|
+
function renderWriteBoundaryPolicyBody(language) {
|
|
3057
|
+
if (language === "en") {
|
|
3058
|
+
return `## Default allowed write scope
|
|
3059
|
+
|
|
3060
|
+
- \`src/**\` \u2014 application/library source
|
|
3061
|
+
- \`openspec/**\` \u2014 OpenSpec specs and change artifacts
|
|
3062
|
+
- \`**/.fet/**\` \u2014 per-change FET handoff files (including \`openspec/changes/<id>/.fet/\`)
|
|
3063
|
+
|
|
3064
|
+
## Ask the user first
|
|
3065
|
+
|
|
3066
|
+
Before creating, editing, or deleting files **outside** the allowed scope:
|
|
3067
|
+
|
|
3068
|
+
1. List every path you need to touch and why.
|
|
3069
|
+
2. Wait for explicit user approval (do not assume silence means yes).
|
|
3070
|
+
3. Prefer minimal diffs.
|
|
3071
|
+
|
|
3072
|
+
Common paths that need approval: \`tests/**\`, \`.github/**\`, \`.workflow/**\`, \`.cursor/**\`, \`.codex/**\`, \`AGENTS.md\`, \`README*\`, \`.env*\`.
|
|
3073
|
+
|
|
3074
|
+
## Repo-root config (always ask \u2014 never silent edit)
|
|
3075
|
+
|
|
3076
|
+
These live at the **repository root** (no subdirectory). Treat every change as infrastructure impact:
|
|
3077
|
+
|
|
3078
|
+
${renderRootConfigPathList(language)}
|
|
3079
|
+
|
|
3080
|
+
Workflow:
|
|
3081
|
+
|
|
3082
|
+
1. State **exact filenames** (e.g. \`package.json\`, \`.eslintrc.js\`, \`.gitignore\`).
|
|
3083
|
+
2. Explain **why** each file must change.
|
|
3084
|
+
3. Wait for **explicit user approval** before writing.
|
|
3085
|
+
4. Do not \u201Cfix lint/format\u201D by editing root configs unless the user requested that scope.
|
|
3086
|
+
|
|
3087
|
+
## Forbidden without approval
|
|
3088
|
+
|
|
3089
|
+
- Dependency or lockfile changes (\`package.json\`, \`package-lock.json\`, etc.) unless the user asked
|
|
3090
|
+
- Lint/format/tooling config at repo root (\`.eslintrc*\`, \`.stylelint*\`, \`.checkrc.js\`, \`.gitignore\`, \u2026) unless the user asked
|
|
3091
|
+
- Secrets or credential files
|
|
3092
|
+
- Using shell redirects or destructive git commands to bypass this policy
|
|
3093
|
+
|
|
3094
|
+
## Cursor enforcement
|
|
3095
|
+
|
|
3096
|
+
When \`.cursor/hooks/fet-guard-write-paths.mjs\` is installed, out-of-scope **write tools** trigger an approval prompt (\`permission: ask\`). Shell writes are gated by \`fet-guard-shell-writes.mjs\`. Rules alone are not sufficient\u2014keep hooks enabled after \`fet init\`.`;
|
|
3097
|
+
}
|
|
3098
|
+
return `## \u9ED8\u8BA4\u5141\u8BB8\u4FEE\u6539\u7684\u8303\u56F4
|
|
3099
|
+
|
|
3100
|
+
- \`src/**\` \u2014 \u4E1A\u52A1/\u5E93\u6E90\u7801
|
|
3101
|
+
- \`openspec/**\` \u2014 OpenSpec \u89C4\u8303\u4E0E change \u4EA7\u7269
|
|
3102
|
+
- \`**/.fet/**\` \u2014 \u5404 change \u7684 FET \u4EA4\u63A5\u6587\u4EF6\uFF08\u542B \`openspec/changes/<id>/.fet/\`\uFF09
|
|
3103
|
+
|
|
3104
|
+
## \u987B\u5148\u5F81\u5F97\u7528\u6237\u540C\u610F
|
|
3105
|
+
|
|
3106
|
+
\u5728**\u5141\u8BB8\u8303\u56F4\u5916**\u521B\u5EFA\u3001\u4FEE\u6539\u6216\u5220\u9664\u6587\u4EF6\u4E4B\u524D\uFF1A
|
|
3107
|
+
|
|
3108
|
+
1. \u5217\u51FA\u5C06\u8981\u4FEE\u6539\u7684\u8DEF\u5F84\u53CA\u539F\u56E0\u3002
|
|
3109
|
+
2. \u7B49\u5F85\u7528\u6237\u660E\u786E\u540C\u610F\uFF08\u4E0D\u8981\u9ED8\u8BA4\u6C89\u9ED8\u5373\u540C\u610F\uFF09\u3002
|
|
3110
|
+
3. \u4FDD\u6301\u6700\u5C0F\u6539\u52A8\u3002
|
|
3111
|
+
|
|
3112
|
+
\u5E38\u89C1\u9700\u5BA1\u6279\u8DEF\u5F84\uFF1A\`tests/**\`\u3001\`.github/**\`\u3001\`.workflow/**\`\u3001\`.cursor/**\`\u3001\`.codex/**\`\u3001\`AGENTS.md\`\u3001\`README*\`\u3001\`.env*\`\u3002
|
|
3113
|
+
|
|
3114
|
+
## \u4ED3\u5E93\u6839\u76EE\u5F55\u914D\u7F6E\uFF08\u59CB\u7EC8\u987B\u8BE2\u95EE\uFF0C\u7981\u6B62\u9759\u9ED8\u4FEE\u6539\uFF09
|
|
3115
|
+
|
|
3116
|
+
\u4EE5\u4E0B\u6587\u4EF6\u4F4D\u4E8E**\u9879\u76EE\u6839\u76EE\u5F55**\uFF08\u8DEF\u5F84\u4E2D\u65E0\u5B50\u76EE\u5F55\uFF09\uFF0C\u4E00\u5F8B\u89C6\u4E3A\u57FA\u7840\u8BBE\u65BD\u7EA7\u6539\u52A8\uFF1A
|
|
3117
|
+
|
|
3118
|
+
${renderRootConfigPathList(language)}
|
|
3119
|
+
|
|
3120
|
+
\u6D41\u7A0B\uFF1A
|
|
3121
|
+
|
|
3122
|
+
1. \u660E\u786E\u5217\u51FA**\u5B8C\u6574\u6587\u4EF6\u540D**\uFF08\u5982 \`package.json\`\u3001\`.eslintrc.js\`\u3001\`.gitignore\`\uFF09\u3002
|
|
3123
|
+
2. \u8BF4\u660E**\u6BCF\u9879\u4FEE\u6539\u7684\u539F\u56E0**\u3002
|
|
3124
|
+
3. \u83B7\u5F97\u7528\u6237**\u660E\u786E\u540C\u610F**\u540E\u518D\u5199\u5165\u3002
|
|
3125
|
+
4. \u4E0D\u8981\u4EE5\u300C\u987A\u4FBF\u4FEE lint/\u683C\u5F0F\u300D\u4E3A\u7531\u64C5\u81EA\u6539\u6839\u76EE\u5F55\u914D\u7F6E\u3002
|
|
3126
|
+
|
|
3127
|
+
## \u672A\u7ECF\u540C\u610F\u7981\u6B62
|
|
3128
|
+
|
|
3129
|
+
- \u64C5\u81EA\u6539\u4F9D\u8D56\u6216\u9501\u6587\u4EF6\uFF08\`package.json\`\u3001\`package-lock.json\` \u7B49\uFF09\uFF0C\u9664\u975E\u7528\u6237\u660E\u786E\u8981\u6C42
|
|
3130
|
+
- \u64C5\u81EA\u6539\u6839\u76EE\u5F55 lint/\u683C\u5F0F\u5316/\u5DE5\u5177\u94FE\u914D\u7F6E\uFF08\`.eslintrc*\`\u3001\`.stylelint*\`\u3001\`.checkrc.js\`\u3001\`.gitignore\` \u7B49\uFF09\uFF0C\u9664\u975E\u7528\u6237\u660E\u786E\u8981\u6C42
|
|
3131
|
+
- \u4FEE\u6539\u5BC6\u94A5\u6216\u51ED\u8BC1\u6587\u4EF6
|
|
3132
|
+
- \u7528 shell \u91CD\u5B9A\u5411\u6216\u7834\u574F\u6027 git \u547D\u4EE4\u7ED5\u8FC7\u672C\u7B56\u7565
|
|
3133
|
+
|
|
3134
|
+
## Cursor \u786C\u95E8\u7981
|
|
3135
|
+
|
|
3136
|
+
\u5B89\u88C5 \`.cursor/hooks/fet-guard-write-paths.mjs\` \u540E\uFF0C\u8D85\u51FA\u8303\u56F4\u7684**\u5199\u5DE5\u5177**\u4F1A\u5F39\u51FA\u5BA1\u6279\uFF08\`permission: ask\`\uFF09\uFF1B\`fet-guard-shell-writes.mjs\` \u7EA6\u675F\u53EF\u80FD\u5199\u6587\u4EF6\u7684 shell\u3002\u4EC5\u89C4\u5219\u4E0D\u591F\u2014\u2014\`fet init\` \u540E\u8BF7\u4FDD\u6301 hooks \u542F\u7528\u3002`;
|
|
3137
|
+
}
|
|
3138
|
+
function renderCursorWriteBoundaryRule(language) {
|
|
3139
|
+
const description = language === "en" ? "FET write boundary: default edits only under src/, openspec/, and .fet/" : "FET \u5199\u8DEF\u5F84\u8FB9\u754C\uFF1A\u9ED8\u8BA4\u4EC5\u53EF\u6539 src/\u3001openspec/\u3001.fet/";
|
|
3140
|
+
return `<!-- FET:MANAGED
|
|
3141
|
+
schemaVersion: 1
|
|
3142
|
+
fetVersion: ${FET_VERSION}
|
|
3143
|
+
generator: cursor-adapter
|
|
3144
|
+
adapterVersion: 1
|
|
3145
|
+
FET:END -->
|
|
3146
|
+
|
|
3147
|
+
---
|
|
3148
|
+
description: ${description}
|
|
3149
|
+
alwaysApply: true
|
|
3150
|
+
---
|
|
3151
|
+
|
|
3152
|
+
${renderWriteBoundaryPolicyBody(language)}
|
|
3153
|
+
|
|
3154
|
+
${renderWriteBoundaryGuardrail(language)}
|
|
3155
|
+
`;
|
|
3156
|
+
}
|
|
3157
|
+
function renderCodexWriteBoundaryGuide(language) {
|
|
3158
|
+
return `<!-- FET:MANAGED
|
|
3159
|
+
schemaVersion: 1
|
|
3160
|
+
fetVersion: ${FET_VERSION}
|
|
3161
|
+
generator: codex-adapter
|
|
3162
|
+
adapterVersion: 1
|
|
3163
|
+
FET:END -->
|
|
3164
|
+
|
|
3165
|
+
# ${language === "en" ? "Write path boundary (Codex)" : "\u5199\u8DEF\u5F84\u8FB9\u754C\uFF08Codex\uFF09"}
|
|
3166
|
+
|
|
3167
|
+
${renderWriteBoundaryPolicyBody(language)}
|
|
3168
|
+
|
|
3169
|
+
${renderWriteBoundaryGuardrail(language)}
|
|
3170
|
+
`;
|
|
3171
|
+
}
|
|
3172
|
+
function renderCursorWritePathsHookMjs() {
|
|
3173
|
+
const allowPrefixes = [...WRITE_BOUNDARY_ALLOW_PREFIXES];
|
|
3174
|
+
const rootExact = [...WRITE_BOUNDARY_ROOT_CONFIG_EXACT];
|
|
3175
|
+
const rootPrefixes = [...WRITE_BOUNDARY_ROOT_CONFIG_BASENAME_PREFIXES];
|
|
3176
|
+
return `#!/usr/bin/env node
|
|
3177
|
+
/**
|
|
3178
|
+
* FET:MANAGED
|
|
3179
|
+
* adapterVersion: 1
|
|
3180
|
+
* write-path guard for Cursor preToolUse (Write/StrReplace/EditNotebook/Delete).
|
|
3181
|
+
*/
|
|
3182
|
+
import { readFileSync } from "node:fs";
|
|
3183
|
+
|
|
3184
|
+
const ALLOW_PREFIXES = ${JSON.stringify(allowPrefixes)};
|
|
3185
|
+
const ROOT_CONFIG_EXACT = ${JSON.stringify(rootExact)};
|
|
3186
|
+
const ROOT_CONFIG_PREFIXES = ${JSON.stringify(rootPrefixes)};
|
|
3187
|
+
|
|
3188
|
+
function normalizePath(path) {
|
|
3189
|
+
return String(path ?? "")
|
|
3190
|
+
.replaceAll("\\\\", "/")
|
|
3191
|
+
.replace(/^\\.\\/+/, "")
|
|
3192
|
+
.replace(/^\\/+/u, "");
|
|
3193
|
+
}
|
|
3194
|
+
|
|
3195
|
+
function isRootConfigPath(path) {
|
|
3196
|
+
const normalized = normalizePath(path);
|
|
3197
|
+
if (!normalized || normalized.includes("/")) return false;
|
|
3198
|
+
const base = normalized.toLowerCase();
|
|
3199
|
+
if (ROOT_CONFIG_EXACT.includes(base)) return true;
|
|
3200
|
+
return ROOT_CONFIG_PREFIXES.some((prefix) => base === prefix || base.startsWith(prefix));
|
|
3201
|
+
}
|
|
3202
|
+
|
|
3203
|
+
function classifyPath(path) {
|
|
3204
|
+
const normalized = normalizePath(path);
|
|
3205
|
+
if (!normalized) return "ask";
|
|
3206
|
+
for (const prefix of ALLOW_PREFIXES) {
|
|
3207
|
+
const bare = prefix.endsWith("/") ? prefix.slice(0, -1) : prefix;
|
|
3208
|
+
if (normalized === bare || normalized.startsWith(prefix)) return "allow";
|
|
3209
|
+
}
|
|
3210
|
+
if (normalized.startsWith(".fet/") || normalized.includes("/.fet/")) return "allow";
|
|
3211
|
+
if (isRootConfigPath(normalized)) return "root_config";
|
|
3212
|
+
return "ask";
|
|
3213
|
+
}
|
|
3214
|
+
|
|
3215
|
+
function extractPaths(payload) {
|
|
3216
|
+
const paths = [];
|
|
3217
|
+
const tool = payload?.tool_name ?? payload?.toolName ?? "";
|
|
3218
|
+
const input = payload?.tool_input ?? payload?.toolInput ?? payload?.input ?? {};
|
|
3219
|
+
if (tool === "Write" || tool === "StrReplace" || tool === "Delete") {
|
|
3220
|
+
if (input.path) paths.push(input.path);
|
|
3221
|
+
}
|
|
3222
|
+
if (tool === "EditNotebook" && input.target_notebook) {
|
|
3223
|
+
paths.push(input.target_notebook);
|
|
3224
|
+
}
|
|
3225
|
+
return paths;
|
|
3226
|
+
}
|
|
3227
|
+
|
|
3228
|
+
function respond(decision, paths) {
|
|
3229
|
+
if (decision === "allow") {
|
|
3230
|
+
process.stdout.write(JSON.stringify({ permission: "allow" }));
|
|
3231
|
+
return;
|
|
3232
|
+
}
|
|
3233
|
+
const list = paths.map((p) => normalizePath(p)).filter(Boolean).join(", ") || "(unknown path)";
|
|
3234
|
+
const hasRootConfig = paths.some((p) => classifyPath(p) === "root_config");
|
|
3235
|
+
const msg = hasRootConfig
|
|
3236
|
+
? "FET: repo-root config file(s) require your explicit approval (.gitignore, package.json, .eslintrc*, .stylelint*, .checkrc.js, lockfiles, etc.). Approve only if you requested this tooling change."
|
|
3237
|
+
: "FET write boundary: this edit is outside src/, openspec/, or **/.fet/. Approve only if you intend to modify protected paths.";
|
|
3238
|
+
process.stdout.write(
|
|
3239
|
+
JSON.stringify({
|
|
3240
|
+
permission: "ask",
|
|
3241
|
+
user_message: msg + " Paths: " + list,
|
|
3242
|
+
agent_message: hasRootConfig
|
|
3243
|
+
? "Repo-root config edit blocked. Explain why each root config file must change and wait for user approval."
|
|
3244
|
+
: "Out-of-scope write blocked pending user approval. List why each path is needed, then retry after approval."
|
|
3245
|
+
})
|
|
3246
|
+
);
|
|
3247
|
+
}
|
|
3248
|
+
|
|
3249
|
+
const raw = readFileSync(0, "utf8");
|
|
3250
|
+
let payload = {};
|
|
3251
|
+
try {
|
|
3252
|
+
payload = JSON.parse(raw || "{}");
|
|
3253
|
+
} catch {
|
|
3254
|
+
process.stdout.write(JSON.stringify({ permission: "ask", user_message: "FET write guard: invalid hook payload." }));
|
|
3255
|
+
process.exit(0);
|
|
3256
|
+
}
|
|
3257
|
+
|
|
3258
|
+
const paths = extractPaths(payload);
|
|
3259
|
+
if (paths.length === 0) {
|
|
3260
|
+
respond("allow", paths);
|
|
3261
|
+
process.exit(0);
|
|
3262
|
+
}
|
|
3263
|
+
|
|
3264
|
+
const decisions = paths.map((p) => classifyPath(p));
|
|
3265
|
+
if (decisions.every((d) => d === "allow")) {
|
|
3266
|
+
respond("allow", paths);
|
|
3267
|
+
} else {
|
|
3268
|
+
respond("ask", paths);
|
|
3269
|
+
}
|
|
3270
|
+
process.exit(0);
|
|
3271
|
+
`;
|
|
3272
|
+
}
|
|
3273
|
+
function renderCursorShellWriteHookMjs() {
|
|
3274
|
+
return `#!/usr/bin/env node
|
|
3275
|
+
/**
|
|
3276
|
+
* FET:MANAGED
|
|
3277
|
+
* adapterVersion: 1
|
|
3278
|
+
* shell guard for commands that may write outside the FET write boundary.
|
|
3279
|
+
*/
|
|
3280
|
+
import { readFileSync } from "node:fs";
|
|
3281
|
+
|
|
3282
|
+
const REDIRECT = />\\s*[^\\s|&;]+/;
|
|
3283
|
+
const GIT_WRITE = /\\bgit\\s+(checkout|restore|reset|clean|apply)\\b/i;
|
|
3284
|
+
const PKG_WRITE = /\\b(npm|pnpm|yarn|bun)\\s+(install|ci|add|remove|update)\\b/i;
|
|
3285
|
+
const STREAM_EDIT = /\\b(sed|perl)\\s+[^\\n]*-i\\b/i;
|
|
3286
|
+
const TEE = /\\btee\\s+[^|;&\\n]+/i;
|
|
3287
|
+
|
|
3288
|
+
function respondAsk(command, reason) {
|
|
3289
|
+
process.stdout.write(
|
|
3290
|
+
JSON.stringify({
|
|
3291
|
+
permission: "ask",
|
|
3292
|
+
user_message: "FET shell guard: " + reason,
|
|
3293
|
+
agent_message: "Shell command may modify files outside src/openspec/.fet. Command: " + command
|
|
3294
|
+
})
|
|
3295
|
+
);
|
|
3296
|
+
}
|
|
3297
|
+
|
|
3298
|
+
const raw = readFileSync(0, "utf8");
|
|
3299
|
+
let payload = {};
|
|
3300
|
+
try {
|
|
3301
|
+
payload = JSON.parse(raw || "{}");
|
|
3302
|
+
} catch {
|
|
3303
|
+
respondAsk("", "invalid hook payload");
|
|
3304
|
+
process.exit(0);
|
|
3305
|
+
}
|
|
3306
|
+
|
|
3307
|
+
const command = String(payload?.command ?? "");
|
|
3308
|
+
if (!command.trim()) {
|
|
3309
|
+
process.stdout.write(JSON.stringify({ permission: "allow" }));
|
|
3310
|
+
process.exit(0);
|
|
3311
|
+
}
|
|
3312
|
+
|
|
3313
|
+
if (
|
|
3314
|
+
REDIRECT.test(command) ||
|
|
3315
|
+
GIT_WRITE.test(command) ||
|
|
3316
|
+
PKG_WRITE.test(command) ||
|
|
3317
|
+
STREAM_EDIT.test(command) ||
|
|
3318
|
+
TEE.test(command)
|
|
3319
|
+
) {
|
|
3320
|
+
respondAsk(command, "command may write or replace project files outside the default FET scope");
|
|
3321
|
+
process.exit(0);
|
|
3322
|
+
}
|
|
3323
|
+
|
|
3324
|
+
process.stdout.write(JSON.stringify({ permission: "allow" }));
|
|
3325
|
+
process.exit(0);
|
|
3326
|
+
`;
|
|
3327
|
+
}
|
|
3328
|
+
function renderCursorHooksJson() {
|
|
3329
|
+
return JSON.stringify(
|
|
3330
|
+
{
|
|
3331
|
+
version: 1,
|
|
3332
|
+
_fet: {
|
|
3333
|
+
writeBoundary: true,
|
|
3334
|
+
fetVersion: FET_VERSION
|
|
3335
|
+
},
|
|
3336
|
+
hooks: {
|
|
3337
|
+
preToolUse: [
|
|
3338
|
+
{
|
|
3339
|
+
command: ".cursor/hooks/fet-guard-write-paths.mjs",
|
|
3340
|
+
matcher: "Write|StrReplace|EditNotebook|Delete",
|
|
3341
|
+
failClosed: true
|
|
3342
|
+
}
|
|
3343
|
+
],
|
|
3344
|
+
beforeShellExecution: [
|
|
3345
|
+
{
|
|
3346
|
+
command: ".cursor/hooks/fet-guard-shell-writes.mjs",
|
|
3347
|
+
failClosed: true
|
|
3348
|
+
}
|
|
3349
|
+
]
|
|
3350
|
+
}
|
|
3351
|
+
},
|
|
3352
|
+
null,
|
|
3353
|
+
2
|
|
3354
|
+
);
|
|
3355
|
+
}
|
|
3356
|
+
function mergeCursorHooksJson(existingContent, fetContent) {
|
|
3357
|
+
const fet = JSON.parse(fetContent);
|
|
3358
|
+
if (!existingContent?.trim()) {
|
|
3359
|
+
return fetContent;
|
|
3360
|
+
}
|
|
3361
|
+
let existing;
|
|
3362
|
+
try {
|
|
3363
|
+
existing = JSON.parse(existingContent);
|
|
3364
|
+
} catch {
|
|
3365
|
+
return fetContent;
|
|
3366
|
+
}
|
|
3367
|
+
const merged = {
|
|
3368
|
+
version: existing.version ?? fet.version ?? 1,
|
|
3369
|
+
_fet: { ...existing._fet, ...fet._fet, writeBoundary: true },
|
|
3370
|
+
hooks: { ...existing.hooks }
|
|
3371
|
+
};
|
|
3372
|
+
for (const [event, fetEntries] of Object.entries(fet.hooks ?? {})) {
|
|
3373
|
+
const current = [...merged.hooks?.[event] ?? []];
|
|
3374
|
+
for (const entry of fetEntries) {
|
|
3375
|
+
const duplicate = current.some((item) => item.command === entry.command);
|
|
3376
|
+
if (!duplicate) {
|
|
3377
|
+
current.push(entry);
|
|
3378
|
+
}
|
|
3379
|
+
}
|
|
3380
|
+
merged.hooks = merged.hooks ?? {};
|
|
3381
|
+
merged.hooks[event] = current;
|
|
3382
|
+
}
|
|
3383
|
+
return `${JSON.stringify(merged, null, 2)}
|
|
3384
|
+
`;
|
|
3385
|
+
}
|
|
3386
|
+
|
|
3003
3387
|
// src/commands/update-context.ts
|
|
3004
3388
|
async function updateContextCommand(ctx) {
|
|
3005
3389
|
let contextResult = { warnings: [] };
|
|
@@ -6385,6 +6769,7 @@ Before doing FET or OpenSpec work in Codex, read:
|
|
|
6385
6769
|
- .codex/fet/spec-language.md when writing or updating OpenSpec specs
|
|
6386
6770
|
- openspec/changes/<change-id>/.fet/figma-apply-instructions.md before UI work when FET apply reports Figma links
|
|
6387
6771
|
- .codex/fet/ui-display-contract.md when UI binds API data; openspec/changes/<change-id>/.fet/ui-display-contract.yaml when present
|
|
6772
|
+
- .codex/fet/write-boundary.md for default edit scope (src/, openspec/, .fet/ only; ask before other paths)
|
|
6388
6773
|
- the active change files under openspec/changes/<change-id>/, when a change is selected
|
|
6389
6774
|
|
|
6390
6775
|
If GitNexus code graph context is available in the IDE or MCP tools, prefer it before broad repository scans. Use it to identify relevant modules, dependencies, and insertion points, then read only the concrete source files needed. If GitNexus is unavailable, continue with the normal FET/OpenSpec workflow.
|
|
@@ -6406,6 +6791,7 @@ ${languageInstruction(language)}
|
|
|
6406
6791
|
- \u6309 Figma \u5B9E\u73B0 UI \u65F6\u9605\u8BFB .codex/fet/figma-stop.md\uFF1B\u6709 figma-apply-instructions.md \u65F6\u5FC5\u987B\u5728\u6539 UI \u524D\u5B8C\u6574\u6267\u884C
|
|
6407
6792
|
- \u7F16\u5199\u6216\u66F4\u65B0 spec \u65F6\u9605\u8BFB .codex/fet/spec-language.md\uFF08\u82F1\u6587\u89C4\u8303 + \u540C\u6B21\u7F16\u8F91\u7EF4\u62A4\u4E2D\u6587\u6CE8\u91CA\uFF09
|
|
6408
6793
|
- \u7ED1\u5B9A\u63A5\u53E3\u7684 UI \u9605\u8BFB .codex/fet/ui-display-contract.md\uFF1B\u5B58\u5728 ui-display-contract.yaml \u65F6\u5B9E\u65BD\u524D\u987B\u786E\u8BA4 displayFields
|
|
6794
|
+
- \u4FEE\u6539\u6587\u4EF6\u524D\u9605\u8BFB .codex/fet/write-boundary.md\uFF08\u9ED8\u8BA4\u4EC5 src/\u3001openspec/\u3001.fet/\uFF1B\u5176\u5B83\u8DEF\u5F84\u987B\u7528\u6237\u660E\u786E\u540C\u610F\uFF09
|
|
6409
6795
|
- \u5982\u679C\u5DF2\u9009\u62E9 change\uFF0C\u9605\u8BFB openspec/changes/<change-id>/ \u4E0B\u7684\u5F53\u524D\u4EA7\u7269
|
|
6410
6796
|
|
|
6411
6797
|
\u5982\u679C IDE \u6216 MCP \u5DE5\u5177\u4E2D\u53EF\u7528 GitNexus \u4EE3\u7801\u56FE\u4E0A\u4E0B\u6587\uFF0C\u5148\u7528\u5B83\u7F29\u5C0F\u4ED3\u5E93\u626B\u63CF\u8303\u56F4\uFF1B\u7528\u56FE\u8BC6\u522B\u76F8\u5173\u6A21\u5757\u3001\u4F9D\u8D56\u548C\u63D2\u5165\u70B9\uFF0C\u518D\u53EA\u8BFB\u53D6\u9700\u8981\u786E\u8BA4\u884C\u4E3A\u7684\u5177\u4F53\u6E90\u7801\u6587\u4EF6\u3002GitNexus \u4E0D\u53EF\u7528\u65F6\uFF0C\u6309\u666E\u901A FET/OpenSpec \u5DE5\u4F5C\u6D41\u7EE7\u7EED\u3002
|
|
@@ -6444,12 +6830,19 @@ function codexUiDisplayContractFile(language = DEFAULT_LANGUAGE) {
|
|
|
6444
6830
|
content: renderCodexUiDisplayContractGuide(language)
|
|
6445
6831
|
};
|
|
6446
6832
|
}
|
|
6833
|
+
function codexWriteBoundaryFile(language = DEFAULT_LANGUAGE) {
|
|
6834
|
+
return {
|
|
6835
|
+
path: ".codex/fet/write-boundary.md",
|
|
6836
|
+
content: renderCodexWriteBoundaryGuide(language)
|
|
6837
|
+
};
|
|
6838
|
+
}
|
|
6447
6839
|
function codexCommandFiles(language = DEFAULT_LANGUAGE) {
|
|
6448
6840
|
return [
|
|
6449
6841
|
codexKarpathyGuidelinesFile(language),
|
|
6450
6842
|
codexFigmaStopFile(language),
|
|
6451
6843
|
codexUiDisplayContractFile(language),
|
|
6452
6844
|
codexSpecLanguageFile(language),
|
|
6845
|
+
codexWriteBoundaryFile(language),
|
|
6453
6846
|
...FET_ADAPTER_COMMANDS.map((command) => ({
|
|
6454
6847
|
path: `.codex/fet/commands/${command}.md`,
|
|
6455
6848
|
content: renderCommand(command, language)
|
|
@@ -6491,6 +6884,9 @@ function renderCommand(command, language) {
|
|
|
6491
6884
|
if (command.startsWith("graph-")) {
|
|
6492
6885
|
return renderGraphCommand(command, language);
|
|
6493
6886
|
}
|
|
6887
|
+
if (command === "tdd" || command === "test" || command === "visual") {
|
|
6888
|
+
return renderStandaloneWorkflowCommand(command, language);
|
|
6889
|
+
}
|
|
6494
6890
|
const usage = renderFetAdapterUsage(command, "");
|
|
6495
6891
|
return `<!-- FET:MANAGED
|
|
6496
6892
|
schemaVersion: 1
|
|
@@ -6521,11 +6917,16 @@ ${usage}
|
|
|
6521
6917
|
If the command needs a change id, pass it with \`--change <change-id>\` or use the active OpenSpec change from the user's request.
|
|
6522
6918
|
|
|
6523
6919
|
After the command completes, report the important next steps from the FET output and keep any generated OpenSpec artifacts in the normal project workflow.
|
|
6920
|
+
|
|
6921
|
+
${renderWriteBoundaryGuardrail(language)}
|
|
6524
6922
|
`;
|
|
6525
6923
|
}
|
|
6526
6924
|
function renderCommandZh(command) {
|
|
6527
6925
|
const usage = renderFetAdapterUsage(command, command === "fill-context" ? "" : command === "passthrough" ? "<openspec-command> [...args]" : "");
|
|
6528
6926
|
const title = commandTitleZh(command);
|
|
6927
|
+
if (command === "tdd" || command === "test" || command === "visual") {
|
|
6928
|
+
return renderStandaloneWorkflowCommand(command, "zh-CN");
|
|
6929
|
+
}
|
|
6529
6930
|
if (command === "graph-setup") {
|
|
6530
6931
|
return `<!-- FET:MANAGED
|
|
6531
6932
|
schemaVersion: 1
|
|
@@ -6583,6 +6984,8 @@ ${usage}
|
|
|
6583
6984
|
\u5982\u679C\u547D\u4EE4\u9700\u8981 change id\uFF0C\u4F18\u5148\u4F7F\u7528\u7528\u6237\u8F93\u5165\u3001\`--change <change-id>\`\u3001FET active change \u6216\u552F\u4E00\u6253\u5F00\u7684 change\u3002\u5B58\u5728\u6B67\u4E49\u65F6\u5148\u8BE2\u95EE\u7528\u6237\u3002
|
|
6584
6985
|
|
|
6585
6986
|
\u6267\u884C\u5B8C\u6210\u540E\uFF0C\u7528\u4E2D\u6587\u603B\u7ED3\u5173\u952E\u8F93\u51FA\u3001\u751F\u6210\u6216\u66F4\u65B0\u7684\u6587\u4EF6\uFF0C\u4EE5\u53CA\u4E0B\u4E00\u6B65\u5EFA\u8BAE\u3002
|
|
6987
|
+
|
|
6988
|
+
${renderWriteBoundaryGuardrail("zh-CN")}
|
|
6586
6989
|
`;
|
|
6587
6990
|
}
|
|
6588
6991
|
function renderPassthroughCommand(language) {
|
|
@@ -6699,6 +7102,9 @@ function renderSlashPrompt(command, language) {
|
|
|
6699
7102
|
if (command === "apply") {
|
|
6700
7103
|
return renderApplySlashPrompt(language);
|
|
6701
7104
|
}
|
|
7105
|
+
if (command === "tdd" || command === "test" || command === "visual") {
|
|
7106
|
+
return renderStandaloneWorkflowSlashPrompt(command, language);
|
|
7107
|
+
}
|
|
6702
7108
|
if (command === "verify") {
|
|
6703
7109
|
return renderVerifySlashPrompt(language);
|
|
6704
7110
|
}
|
|
@@ -6760,6 +7166,9 @@ function renderSlashPromptZh(command) {
|
|
|
6760
7166
|
if (command === "graph-setup") {
|
|
6761
7167
|
return renderGraphSetupSlashPrompt("zh-CN");
|
|
6762
7168
|
}
|
|
7169
|
+
if (command === "tdd" || command === "test" || command === "visual") {
|
|
7170
|
+
return renderStandaloneWorkflowSlashPrompt(command, "zh-CN");
|
|
7171
|
+
}
|
|
6763
7172
|
const usage = renderFetAdapterUsage(command, command === "fill-context" ? "" : command === "passthrough" ? "<openspec-command> [...args]" : "[...args]");
|
|
6764
7173
|
const argumentHint = command === "passthrough" ? "openspec-command [...args]" : void 0;
|
|
6765
7174
|
const argumentHintLine = argumentHint ? `argument-hint: ${argumentHint}
|
|
@@ -6800,9 +7209,10 @@ ${commandGoalZh(command)}
|
|
|
6800
7209
|
- \u9ED8\u8BA4\u4F7F\u7528\u4E2D\u6587\u4EA7\u51FA\u3002
|
|
6801
7210
|
- \u4E0D\u8981\u7ED5\u8FC7 FET \u76F4\u63A5\u8C03\u7528 openspec\uFF0C\u9664\u975E FET \u547D\u4EE4\u672C\u8EAB\u4E0D\u53EF\u7528\u3002
|
|
6802
7211
|
- change \u4E0D\u660E\u786E\u65F6\u5148\u8BE2\u95EE\u7528\u6237\u3002
|
|
6803
|
-
${
|
|
7212
|
+
${renderWriteBoundaryGuardrail("zh-CN")}
|
|
7213
|
+
${command === "fill-context" ? "- fet fill-context \u53EF\u80FD\u5DF2\u5199\u5165\u5C0F\u7A0B\u5E8F\u5305\u4F53\u79EF\u4E0E 2MB \u7EA6\u675F\uFF0C\u4E0D\u8981\u8986\u76D6\u8BE5\u8282\u4E2D\u7684\u626B\u63CF\u7ED3\u679C\uFF0C\u9664\u975E\u4ED3\u5E93\u7ED3\u6784\u5DF2\u53D8\u3002\n- \u66FF\u6362 AGENTS.md \u4E2D\u5176\u4F59 [NEEDS LLM INPUT] \u5360\u4F4D\u7B26\uFF0C\u4FDD\u7559 FET \u6258\u7BA1\u6807\u8BB0\uFF0C\u4E0D\u8981\u4FEE\u6539\u4E1A\u52A1\u4EE3\u7801\uFF1B\u4FEE\u6539 AGENTS.md \u5C5E\u4E8E\u5141\u8BB8\u8303\u56F4\u5916\u8DEF\u5F84\uFF0C\u987B\u5728\u672C\u8F6E\u5DF2\u83B7\u5F97\u7528\u6237\u540C\u610F\u3002\n" : ""}${command === "propose" || command === "continue" || command === "ff" ? "- \u4E00\u6B21\u53EA\u521B\u5EFA\u4E00\u4E2A ready artifact\uFF0C\u5E76\u5728\u5199\u5165\u524D\u9605\u8BFB\u4F9D\u8D56\u6587\u4EF6\u3002\n" : ""}${command === "propose" || command === "continue" || command === "ff" ? "- \u4E0D\u8981\u5728\u7528\u6237\u5BA1\u9605\u5F53\u524D\u4EA7\u7269\u524D\u81EA\u52A8\u8FD0\u884C fet continue\u3001fet ff \u6216\u5FAA\u73AF\u751F\u6210\u540E\u7EED\u4EA7\u7269\uFF1B\u9700\u8981\u7528\u6237\u660E\u786E\u786E\u8BA4\u540E\u518D\u63A8\u8FDB\u3002\n" : ""}${command === "propose" || command === "continue" || command === "ff" || command === "sync" ? `${renderSpecArtifactGuardrail("zh-CN")}
|
|
6804
7214
|
` : ""}${command === "propose" || command === "continue" || command === "ff" ? `${renderUiDisplayContractGuardrail("zh-CN")}
|
|
6805
|
-
` : ""}${command === "apply" ? "- \u4E0D\u8981\u5728\u672A\u5B8C\u6210\u771F\u5B9E\u5B9E\u73B0\u524D\u52FE\u9009 tasks.md\uFF1B\u4E0D\u8981\u4ECE apply \u9636\u6BB5\u76F4\u63A5 sync \u6216 archive\u3002\n- \u82E5\u5B58\u5728 openspec/changes/<change-id>/.fet/figma-apply-instructions.md\uFF0C\u5FC5\u987B\u5148\u8BFB Figma\uFF08MCP/API\uFF09\u518D\u6539 UI\uFF1B\u5931\u8D25\u5219\u505C\u4E0B\u95EE\u7528\u6237\uFF0C\u7981\u6B62\u731C\u6837\u5F0F\u3002\n- \u82E5\u5B58\u5728 openspec/changes/<change-id>/.fet/ui-display-contract.yaml\uFF0C\u5B9E\u65BD\u7ED1\u5B9A\u63A5\u53E3\u7684 UI \u524D\u987B\u786E\u8BA4 displayFields\uFF0C\u7981\u6B62\u6309 OpenAPI \u5168\u91CF\u5C55\u793A\u3002\n" : ""}`;
|
|
7215
|
+
` : ""}${command === "apply" ? "- \u4E0D\u8981\u5728\u672A\u5B8C\u6210\u771F\u5B9E\u5B9E\u73B0\u524D\u52FE\u9009 tasks.md\uFF1B\u4E0D\u8981\u4ECE apply \u9636\u6BB5\u76F4\u63A5 sync \u6216 archive\u3002\n- \u5B9E\u65BD\u524D\u987B\u5DF2\u8FD0\u884C fet tdd\uFF1B\u5B58\u5728 tdd-manifest.yaml \u65F6\u6309\u6E05\u5355\u7F16\u5199\u6D4B\u8BD5\u4E0E\u5B9E\u73B0\uFF0Cfet verify \u524D\u5148 fet test\u3002\n- \u82E5\u5B58\u5728 openspec/changes/<change-id>/.fet/figma-apply-instructions.md\uFF0C\u5FC5\u987B\u5148\u8BFB Figma\uFF08MCP/API\uFF09\u518D\u6539 UI\uFF1B\u5931\u8D25\u5219\u505C\u4E0B\u95EE\u7528\u6237\uFF0C\u7981\u6B62\u731C\u6837\u5F0F\u3002\n- \u82E5\u5B58\u5728 openspec/changes/<change-id>/.fet/ui-display-contract.yaml\uFF0C\u5B9E\u65BD\u7ED1\u5B9A\u63A5\u53E3\u7684 UI \u524D\u987B\u786E\u8BA4 displayFields\uFF0C\u7981\u6B62\u6309 OpenAPI \u5168\u91CF\u5C55\u793A\u3002\n" : ""}`;
|
|
6806
7216
|
}
|
|
6807
7217
|
function commandTitleZh(command) {
|
|
6808
7218
|
const titles = {
|
|
@@ -6876,7 +7286,9 @@ First run:
|
|
|
6876
7286
|
fet fill-context
|
|
6877
7287
|
\`\`\`
|
|
6878
7288
|
|
|
6879
|
-
Then read AGENTS.md and openspec/config.yaml, inspect the project, and replace every [NEEDS LLM INPUT] or [NEED LLM INPUT] placeholder in AGENTS.md with concrete project-specific content. Preserve FET managed markers and do not modify business code.
|
|
7289
|
+
Then read AGENTS.md and openspec/config.yaml, inspect the project, and replace every [NEEDS LLM INPUT] or [NEED LLM INPUT] placeholder in AGENTS.md with concrete project-specific content. Preserve FET managed markers and do not modify business code. Editing AGENTS.md is outside the default write scope\u2014confirm user approval first.
|
|
7290
|
+
|
|
7291
|
+
${renderWriteBoundaryGuardrail(language)}
|
|
6880
7292
|
`;
|
|
6881
7293
|
}
|
|
6882
7294
|
function renderFillContextSlashPrompt(language) {
|
|
@@ -6912,7 +7324,9 @@ Steps:
|
|
|
6912
7324
|
Guardrails:
|
|
6913
7325
|
- Do not invent facts that cannot be inferred from the repo.
|
|
6914
7326
|
- Use [UNKNOWN] only when the repository does not contain enough evidence.
|
|
6915
|
-
- Keep generated context stable and useful for future AI coding sessions
|
|
7327
|
+
- Keep generated context stable and useful for future AI coding sessions.
|
|
7328
|
+
- Editing AGENTS.md is outside the default write scope; confirm user approval first (see .codex/fet/write-boundary.md).
|
|
7329
|
+
${renderWriteBoundaryGuardrail(language)}`,
|
|
6916
7330
|
void 0,
|
|
6917
7331
|
language
|
|
6918
7332
|
);
|
|
@@ -6981,14 +7395,16 @@ Steps:
|
|
|
6981
7395
|
- Follow proposal, specs, design, and tasks.
|
|
6982
7396
|
- Mark each completed task checkbox in tasks.md from \`- [ ]\` to \`- [x]\`.
|
|
6983
7397
|
- Pause and ask if a task is ambiguous or reveals a design conflict.
|
|
6984
|
-
8.
|
|
7398
|
+
8. Run \`fet tdd --change <change-id>\` before implementation when no \`tdd-manifest.yaml\` exists yet; after coding run \`fet test --change <change-id>\` before \`fet verify\`.
|
|
7399
|
+
9. After completing or pausing, summarize completed tasks, remaining tasks, and blockers.
|
|
6985
7400
|
|
|
6986
7401
|
Guardrails:
|
|
6987
7402
|
- Never skip reading OpenSpec artifacts before implementation.
|
|
6988
7403
|
- When Figma links exist for this change, never implement or restyle UI without reading Figma first.
|
|
6989
7404
|
- When ui-display-contract.yaml exists, API schemas are not UI checklists\u2014only displayFields may render.
|
|
6990
7405
|
- Do not mark a task complete until the code change is actually done.
|
|
6991
|
-
- Do not run sync or archive from apply
|
|
7406
|
+
- Do not run sync or archive from apply.
|
|
7407
|
+
${renderWriteBoundaryGuardrail(language)}`,
|
|
6992
7408
|
void 0,
|
|
6993
7409
|
language
|
|
6994
7410
|
);
|
|
@@ -7415,8 +7831,97 @@ ${languageInstruction(language)}
|
|
|
7415
7831
|
${graphContextInstruction}
|
|
7416
7832
|
|
|
7417
7833
|
${body}
|
|
7834
|
+
|
|
7835
|
+
${renderWriteBoundaryGuardrail(language)}
|
|
7418
7836
|
`;
|
|
7419
7837
|
}
|
|
7838
|
+
function renderStandaloneWorkflowCommand(command, language) {
|
|
7839
|
+
const usage = renderFetAdapterUsage(command, "");
|
|
7840
|
+
const { title, body } = standaloneWorkflowCopy(command, language);
|
|
7841
|
+
return `<!-- FET:MANAGED
|
|
7842
|
+
schemaVersion: 1
|
|
7843
|
+
fetVersion: ${FET_VERSION}
|
|
7844
|
+
generator: codex-adapter
|
|
7845
|
+
adapterVersion: 1
|
|
7846
|
+
command: ${usage}
|
|
7847
|
+
FET:END -->
|
|
7848
|
+
|
|
7849
|
+
# ${usage}
|
|
7850
|
+
|
|
7851
|
+
${renderIdeModelPolicy(command, language)}
|
|
7852
|
+
|
|
7853
|
+
${languageInstruction(language)}
|
|
7854
|
+
|
|
7855
|
+
## ${language === "en" ? "Purpose" : "\u7528\u9014"}
|
|
7856
|
+
|
|
7857
|
+
${title}
|
|
7858
|
+
|
|
7859
|
+
## ${language === "en" ? "Workflow" : "\u5DE5\u4F5C\u6D41"}
|
|
7860
|
+
|
|
7861
|
+
${body}
|
|
7862
|
+
|
|
7863
|
+
${renderWriteBoundaryGuardrail(language)}
|
|
7864
|
+
`;
|
|
7865
|
+
}
|
|
7866
|
+
function renderStandaloneWorkflowSlashPrompt(command, language) {
|
|
7867
|
+
const usage = renderFetAdapterUsage(command, "[...args]");
|
|
7868
|
+
const { title, body, description } = standaloneWorkflowCopy(command, language);
|
|
7869
|
+
return renderManagedSlashPrompt(
|
|
7870
|
+
usage,
|
|
7871
|
+
description,
|
|
7872
|
+
`${title}
|
|
7873
|
+
|
|
7874
|
+
Steps:
|
|
7875
|
+
|
|
7876
|
+
1. Read AGENTS.md, openspec/config.yaml, .codex/fet/karpathy-guidelines.md, and .codex/fet/write-boundary.md.
|
|
7877
|
+
2. Resolve the change id when needed (\`--change <change-id>\` or active change).
|
|
7878
|
+
3. Run:
|
|
7879
|
+
\`\`\`sh
|
|
7880
|
+
${renderFetAdapterUsage(command, "")}
|
|
7881
|
+
\`\`\`
|
|
7882
|
+
4. ${body}
|
|
7883
|
+
5. Summarize outputs, paths written under openspec/changes/<change-id>/.fet/, and next steps.
|
|
7884
|
+
|
|
7885
|
+
Guardrails:
|
|
7886
|
+
- ${body.split("\n")[0]}
|
|
7887
|
+
${renderWriteBoundaryGuardrail(language)}`,
|
|
7888
|
+
void 0,
|
|
7889
|
+
language
|
|
7890
|
+
);
|
|
7891
|
+
}
|
|
7892
|
+
function standaloneWorkflowCopy(command, language) {
|
|
7893
|
+
if (command === "tdd") {
|
|
7894
|
+
return language === "en" ? {
|
|
7895
|
+
description: "Generate per-change TDD manifest and test instructions",
|
|
7896
|
+
title: "Generate the TDD manifest before implementation.",
|
|
7897
|
+
body: "After planning artifacts exist, run this before `fet apply`. It writes `openspec/changes/<change-id>/.fet/tdd-manifest.yaml`, `tdd-spec.md`, and `tdd-instructions.md`. Then add failing tests in the repo before implementation (tests/ edits need user approval per write-boundary)."
|
|
7898
|
+
} : {
|
|
7899
|
+
description: "\u751F\u6210\u672C change \u7684 TDD \u6E05\u5355\u4E0E\u6D4B\u8BD5\u6307\u5F15",
|
|
7900
|
+
title: "\u5728\u5B9E\u65BD\u524D\u751F\u6210 TDD \u6E05\u5355\u3002",
|
|
7901
|
+
body: "\u89C4\u5212\u4EA7\u7269\u5C31\u7EEA\u540E\u3001\u6267\u884C `fet apply` \u4E4B\u524D\u8FD0\u884C\u3002\u4F1A\u5199\u5165 `openspec/changes/<change-id>/.fet/tdd-manifest.yaml`\u3001`tdd-spec.md`\u3001`tdd-instructions.md`\uFF0C\u518D\u5728\u4ED3\u5E93\u4E2D\u7F16\u5199\u9884\u671F\u5931\u8D25\u7684\u6D4B\u8BD5\uFF08\u4FEE\u6539 tests/ \u987B\u7B26\u5408 write-boundary\uFF0C\u5148\u83B7\u7528\u6237\u540C\u610F\uFF09\u3002"
|
|
7902
|
+
};
|
|
7903
|
+
}
|
|
7904
|
+
if (command === "test") {
|
|
7905
|
+
return language === "en" ? {
|
|
7906
|
+
description: "Run unit tests scoped to the change TDD manifest",
|
|
7907
|
+
title: "Run change-scoped tests after implementation.",
|
|
7908
|
+
body: "Requires `tdd-manifest.yaml`. Records pass/fail in FET state; `fet verify` is blocked until this passes (unless configured to skip)."
|
|
7909
|
+
} : {
|
|
7910
|
+
description: "\u6309 change TDD \u6E05\u5355\u8FD0\u884C\u5355\u6D4B",
|
|
7911
|
+
title: "\u5B9E\u73B0\u5B8C\u6210\u540E\u6309 change \u8FD0\u884C\u6D4B\u8BD5\u3002",
|
|
7912
|
+
body: "\u9700\u8981\u6709\u6548\u7684 `tdd-manifest.yaml`\u3002\u7ED3\u679C\u5199\u5165 FET \u72B6\u6001\uFF1B\u672A\u901A\u8FC7\u524D `fet verify` \u4F1A\u88AB\u62E6\u622A\uFF08\u9664\u975E\u914D\u7F6E\u4E3A skip\uFF09\u3002"
|
|
7913
|
+
};
|
|
7914
|
+
}
|
|
7915
|
+
return language === "en" ? {
|
|
7916
|
+
description: "Layout-only visual verification for a change",
|
|
7917
|
+
title: "Run layout-only visual checks for the change.",
|
|
7918
|
+
body: "Default `fet visual` refreshes the manifest, captures with Playwright (`--base-url` required), and runs layout-only checks (no pixel match on dynamic API content). Use `--plan`, `--capture-only`, or `--check-layout-only` only when debugging."
|
|
7919
|
+
} : {
|
|
7920
|
+
description: "change \u7EA7 layout-only \u89C6\u89C9\u9A8C\u6536",
|
|
7921
|
+
title: "\u5BF9 change \u505A layout-only \u89C6\u89C9\u9A8C\u6536\u3002",
|
|
7922
|
+
body: "\u9ED8\u8BA4 `fet visual` \u4F1A\u66F4\u65B0\u6E05\u5355\u3001Playwright \u622A\u56FE\uFF08\u9700 `--base-url`\uFF09\u5E76\u505A layout-only \u68C0\u67E5\uFF08\u4E0D\u5BF9\u52A8\u6001\u63A5\u53E3\u5185\u5BB9\u505A\u50CF\u7D20\u5BF9\u6BD4\uFF09\u3002\u4EC5\u5728\u8C03\u8BD5\u65F6\u4F7F\u7528 `--plan`\u3001`--capture-only`\u3001`--check-layout-only`\u3002"
|
|
7923
|
+
};
|
|
7924
|
+
}
|
|
7420
7925
|
|
|
7421
7926
|
// src/adapters/codex/index.ts
|
|
7422
7927
|
var CodexAdapter = class {
|
|
@@ -7536,6 +8041,28 @@ function cursorSpecLanguageRuleFile(language = DEFAULT_LANGUAGE) {
|
|
|
7536
8041
|
content: renderCursorSpecLanguageRule(language)
|
|
7537
8042
|
};
|
|
7538
8043
|
}
|
|
8044
|
+
function cursorWriteBoundaryRuleFile(language = DEFAULT_LANGUAGE) {
|
|
8045
|
+
return {
|
|
8046
|
+
path: ".cursor/rules/fet-write-boundary.mdc",
|
|
8047
|
+
content: renderCursorWriteBoundaryRule(language)
|
|
8048
|
+
};
|
|
8049
|
+
}
|
|
8050
|
+
function cursorHookFiles() {
|
|
8051
|
+
return [
|
|
8052
|
+
{
|
|
8053
|
+
path: ".cursor/hooks/fet-guard-write-paths.mjs",
|
|
8054
|
+
content: renderCursorWritePathsHookMjs()
|
|
8055
|
+
},
|
|
8056
|
+
{
|
|
8057
|
+
path: ".cursor/hooks/fet-guard-shell-writes.mjs",
|
|
8058
|
+
content: renderCursorShellWriteHookMjs()
|
|
8059
|
+
},
|
|
8060
|
+
{
|
|
8061
|
+
path: ".cursor/hooks.json",
|
|
8062
|
+
content: renderCursorHooksJson()
|
|
8063
|
+
}
|
|
8064
|
+
];
|
|
8065
|
+
}
|
|
7539
8066
|
function cursorUiDisplayContractRuleFile(language = DEFAULT_LANGUAGE) {
|
|
7540
8067
|
return {
|
|
7541
8068
|
path: ".cursor/rules/fet-ui-display-contract.mdc",
|
|
@@ -7545,6 +8072,7 @@ function cursorUiDisplayContractRuleFile(language = DEFAULT_LANGUAGE) {
|
|
|
7545
8072
|
function cursorRuleFiles(language = DEFAULT_LANGUAGE) {
|
|
7546
8073
|
return [
|
|
7547
8074
|
cursorRuleFile(language),
|
|
8075
|
+
cursorWriteBoundaryRuleFile(language),
|
|
7548
8076
|
cursorFigmaStopRuleFile(language),
|
|
7549
8077
|
cursorUiDisplayContractRuleFile(language),
|
|
7550
8078
|
cursorSpecLanguageRuleFile(language)
|
|
@@ -7582,6 +8110,7 @@ ${languageInstruction(language)}
|
|
|
7582
8110
|
- \u6309 Figma \u5B9E\u73B0 UI \u65F6\u9075\u5B88 \`.cursor/rules/fet-figma-stop.mdc\`\uFF1B\u82E5\u5B58\u5728 \`openspec/changes/<change-id>/.fet/figma-apply-instructions.md\` \u5FC5\u987B\u5728\u6539 UI \u524D\u5B8C\u6574\u6267\u884C\uFF1B\u540C\u76EE\u5F55 \`figma-stop.md\` \u542B\u94FE\u63A5\u4E0E\u505C\u6B62\u89C4\u5219\u3002
|
|
7583
8111
|
- \u7F16\u5199\u6216\u4FEE\u6539 OpenSpec \`specs/**/spec.md\` \u65F6\u9075\u5B88 \`.cursor/rules/fet-spec-language.mdc\`\uFF08\u82F1\u6587\u89C4\u8303 + \`<!-- \u4E2D\u6587\uFF1A... -->\`\uFF0C\u540C\u6B21\u7F16\u8F91\u540C\u6B65\u66F4\u65B0\u4E2D\u6587\u8BF4\u660E\uFF09\u3002
|
|
7584
8112
|
- \u7ED1\u5B9A\u63A5\u53E3\u7684 UI \u9075\u5B88 \`.cursor/rules/fet-ui-display-contract.mdc\`\uFF1B\u5B58\u5728 \`openspec/changes/<change-id>/.fet/ui-display-contract.yaml\` \u65F6\u5B9E\u65BD\u524D\u987B\u786E\u8BA4 displayFields\u3002
|
|
8113
|
+
- \u4FEE\u6539\u6587\u4EF6\u65F6\u9075\u5B88 \`.cursor/rules/fet-write-boundary.mdc\`\uFF08\u9ED8\u8BA4\u4EC5 \`src/**\`\u3001\`openspec/**\`\u3001\`**/.fet/**\`\uFF1B\u5176\u5B83\u8DEF\u5F84\u987B\u7528\u6237\u660E\u786E\u540C\u610F\uFF09\u3002Cursor hooks \u4F1A\u5BF9\u8D8A\u754C\u5199\u5165\u5F39\u51FA\u5BA1\u6279\u3002
|
|
7585
8114
|
|
|
7586
8115
|
\u5982\u679C\u7528\u6237\u8F93\u5165\u7C7B\u4F3C \`/fet apply\` \u7684\u8BF7\u6C42\uFF0C\u8BF7\u628A\u5B83\u89C6\u4E3A\u5DE5\u4F5C\u6D41\u610F\u56FE\uFF0C\u5E76\u63D0\u793A\u6216\u6267\u884C\u5BF9\u5E94\u7684\u7EC8\u7AEF\u547D\u4EE4 \`fet <cmd>\`\u3002
|
|
7587
8116
|
`
|
|
@@ -7617,7 +8146,9 @@ ${languageInstruction(language)}
|
|
|
7617
8146
|
|
|
7618
8147
|
\`fet fill-context\` \u53EF\u80FD\u5DF2\u5199\u5165\u5C0F\u7A0B\u5E8F\u5305\u4F53\u79EF\u8868\u4E0E 2MB \u5F00\u53D1\u7EA6\u675F\uFF0C\u4E0D\u8981\u8986\u76D6 AGENTS.md\u300C\u5C0F\u7A0B\u5E8F\u300D\u8282\u4E2D\u7684\u626B\u63CF\u7ED3\u679C\uFF0C\u9664\u975E\u4ED3\u5E93\u7ED3\u6784\u5DF2\u53D8\u3002
|
|
7619
8148
|
|
|
7620
|
-
\u68C0\u67E5 README\u3001package scripts\u3001\u8DEF\u7531\u3001\u6D4B\u8BD5\u3001\u6E90\u7801\u7ED3\u6784\u548C\u73B0\u6709\u7EA6\u5B9A\u540E\uFF0C\u628A AGENTS.md \u4E2D**\u5176\u4F59** \`[NEEDS LLM INPUT]\` \u6216 \`[NEED LLM INPUT]\` \u5360\u4F4D\u7B26\u66FF\u6362\u4E3A\u5177\u4F53\u3001\u7B80\u6D01\u3001\u9879\u76EE\u76F8\u5173\u7684\u5185\u5BB9\u3002\u4FDD\u7559 FET \u6258\u7BA1\u6807\u8BB0\uFF0C\u4E0D\u8981\u4FEE\u6539\u4E1A\u52A1\u4EE3\u7801\u3002
|
|
8149
|
+
\u68C0\u67E5 README\u3001package scripts\u3001\u8DEF\u7531\u3001\u6D4B\u8BD5\u3001\u6E90\u7801\u7ED3\u6784\u548C\u73B0\u6709\u7EA6\u5B9A\u540E\uFF0C\u628A AGENTS.md \u4E2D**\u5176\u4F59** \`[NEEDS LLM INPUT]\` \u6216 \`[NEED LLM INPUT]\` \u5360\u4F4D\u7B26\u66FF\u6362\u4E3A\u5177\u4F53\u3001\u7B80\u6D01\u3001\u9879\u76EE\u76F8\u5173\u7684\u5185\u5BB9\u3002\u4FDD\u7559 FET \u6258\u7BA1\u6807\u8BB0\uFF0C\u4E0D\u8981\u4FEE\u6539\u4E1A\u52A1\u4EE3\u7801\u3002\u4FEE\u6539 \`AGENTS.md\` \u4E0D\u5728\u9ED8\u8BA4\u5199\u8DEF\u5F84\u5185\uFF0C\u987B\u786E\u8BA4\u7528\u6237\u5DF2\u540C\u610F\uFF08\u6216\u7531 hooks \u5BA1\u6279\uFF09\u3002
|
|
8150
|
+
|
|
8151
|
+
${renderWriteBoundaryGuardrail(language)}
|
|
7621
8152
|
`;
|
|
7622
8153
|
}
|
|
7623
8154
|
if (command === "graph-setup") {
|
|
@@ -7723,6 +8254,8 @@ ${renderSpecArtifactGuardrail(language)}
|
|
|
7723
8254
|
|
|
7724
8255
|
${renderUiDisplayContractGuardrail(language)}
|
|
7725
8256
|
|
|
8257
|
+
${renderWriteBoundaryGuardrail(language)}
|
|
8258
|
+
|
|
7726
8259
|
\u6267\u884C\u524D\u8BF7\u9605\u8BFB AGENTS.md\u3001openspec/config.yaml\uFF08\u542B \`fet.specLanguage\`\uFF09\u4E0E\u5F53\u524D change \u5DF2\u6709\u4EA7\u7269\u3002
|
|
7727
8260
|
`;
|
|
7728
8261
|
}
|
|
@@ -7778,6 +8311,8 @@ ${uiContractBlock}
|
|
|
7778
8311
|
\u6267\u884C\u524D\u8BF7\u9605\u8BFB AGENTS.md\u3001openspec/config.yaml \u4E0E\u5F53\u524D change \u4E0B\u7684 OpenSpec \u4EA7\u7269\u3002
|
|
7779
8312
|
|
|
7780
8313
|
\u5B9E\u65BD\u524D\u987B\u5DF2\u8FD0\u884C \`fet tdd\`\uFF1B\u5B58\u5728 \`openspec/changes/<change-id>/.fet/tdd-manifest.yaml\` \u65F6\u6309\u6E05\u5355\u7F16\u5199\u6D4B\u8BD5\u4E0E\u5B9E\u73B0\uFF0C\u5E76\u5728 \`fet verify\` \u524D\u5148 \`fet test\`\u3002
|
|
8314
|
+
|
|
8315
|
+
${renderWriteBoundaryGuardrail(language)}
|
|
7781
8316
|
`;
|
|
7782
8317
|
}
|
|
7783
8318
|
function renderVisualSkill(usage, language) {
|
|
@@ -7853,7 +8388,7 @@ var CursorAdapter = class {
|
|
|
7853
8388
|
async planInstall(_projectRoot, language) {
|
|
7854
8389
|
return {
|
|
7855
8390
|
tool: this.tool,
|
|
7856
|
-
files: [...cursorSkillFiles(language), ...cursorRuleFiles(language)].map((file) => ({
|
|
8391
|
+
files: [...cursorSkillFiles(language), ...cursorRuleFiles(language), ...cursorHookFiles()].map((file) => ({
|
|
7857
8392
|
...file,
|
|
7858
8393
|
managed: true
|
|
7859
8394
|
}))
|
|
@@ -7865,6 +8400,12 @@ var CursorAdapter = class {
|
|
|
7865
8400
|
for (const file of plan.files) {
|
|
7866
8401
|
const target = join31(projectRoot, file.path);
|
|
7867
8402
|
const existing = await readExisting2(target);
|
|
8403
|
+
if (file.path === ".cursor/hooks.json") {
|
|
8404
|
+
await mkdir13(dirname12(target), { recursive: true });
|
|
8405
|
+
await atomicWrite(target, mergeCursorHooksJson(existing, file.content));
|
|
8406
|
+
written.push(file.path);
|
|
8407
|
+
continue;
|
|
8408
|
+
}
|
|
7868
8409
|
if (existing && !existing.includes("FET:MANAGED") && !force) {
|
|
7869
8410
|
throw new FetError({
|
|
7870
8411
|
code: "TOOL_ADAPTER_CONFLICT" /* ToolAdapterConflict */,
|
|
@@ -7888,12 +8429,14 @@ var CursorAdapter = class {
|
|
|
7888
8429
|
for (const file of plan.files) {
|
|
7889
8430
|
const target = join31(projectRoot, file.path);
|
|
7890
8431
|
const content = await readExisting2(target);
|
|
7891
|
-
const
|
|
7892
|
-
const
|
|
8432
|
+
const hooksManaged = file.path === ".cursor/hooks.json" && Boolean(content?.includes('"writeBoundary"'));
|
|
8433
|
+
const hookScript = file.path.startsWith(".cursor/hooks/fet-guard-") && file.path.endsWith(".mjs");
|
|
8434
|
+
const managed = hooksManaged || hookScript || Boolean(content?.includes("FET:MANAGED"));
|
|
8435
|
+
const versionMatches = file.path === ".cursor/hooks.json" ? Boolean(content?.includes('"writeBoundary"')) : hookScript ? Boolean(content?.includes(`adapterVersion: ${this.adapterVersion}`)) : Boolean(content?.includes(`adapterVersion: ${this.adapterVersion}`));
|
|
7893
8436
|
checks.push({
|
|
7894
8437
|
id: `cursor:${file.path}`,
|
|
7895
8438
|
status: !content ? "warn" : managed && versionMatches ? "pass" : "warn",
|
|
7896
|
-
message: !content ? `${file.path} \u7F3A\u5931` : !managed ? `${file.path} \u5DF2\u5B58\u5728\uFF0C\u4F46\u4E0D\u7531 FET \u7BA1\u7406` : !versionMatches ? `${file.path}
|
|
8439
|
+
message: !content ? `${file.path} \u7F3A\u5931` : !managed ? `${file.path} \u5DF2\u5B58\u5728\uFF0C\u4F46\u4E0D\u7531 FET \u7BA1\u7406` : !versionMatches ? `${file.path} \u7248\u672C\u5DF2\u8FC7\u671F\uFF08\u8BF7\u8FD0\u884C fet init \u66F4\u65B0\uFF09` : `${file.path} \u5DF2\u5B58\u5728\u4E14\u7248\u672C\u5339\u914D`,
|
|
7897
8440
|
suggestedCommand: !content || !managed || !versionMatches ? "fet init" : void 0
|
|
7898
8441
|
});
|
|
7899
8442
|
}
|