abelworkflow 1.0.0-rc.1 → 1.1.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.
Files changed (31) hide show
  1. package/README.md +7 -8
  2. package/extensions/pi-gpt-responses-compat/index.ts +1 -1
  3. package/lib/cli/args.mjs +2 -2
  4. package/lib/cli/main.mjs +39 -41
  5. package/lib/cli/prompts.mjs +16 -29
  6. package/lib/installer/assets.mjs +13 -15
  7. package/lib/installer/install.mjs +18 -32
  8. package/lib/installer/state.mjs +0 -19
  9. package/lib/providers/claude.mjs +23 -214
  10. package/lib/providers/codex.mjs +14 -22
  11. package/lib/providers/pi.mjs +273 -58
  12. package/lib/providers/skills.mjs +39 -88
  13. package/lib/templates/workflow/AGENTS.md +10 -10
  14. package/lib/templates/workflow/commands/abel-design.md +175 -0
  15. package/lib/templates/workflow/commands/abel-implement.md +64 -46
  16. package/lib/templates/workflow/commands/abel-init.md +2 -4
  17. package/package.json +1 -7
  18. package/skills/grok-search/.env.example +3 -3
  19. package/skills/grok-search/SKILL.md +1 -1
  20. package/skills/grok-search/defaults.json +1 -1
  21. package/skills/grok-search/scripts/groksearch_cli.py +42 -15
  22. package/skills/prompt-enhancer/SKILL.md +2 -27
  23. package/lib/installer/render.mjs +0 -46
  24. package/lib/templates/workflow/commands/abel-plan.md +0 -82
  25. package/lib/templates/workflow/commands/abel-research.md +0 -126
  26. package/skills/prompt-enhancer/.env.example +0 -12
  27. package/skills/prompt-enhancer/ADVANCED.md +0 -103
  28. package/skills/prompt-enhancer/requirements.txt +0 -1
  29. package/skills/prompt-enhancer/scripts/_dotenv.py +0 -32
  30. package/skills/prompt-enhancer/scripts/enhance.py +0 -144
  31. package/skills/prompt-enhancer/scripts/prompt_enhancer_entry.py +0 -238
package/README.md CHANGED
@@ -13,19 +13,18 @@ AbelWorkflow 为 Codex、Claude Code 和 Pi 部署统一的 Skills、工作流
13
13
  | 命令 | 用途 |
14
14
  | --- | --- |
15
15
  | `/abel-init` | 初始化 OpenSpec 环境并验证工具链 |
16
- | `/abel-research` | 研究需求并生成约束,不实施代码 |
17
- | `/abel-plan` | 将已确认变更细化为可执行计划 |
16
+ | `/abel-design` | 通过双闸门澄清生成可实施、可验证规格 |
18
17
  | `/abel-implement` | 按强制 TDD 实施已批准变更 |
19
18
  | `/abel-diagnose` | 进行根因分析、回归测试和批量修复 |
20
19
 
21
20
  工作流:
22
21
 
23
22
  ```text
24
- /abel-init → /abel-research → /abel-plan → /abel-implement(TDD)
25
- ↘ /abel-diagnose (bug fix)
23
+ /abel-init → /abel-design → /abel-implement(TDD)
24
+ ↘ /abel-diagnose (bug fix)
26
25
  ```
27
26
 
28
- OpenSpec 命令保持独立:`/opsx:new`、`/opsx:ff`、`/opsx:archive`、`openspec view`、`openspec status`。
27
+ OpenSpec 命令保持独立:`/opsx:propose`、`/opsx:explore`、`/opsx:apply`、`/opsx:update`、`/opsx:sync`、`/opsx:archive`、`openspec view`、`openspec status`。
29
28
 
30
29
  ## 安装与更新
31
30
 
@@ -62,10 +61,10 @@ node .\bin\abelworkflow.mjs install --agents-dir "$HOME\.agents"
62
61
 
63
62
  ## 配置行为
64
63
 
65
- - Claude 默认使用 `standard` 权限档,不预授权 Bash、Write Edit;只有用户明确选择 `trusted` 才增加宽权限。已有 permissions、deny、hooks 和 timeout 保持不变。
64
+ - Claude 配置默认写入 `bypassPermissions` 全权限模式(YOLO),第三方 API 仅使用 API Key 认证,不添加任何 MCP 权限。已有 permissions、deny、hooks 和 timeout 保持不变。
66
65
  - Codex 只更新 AbelWorkflow 管理的认证字段,保留未知字段、其他 Provider 和用户 token。
67
- - Pi 0.80.0 及以上版本将 GPT API Key 保存在 `~/.pi/agent/auth.json`;`models.json` 只保留模型定义。旧 models-only key 会按先写 auth、后删除旧 key 的顺序迁移。
68
- - Grok 默认模型统一为 `grok-4.20-auto`。
66
+ - Pi 0.80.0 及以上版本按无会话启动时实际选中的当前有效 Provider 配置 API,并将其 API Key 保存在 `~/.pi/agent/auth.json`;`models.json` 只保留模型定义。旧 models-only key 会按先写 auth、后删除旧 key 的顺序迁移。
67
+ - Grok 默认模型统一为 `grok-4.20-non-reasoning`。
69
68
  - Context7 使用显式 CommonJS 入口 `context7-api.cjs`。
70
69
  - dev-browser 发布运行时使用 Node ESM 编译产物,入口为 `node dist/scripts/start.js`,不依赖 Bun 或运行时 `npx tsx`。
71
70
  - skill 密钥写入 `~/.agents/skills/<skill>/.env`;这些文件属于私密配置。
@@ -21,7 +21,7 @@ export default function (pi: ExtensionAPI) {
21
21
  pi.on("before_provider_request", (event, ctx) => {
22
22
  const payload = event.payload as any;
23
23
  if (!payload || typeof payload !== "object" || Array.isArray(payload)) return;
24
- if (ctx.model?.provider !== "gpt") return;
24
+ if (ctx.model?.api !== "openai-responses") return;
25
25
  if (!("input" in payload)) return;
26
26
 
27
27
  const input = Array.isArray(payload.input) ? payload.input : [];
package/lib/cli/args.mjs CHANGED
@@ -1,4 +1,4 @@
1
- function parseArgs(argv, { defaultAgentsDir, resolvePath }) {
1
+ function parseArgs(argv, { defaultAgentsDir, resolvePath, env = process.env }) {
2
2
  const options = {
3
3
  agentsDir: defaultAgentsDir,
4
4
  force: false,
@@ -60,7 +60,7 @@ function parseArgs(argv, { defaultAgentsDir, resolvePath }) {
60
60
  if (options.command !== "install" && (options.force || options.relinkOnly || options.agentsDir !== defaultAgentsDir)) {
61
61
  throw new Error("`--force`、`--link-only`、`--agents-dir` 仅能与 `install` 命令一起使用");
62
62
  }
63
- if (!options.nonInteractive && process.env.CI) options.nonInteractive = true;
63
+ if (!options.nonInteractive && env.CI) options.nonInteractive = true;
64
64
  return options;
65
65
  }
66
66
 
package/lib/cli/main.mjs CHANGED
@@ -6,28 +6,23 @@ import { assertInteractiveMenuSupported, parseArgs } from "./args.mjs";
6
6
  import * as promptApi from "./prompts.mjs";
7
7
  import {
8
8
  ensurePiResourcesLinked,
9
- ensureWorkflowPresent,
9
+ ensureSkillPresent,
10
10
  installManagedWorkflow,
11
11
  packageVersionFor
12
12
  } from "../installer/install.mjs";
13
13
  import {
14
14
  finalizeProviderInstallMetadata,
15
- getPreviousClaudePermissionProfile,
16
15
  getPreviousManagedCodexAgentFiles,
17
16
  getPreviousManagedCodexAuthKeys,
18
17
  readInstallMetadata,
19
18
  writeInstallMetadata
20
19
  } from "../installer/state.mjs";
21
- import {
22
- configureClaudeApi,
23
- getPreviousManagedClaudePermissions
24
- } from "../providers/claude.mjs";
20
+ import { configureClaudeApi } from "../providers/claude.mjs";
25
21
  import { configureCodexApi } from "../providers/codex.mjs";
26
22
  import { configurePiApi } from "../providers/pi.mjs";
27
23
  import {
28
24
  configureContext7Env,
29
- configureGrokSearchEnv,
30
- configurePromptEnhancerEnv
25
+ configureGrokSearchEnv
31
26
  } from "../providers/skills.mjs";
32
27
  import { commandExists, installCliTool } from "../tools/cli-installer.mjs";
33
28
  import { createPaths, defaultPaths, pathToLabel } from "../paths.mjs";
@@ -36,11 +31,8 @@ const {
36
31
  buildCliToolMenuDescriptors,
37
32
  CancelledError,
38
33
  confirmOrCancel,
39
- getClaudePermissionProfilePromptOptions,
40
- getAugmentContextEnginePromptOptions,
41
34
  interactiveMenuDefaultValue,
42
- interactiveMenuDescriptors,
43
- selectOrCancel
35
+ interactiveMenuDescriptors
44
36
  } = promptApi;
45
37
 
46
38
  const installReportSummaryFields = [
@@ -66,6 +58,19 @@ function presentInstallReport(report, reportOutput) {
66
58
  .map(([label, field]) => `${label} ${pathsByField[field].length}`)
67
59
  .join(",")}`);
68
60
 
61
+ for (const [message, field] of [
62
+ ["已更新", "updated"],
63
+ ["已跳过", "unchanged"]
64
+ ]) {
65
+ const paths = pathsByField[field];
66
+ if (paths.length > 0) {
67
+ reportOutput.message([
68
+ `${message} ${paths.length} 个${field === "unchanged" ? "未变化" : ""}文件:`,
69
+ ...paths.map((path) => `- ${path}`)
70
+ ].join("\n"));
71
+ }
72
+ }
73
+
69
74
  const conflictPaths = pathsByField.conflicts;
70
75
  const conflictSet = new Set(conflictPaths);
71
76
  const preservedPaths = pathsByField.preserved.filter((path) => !conflictSet.has(path));
@@ -90,6 +95,18 @@ async function runManagedInstall(options) {
90
95
  return report;
91
96
  }
92
97
 
98
+ async function ensureSkillPresentWithReport(paths, skillName) {
99
+ const report = await ensureSkillPresent(paths, skillName);
100
+ presentInstallReport(report, p.log);
101
+ return report;
102
+ }
103
+
104
+ async function ensurePiResourcesLinkedWithReport(paths) {
105
+ const report = await ensurePiResourcesLinked(paths);
106
+ presentInstallReport(report, p.log);
107
+ return report;
108
+ }
109
+
93
110
  function printHelp() {
94
111
  console.log(`${c.bold("AbelWorkflow")} ${c.cyan("installer")}
95
112
 
@@ -129,33 +146,18 @@ async function configureCodexForPaths(paths) {
129
146
  async function configureClaudeForPaths(paths) {
130
147
  const metadata = await readInstallMetadata(paths);
131
148
  const packageVersion = await packageVersionFor(paths, metadata);
132
- const permissionProfile = await selectOrCancel(getClaudePermissionProfilePromptOptions(
133
- getPreviousClaudePermissionProfile(metadata)
134
- ));
135
- const result = await configureClaudeApi(paths, promptApi, {
136
- permissionProfile,
137
- previousManagedPermissions: getPreviousManagedClaudePermissions(metadata)
138
- });
149
+ await configureClaudeApi(paths, promptApi);
139
150
  await writeInstallMetadata(paths, finalizeProviderInstallMetadata({
140
151
  previousMetadata: metadata,
141
- packageVersion,
142
- overrides: {
143
- claudePermissionProfile: result.permissionProfile,
144
- managedClaudePermissions: result.managedPermissions
145
- }
152
+ packageVersion
146
153
  }));
147
154
  }
148
155
 
149
156
  async function runFullInit(options) {
150
- const augmentContextEngine = options.nonInteractive
151
- ? false
152
- : await confirmOrCancel(getAugmentContextEnginePromptOptions());
153
-
154
157
  const installReport = await runManagedInstall({
155
158
  paths: options.paths,
156
159
  force: options.force,
157
- relinkOnly: false,
158
- featureOverrides: { augmentContextEngine }
160
+ relinkOnly: false
159
161
  });
160
162
 
161
163
  if (await confirmOrCancel({ message: "是否安装或更新 Claude Code CLI?", initialValue: false })) {
@@ -173,17 +175,14 @@ async function runFullInit(options) {
173
175
  if (await confirmOrCancel({ message: "是否安装或更新 Pi CLI?", initialValue: false })) {
174
176
  await installCliTool("pi", promptApi);
175
177
  }
176
- if (await confirmOrCancel({ message: "是否配置 Pi gpt 自定义 API?", initialValue: commandExists("pi") })) {
178
+ if (await confirmOrCancel({ message: "是否配置 Pi 当前有效 Provider API?", initialValue: commandExists("pi") })) {
177
179
  await configurePiApi(options.paths, ensurePiResourcesLinked, promptApi);
178
180
  }
179
181
  if (await confirmOrCancel({ message: "是否填写 grok-search 环境变量?", initialValue: false })) {
180
- await configureGrokSearchEnv(options.paths, ensureWorkflowPresent, promptApi);
182
+ await configureGrokSearchEnv(options.paths, (paths) => ensureSkillPresent(paths, "grok-search"), promptApi);
181
183
  }
182
184
  if (await confirmOrCancel({ message: "是否填写 context7-auto-research 环境变量?", initialValue: false })) {
183
- await configureContext7Env(options.paths, ensureWorkflowPresent, promptApi);
184
- }
185
- if (await confirmOrCancel({ message: "是否填写 prompt-enhancer 环境变量?", initialValue: false })) {
186
- await configurePromptEnhancerEnv(options.paths, ensureWorkflowPresent, promptApi);
185
+ await configureContext7Env(options.paths, (paths) => ensureSkillPresent(paths, "context7-auto-research"), promptApi);
187
186
  }
188
187
 
189
188
  const conflictCount = uniqueReportPaths(installReport.conflicts).length;
@@ -211,7 +210,7 @@ async function runInteractiveMenu(options) {
211
210
  title: "Pi",
212
211
  actions: {
213
212
  "pi-install": async () => installCliTool("pi", promptApi),
214
- "pi-api": async () => configurePiApi(options.paths, ensurePiResourcesLinked, promptApi)
213
+ "pi-api": async () => configurePiApi(options.paths, ensurePiResourcesLinkedWithReport, promptApi)
215
214
  }
216
215
  },
217
216
  "codex-cli": {
@@ -236,7 +235,7 @@ async function runInteractiveMenu(options) {
236
235
  const choice = await p.select({
237
236
  message: `请选择 ${title} 操作`,
238
237
  options: buildCliToolMenuDescriptors(tool).map(buildOption),
239
- initialValue: `${tool}-install`
238
+ initialValue: `${tool}-api`
240
239
  });
241
240
 
242
241
  if (p.isCancel(choice) || choice === "back") {
@@ -258,9 +257,8 @@ async function runInteractiveMenu(options) {
258
257
  force: options.force,
259
258
  relinkOnly: options.relinkOnly
260
259
  }),
261
- "grok-search": async () => configureGrokSearchEnv(options.paths, ensureWorkflowPresent, promptApi),
262
- context7: async () => configureContext7Env(options.paths, ensureWorkflowPresent, promptApi),
263
- "prompt-enhancer": async () => configurePromptEnhancerEnv(options.paths, ensureWorkflowPresent, promptApi),
260
+ "grok-search": async () => configureGrokSearchEnv(options.paths, (paths) => ensureSkillPresentWithReport(paths, "grok-search"), promptApi),
261
+ context7: async () => configureContext7Env(options.paths, (paths) => ensureSkillPresentWithReport(paths, "context7-auto-research"), promptApi),
264
262
  "pi-cli": async () => runCliToolMenu(cliToolMenus["pi-cli"]),
265
263
  "codex-cli": async () => runCliToolMenu(cliToolMenus["codex-cli"]),
266
264
  "claude-cli": async () => runCliToolMenu(cliToolMenus["claude-cli"])
@@ -5,35 +5,29 @@ const interactiveMenuDescriptors = [
5
5
  { value: "install", label: "仅同步工作流", group: "main" },
6
6
  { value: "grok-search", label: "配置 grok-search", hint: "技能", group: "skill" },
7
7
  { value: "context7", label: "配置 context7-auto-research", hint: "技能", group: "skill" },
8
- { value: "prompt-enhancer", label: "配置 prompt-enhancer", hint: "技能", group: "skill" },
9
- { value: "pi-cli", label: "安装/配置 Pi", hint: "CLI", group: "cli" },
10
- { value: "codex-cli", label: "安装/配置 Codex", hint: "CLI", group: "cli" },
11
- { value: "claude-cli", label: "安装/配置 Claude Code", hint: "CLI", group: "cli" },
8
+ { value: "pi-cli", label: "配置/安装 Pi", hint: "CLI", group: "cli" },
9
+ { value: "codex-cli", label: "配置/安装 Codex", hint: "CLI", group: "cli" },
10
+ { value: "claude-cli", label: "配置/安装 Claude Code", hint: "CLI", group: "cli" },
12
11
  { value: "exit", label: "退出", group: "exit" }
13
12
  ];
14
13
  const interactiveMenuDefaultValue = "full-init";
15
14
  const cliToolMenuDescriptorMap = {
16
15
  pi: [
17
- { value: "pi-install", label: "安装/更新 Pi" },
18
16
  { value: "pi-api", label: "配置 Pi API" },
17
+ { value: "pi-install", label: "安装/更新 Pi" },
19
18
  { value: "back", label: "返回上一级" }
20
19
  ],
21
20
  codex: [
22
- { value: "codex-install", label: "安装/更新 Codex" },
23
21
  { value: "codex-api", label: "配置 Codex API" },
22
+ { value: "codex-install", label: "安装/更新 Codex" },
24
23
  { value: "back", label: "返回上一级" }
25
24
  ],
26
25
  claude: [
27
- { value: "claude-install", label: "安装/更新 Claude Code" },
28
26
  { value: "claude-api", label: "配置 Claude Code API" },
27
+ { value: "claude-install", label: "安装/更新 Claude Code" },
29
28
  { value: "back", label: "返回上一级" }
30
29
  ]
31
30
  };
32
- const augmentContextEngineFeaturePrompt = {
33
- message: "是否启用 augment-context-engine MCP 代码检索支持?不确定建议选否,可减少 MCP 安装和配置麻烦。",
34
- initialValue: false
35
- };
36
-
37
31
  class CancelledError extends Error {
38
32
  constructor(message = "用户取消") {
39
33
  super(message);
@@ -72,6 +66,15 @@ function resolvePasswordValue(userInput, existingValue) {
72
66
  return existingValue || undefined;
73
67
  }
74
68
 
69
+ function passwordPromptOptions(message, existingValue, validate) {
70
+ const configured = typeof existingValue === "string" && existingValue.trim() !== "";
71
+ return {
72
+ message: `${message}${configured ? "(已配置,直接回车保留;输入 - 清除)" : "(输入 - 清除)"}`,
73
+ mask: "*",
74
+ ...(validate ? { validate } : {})
75
+ };
76
+ }
77
+
75
78
  function assertNotCancelled(value) {
76
79
  if (p.isCancel(value)) throw new CancelledError();
77
80
  }
@@ -88,30 +91,14 @@ async function selectOrCancel(options) {
88
91
  return value;
89
92
  }
90
93
 
91
- function getAugmentContextEnginePromptOptions() {
92
- return { ...augmentContextEngineFeaturePrompt };
93
- }
94
-
95
- function getClaudePermissionProfilePromptOptions(initialValue = "standard") {
96
- return {
97
- message: "请选择 Claude Code 权限配置档",
98
- options: [
99
- { value: "standard", label: "standard(默认,不预授权 Bash/Write/Edit)" },
100
- { value: "trusted", label: "trusted(显式授予完整工具权限)" }
101
- ],
102
- initialValue: initialValue === "trusted" ? "trusted" : "standard"
103
- };
104
- }
105
-
106
94
  export {
107
95
  assertNotCancelled,
108
96
  buildCliToolMenuDescriptors,
109
97
  CancelledError,
110
98
  confirmOrCancel,
111
- getClaudePermissionProfilePromptOptions,
112
- getAugmentContextEnginePromptOptions,
113
99
  interactiveMenuDefaultValue,
114
100
  interactiveMenuDescriptors,
101
+ passwordPromptOptions,
115
102
  required,
116
103
  requiredUnlessExisting,
117
104
  resolvePasswordValue,
@@ -8,7 +8,6 @@ import {
8
8
  readJsonFileSafe,
9
9
  writeText
10
10
  } from "../config/store.mjs";
11
- import { renderWorkflowTemplate } from "./render.mjs";
12
11
  import { createInstallReport, readInstallMetadata } from "./state.mjs";
13
12
 
14
13
  const ignoredSkillPathPatterns = [
@@ -81,7 +80,7 @@ function shouldCopySkillPath(skillsRoot, sourcePath) {
81
80
  return !ignoredSkillPathPatterns.some((pattern) => pattern.test(relativePath));
82
81
  }
83
82
 
84
- async function collectFiles(root, outputPrefix, { filter, mapTargetPath, render } = {}) {
83
+ async function collectFiles(root, outputPrefix, { filter, mapTargetPath } = {}) {
85
84
  const assets = new Map();
86
85
  async function visit(directory) {
87
86
  const entries = await fs.readdir(directory, { withFileTypes: true });
@@ -97,17 +96,14 @@ async function collectFiles(root, outputPrefix, { filter, mapTargetPath, render
97
96
  const sourceRelativePath = normalizeRelativePath(relative(root, sourcePath));
98
97
  if (sourceRelativePath.endsWith(".skill-lock.json")) continue;
99
98
  const sourceContent = await fs.readFile(sourcePath);
100
- const renderedContent = render
101
- ? Buffer.from(render(sourceContent.toString("utf8"), sourceRelativePath))
102
- : sourceContent;
103
99
  const sourceStat = await fs.stat(sourcePath);
104
100
  const targetRelativePath = normalizeRelativePath(join(
105
101
  outputPrefix,
106
102
  mapTargetPath ? mapTargetPath(sourceRelativePath) : sourceRelativePath
107
103
  ));
108
104
  assets.set(targetRelativePath, {
109
- content: renderedContent,
110
- hash: hashBytes(renderedContent),
105
+ content: sourceContent,
106
+ hash: hashBytes(sourceContent),
111
107
  sourceHash: hashBytes(sourceContent),
112
108
  mode: sourceStat.mode & 0o777,
113
109
  sourcePath
@@ -126,12 +122,11 @@ async function addAssetTree(manifest, root, outputPrefix, options = {}) {
126
122
  for (const [key, value] of await collectFiles(root, outputPrefix, options)) manifest.set(key, value);
127
123
  }
128
124
 
129
- async function collectManagedAssets(paths, featureState) {
125
+ async function collectManagedAssets(paths) {
130
126
  const manifest = new Map();
131
127
  await addAssetTree(manifest, paths.workflowTemplateRoot, "", {
132
128
  required: true,
133
- mapTargetPath: mapGitignoreTemplate,
134
- render: (content) => renderWorkflowTemplate(content, featureState)
129
+ mapTargetPath: mapGitignoreTemplate
135
130
  });
136
131
 
137
132
  const readmePath = join(paths.packageRoot, "README.md");
@@ -258,16 +253,17 @@ async function readPackageVersion(paths) {
258
253
 
259
254
  async function syncManagedFiles({
260
255
  paths,
261
- featureState = {},
262
- force = false
256
+ force = false,
257
+ pathPrefix = null
263
258
  }) {
264
259
  const previousMetadata = await readInstallMetadata(paths);
265
260
  const previousManagedFiles = await readManagedFiles(paths, previousMetadata);
266
- const manifest = await collectManagedAssets(paths, featureState);
261
+ const manifest = await collectManagedAssets(paths);
267
262
  const report = createInstallReport();
268
- const managedFiles = {};
263
+ const managedFiles = pathPrefix ? { ...previousManagedFiles } : {};
269
264
 
270
265
  for (const [relativePath, asset] of manifest) {
266
+ if (pathPrefix && !relativePath.startsWith(pathPrefix)) continue;
271
267
  const managedHash = await applyManifestEntry(
272
268
  paths,
273
269
  relativePath,
@@ -277,18 +273,20 @@ async function syncManagedFiles({
277
273
  report
278
274
  );
279
275
  if (managedHash) managedFiles[relativePath] = managedHash;
276
+ else delete managedFiles[relativePath];
280
277
  }
281
278
  for (const [relativePath, previousHash] of Object.entries(previousManagedFiles)) {
279
+ if (pathPrefix && !relativePath.startsWith(pathPrefix)) continue;
282
280
  if (!manifest.has(relativePath)) {
283
281
  const managedHash = await removeStaleManagedFile(paths, relativePath, previousHash, force, report);
284
282
  if (managedHash) managedFiles[relativePath] = managedHash;
283
+ else delete managedFiles[relativePath];
285
284
  }
286
285
  }
287
286
 
288
287
  return {
289
288
  previousMetadata,
290
289
  packageVersion: await readPackageVersion(paths),
291
- features: featureState,
292
290
  managedFiles,
293
291
  report
294
292
  };
@@ -1,13 +1,8 @@
1
1
  import { join } from "node:path";
2
2
  import { pathExists, readJsonFileSafe } from "../config/store.mjs";
3
3
  import { createPaths, defaultPaths } from "../paths.mjs";
4
- import {
5
- ensureClaudeSettingsForFeature,
6
- getPreviousManagedClaudePermissions
7
- } from "../providers/claude.mjs";
8
4
  import { readManagedFiles, syncManagedFiles } from "./assets.mjs";
9
5
  import { linkClaude, linkCodex, linkPi } from "./links.mjs";
10
- import { resolveAugmentContextEngineFeature } from "./render.mjs";
11
6
  import {
12
7
  buildInstallMetadata,
13
8
  createInstallReport,
@@ -41,18 +36,6 @@ function resolveInstallPaths(options = {}) {
41
36
  });
42
37
  }
43
38
 
44
- function resolveFeatureState(options, previousMetadata) {
45
- const overrides = {
46
- ...(options.featureOverrides ?? {})
47
- };
48
- return {
49
- ...(previousMetadata.features && typeof previousMetadata.features === "object"
50
- ? previousMetadata.features
51
- : {}),
52
- augmentContextEngine: resolveAugmentContextEngineFeature(overrides, previousMetadata)
53
- };
54
- }
55
-
56
39
  function reportFromLinkResults(results) {
57
40
  const report = createInstallReport();
58
41
  for (const result of results) {
@@ -86,7 +69,6 @@ async function packageVersionFor(paths, previousMetadata) {
86
69
  async function installWorkflow(options) {
87
70
  const paths = validatedPaths(options.paths);
88
71
  const previousMetadata = await readInstallMetadata(paths);
89
- const featureState = resolveFeatureState(options, previousMetadata);
90
72
  let assetResult;
91
73
 
92
74
  if (options.relinkOnly) {
@@ -96,14 +78,12 @@ async function installWorkflow(options) {
96
78
  assetResult = {
97
79
  previousMetadata,
98
80
  packageVersion: await packageVersionFor(paths, previousMetadata),
99
- features: featureState,
100
81
  managedFiles: await readManagedFiles(paths, previousMetadata),
101
82
  report: createInstallReport()
102
83
  };
103
84
  } else {
104
85
  assetResult = await syncManagedFiles({
105
86
  paths,
106
- featureState,
107
87
  force: options.force ?? false
108
88
  });
109
89
  }
@@ -114,16 +94,12 @@ async function installWorkflow(options) {
114
94
  const codexResults = await linkCodex(paths, previousLinkedTargets, linkOptions);
115
95
  const piResults = await linkPi(paths, previousLinkedTargets, linkOptions);
116
96
  const linkResults = [...claudeResults, ...codexResults, ...piResults];
117
- const ensureClaudeSettings = options.ensureClaudeSettings ?? ensureClaudeSettingsForFeature;
118
- const managedClaudePermissions = await ensureClaudeSettings(paths, featureState.augmentContextEngine, previousMetadata);
119
97
  const linkedTargets = mergeLinkedTargets(previousLinkedTargets, linkResults);
120
98
 
121
99
  const metadata = buildInstallMetadata({
122
100
  previousMetadata,
123
101
  packageVersion: assetResult.packageVersion,
124
- features: featureState,
125
102
  managedFiles: assetResult.managedFiles,
126
- managedClaudePermissions,
127
103
  linkedTargets
128
104
  });
129
105
  await writeInstallMetadata(paths, metadata);
@@ -134,33 +110,43 @@ async function installManagedWorkflow(options = {}) {
134
110
  return installWorkflow({ ...options, paths: resolveInstallPaths(options) });
135
111
  }
136
112
 
137
- async function ensureWorkflowPresent(inputPaths) {
113
+ async function ensureSkillPresent(inputPaths, skillName) {
138
114
  const paths = validatedPaths(inputPaths);
139
- return installWorkflow({ paths, force: false, relinkOnly: false });
115
+ const previousMetadata = await readInstallMetadata(paths);
116
+ const assetResult = await syncManagedFiles({
117
+ paths,
118
+ force: false,
119
+ pathPrefix: `skills/${skillName}/`
120
+ });
121
+ const metadata = buildInstallMetadata({
122
+ previousMetadata,
123
+ packageVersion: assetResult.packageVersion,
124
+ managedFiles: assetResult.managedFiles,
125
+ linkedTargets: previousMetadata.linkedTargets ?? {}
126
+ });
127
+ await writeInstallMetadata(paths, metadata);
128
+ return assetResult.report;
140
129
  }
141
130
 
142
131
  async function ensurePiResourcesLinked(inputPaths) {
143
132
  const paths = validatedPaths(inputPaths);
144
133
  const previousMetadata = await readInstallMetadata(paths);
145
- const featureState = resolveFeatureState({}, previousMetadata);
146
- const assetResult = await syncManagedFiles({ paths, featureState, force: false });
134
+ const assetResult = await syncManagedFiles({ paths, force: false });
147
135
  const previousLinkedTargets = previousMetadata.linkedTargets ?? {};
148
136
  const piResults = await linkPi(paths, previousLinkedTargets, { force: false });
149
137
  const metadata = buildInstallMetadata({
150
138
  previousMetadata,
151
139
  packageVersion: assetResult.packageVersion,
152
- features: featureState,
153
140
  managedFiles: assetResult.managedFiles,
154
- managedClaudePermissions: getPreviousManagedClaudePermissions(previousMetadata),
155
141
  linkedTargets: mergeLinkedTargets(previousLinkedTargets, piResults)
156
142
  });
157
143
  await writeInstallMetadata(paths, metadata);
158
- return piResults;
144
+ return mergeInstallReports(assetResult.report, reportFromLinkResults(piResults));
159
145
  }
160
146
 
161
147
  export {
162
148
  ensurePiResourcesLinked,
163
- ensureWorkflowPresent,
149
+ ensureSkillPresent,
164
150
  installManagedWorkflow,
165
151
  installWorkflow,
166
152
  packageVersionFor
@@ -43,12 +43,6 @@ function getPreviousManagedCodexAgentFiles(previousMetadata = {}) {
43
43
  return normalizeManagedCodexAgentFiles(previousMetadata.managedCodexAgentFiles);
44
44
  }
45
45
 
46
- function getPreviousClaudePermissionProfile(previousMetadata = {}) {
47
- return isMetadataV2(previousMetadata) && previousMetadata.claudePermissionProfile === "trusted"
48
- ? "trusted"
49
- : "standard";
50
- }
51
-
52
46
  async function readInstallMetadata(paths) {
53
47
  const metadata = await readJsonFileSafe(metadataPathFor(paths), {});
54
48
  if (!metadata || typeof metadata !== "object" || Array.isArray(metadata)) {
@@ -64,23 +58,17 @@ async function writeInstallMetadata(paths, metadata) {
64
58
  function buildInstallMetadata({
65
59
  previousMetadata = {},
66
60
  packageVersion,
67
- features = {},
68
61
  managedFiles = {},
69
- managedClaudePermissions = [],
70
62
  managedCodexAuthKeys = getPreviousManagedCodexAuthKeys(previousMetadata),
71
63
  managedCodexAgentFiles = getPreviousManagedCodexAgentFiles(previousMetadata),
72
- claudePermissionProfile = getPreviousClaudePermissionProfile(previousMetadata),
73
64
  linkedTargets = {}
74
65
  }) {
75
66
  const metadata = {
76
67
  schemaVersion: 2,
77
68
  packageVersion,
78
- features: sortObject(features),
79
69
  managedFiles: sortObject(managedFiles),
80
- managedClaudePermissions: [...new Set(managedClaudePermissions)].sort(),
81
70
  managedCodexAuthKeys: [...new Set(managedCodexAuthKeys)].sort(),
82
71
  managedCodexAgentFiles: normalizeManagedCodexAgentFiles(managedCodexAgentFiles),
83
- claudePermissionProfile,
84
72
  linkedTargets: sortObject(linkedTargets)
85
73
  };
86
74
  if (typeof previousMetadata.installedAt === "string") {
@@ -102,18 +90,12 @@ function finalizeProviderInstallMetadata({
102
90
  && previousMetadata.packageVersion
103
91
  ? previousMetadata.packageVersion
104
92
  : packageVersion,
105
- features: objectField(previousMetadata.features),
106
93
  managedFiles: isV2 ? objectField(previousMetadata.managedFiles) : {},
107
- managedClaudePermissions: stringArray(
108
- overrides.managedClaudePermissions ?? previousMetadata.managedClaudePermissions
109
- ),
110
94
  managedCodexAuthKeys: stringArray(
111
95
  overrides.managedCodexAuthKeys ?? getPreviousManagedCodexAuthKeys(previousMetadata)
112
96
  ),
113
97
  managedCodexAgentFiles: overrides.managedCodexAgentFiles
114
98
  ?? getPreviousManagedCodexAgentFiles(previousMetadata),
115
- claudePermissionProfile: overrides.claudePermissionProfile
116
- ?? getPreviousClaudePermissionProfile(previousMetadata),
117
99
  linkedTargets: objectField(previousMetadata.linkedTargets)
118
100
  });
119
101
  }
@@ -170,7 +152,6 @@ export {
170
152
  buildInstallMetadata,
171
153
  createInstallReport,
172
154
  finalizeProviderInstallMetadata,
173
- getPreviousClaudePermissionProfile,
174
155
  getPreviousManagedCodexAgentFiles,
175
156
  getPreviousManagedCodexAuthKeys,
176
157
  linkedTargetsFromResults,