@birdie_moblie/open_spec 2.1.7

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 (186) hide show
  1. package/.agents/plugins/marketplace.json +13 -0
  2. package/.claude-plugin/marketplace.json +12 -0
  3. package/.claude-plugin/plugin.json +9 -0
  4. package/.codex-plugin/plugin.json +9 -0
  5. package/LICENSE +21 -0
  6. package/README.md +111 -0
  7. package/bin/openspec.js +3 -0
  8. package/dist/cli/commands/change.d.ts +3 -0
  9. package/dist/cli/commands/change.js +37 -0
  10. package/dist/cli/commands/ext.d.ts +3 -0
  11. package/dist/cli/commands/ext.js +196 -0
  12. package/dist/cli/commands/figma.d.ts +3 -0
  13. package/dist/cli/commands/figma.js +73 -0
  14. package/dist/cli/commands/init.d.ts +3 -0
  15. package/dist/cli/commands/init.js +71 -0
  16. package/dist/cli/commands/opsx.d.ts +3 -0
  17. package/dist/cli/commands/opsx.js +324 -0
  18. package/dist/cli/commands/usage.d.ts +3 -0
  19. package/dist/cli/commands/usage.js +21 -0
  20. package/dist/cli/output.d.ts +8 -0
  21. package/dist/cli/output.js +17 -0
  22. package/dist/cli/program.d.ts +4 -0
  23. package/dist/cli/program.js +42 -0
  24. package/dist/cli/register.d.ts +3 -0
  25. package/dist/cli/register.js +13 -0
  26. package/dist/cli.d.ts +2 -0
  27. package/dist/cli.js +3 -0
  28. package/dist/config/load.d.ts +7 -0
  29. package/dist/config/load.js +55 -0
  30. package/dist/config/presets.d.ts +129 -0
  31. package/dist/config/presets.js +125 -0
  32. package/dist/config/schema.d.ts +281 -0
  33. package/dist/config/schema.js +178 -0
  34. package/dist/core/archive.d.ts +5 -0
  35. package/dist/core/archive.js +33 -0
  36. package/dist/core/change.d.ts +12 -0
  37. package/dist/core/change.js +69 -0
  38. package/dist/core/code-changes.d.ts +9 -0
  39. package/dist/core/code-changes.js +31 -0
  40. package/dist/core/flow.d.ts +6 -0
  41. package/dist/core/flow.js +99 -0
  42. package/dist/core/gates.d.ts +5 -0
  43. package/dist/core/gates.js +71 -0
  44. package/dist/core/instructions.d.ts +21 -0
  45. package/dist/core/instructions.js +102 -0
  46. package/dist/core/source-operations.d.ts +25 -0
  47. package/dist/core/source-operations.js +94 -0
  48. package/dist/core/sources.d.ts +33 -0
  49. package/dist/core/sources.js +234 -0
  50. package/dist/core/state.d.ts +15 -0
  51. package/dist/core/state.js +75 -0
  52. package/dist/core/status.d.ts +31 -0
  53. package/dist/core/status.js +22 -0
  54. package/dist/core/tasks.d.ts +15 -0
  55. package/dist/core/tasks.js +143 -0
  56. package/dist/core/todo.d.ts +31 -0
  57. package/dist/core/todo.js +114 -0
  58. package/dist/core/types.d.ts +297 -0
  59. package/dist/core/types.js +227 -0
  60. package/dist/core/verify.d.ts +229 -0
  61. package/dist/core/verify.js +391 -0
  62. package/dist/core/worker-evidence.d.ts +71 -0
  63. package/dist/core/worker-evidence.js +253 -0
  64. package/dist/core/worker.d.ts +22 -0
  65. package/dist/core/worker.js +478 -0
  66. package/dist/delivery/claude-install.d.ts +4 -0
  67. package/dist/delivery/claude-install.js +44 -0
  68. package/dist/delivery/distribution.d.ts +3 -0
  69. package/dist/delivery/distribution.js +53 -0
  70. package/dist/delivery/skills.d.ts +36 -0
  71. package/dist/delivery/skills.js +178 -0
  72. package/dist/figma/cache.d.ts +19 -0
  73. package/dist/figma/cache.js +115 -0
  74. package/dist/figma/claude.d.ts +14 -0
  75. package/dist/figma/claude.js +121 -0
  76. package/dist/figma/collect.d.ts +3 -0
  77. package/dist/figma/collect.js +57 -0
  78. package/dist/figma/index.d.ts +35 -0
  79. package/dist/figma/index.js +178 -0
  80. package/dist/figma/media.d.ts +5 -0
  81. package/dist/figma/media.js +77 -0
  82. package/dist/figma/response.d.ts +9 -0
  83. package/dist/figma/response.js +27 -0
  84. package/dist/hooks/command-context.d.ts +19 -0
  85. package/dist/hooks/command-context.js +33 -0
  86. package/dist/hooks/engine.d.ts +61 -0
  87. package/dist/hooks/engine.js +314 -0
  88. package/dist/hooks/plugin-runtime.d.ts +188 -0
  89. package/dist/hooks/plugin-runtime.js +198 -0
  90. package/dist/index.d.ts +99 -0
  91. package/dist/index.js +185 -0
  92. package/dist/marketplace/adapters.d.ts +202 -0
  93. package/dist/marketplace/adapters.js +203 -0
  94. package/dist/marketplace/builtin.d.ts +9 -0
  95. package/dist/marketplace/builtin.js +9 -0
  96. package/dist/marketplace/install.d.ts +38 -0
  97. package/dist/marketplace/install.js +108 -0
  98. package/dist/marketplace/registry.d.ts +71 -0
  99. package/dist/marketplace/registry.js +342 -0
  100. package/dist/marketplace/upgrade.d.ts +39 -0
  101. package/dist/marketplace/upgrade.js +104 -0
  102. package/dist/shared/command-log.d.ts +9 -0
  103. package/dist/shared/command-log.js +55 -0
  104. package/dist/shared/errors.d.ts +7 -0
  105. package/dist/shared/errors.js +14 -0
  106. package/dist/shared/exec.d.ts +16 -0
  107. package/dist/shared/exec.js +86 -0
  108. package/dist/shared/fs.d.ts +18 -0
  109. package/dist/shared/fs.js +93 -0
  110. package/dist/shared/git.d.ts +22 -0
  111. package/dist/shared/git.js +132 -0
  112. package/dist/shared/hash.d.ts +5 -0
  113. package/dist/shared/hash.js +29 -0
  114. package/dist/shared/paths.d.ts +16 -0
  115. package/dist/shared/paths.js +97 -0
  116. package/dist/shared/pkg.d.ts +3 -0
  117. package/dist/shared/pkg.js +23 -0
  118. package/dist/shared/version.d.ts +9 -0
  119. package/dist/shared/version.js +34 -0
  120. package/dist/usage/analyze.d.ts +5 -0
  121. package/dist/usage/analyze.js +212 -0
  122. package/dist/usage/claude.d.ts +9 -0
  123. package/dist/usage/claude.js +59 -0
  124. package/dist/usage/collect.d.ts +11 -0
  125. package/dist/usage/collect.js +71 -0
  126. package/dist/usage/doctor.d.ts +72 -0
  127. package/dist/usage/doctor.js +121 -0
  128. package/dist/usage/parse.d.ts +28 -0
  129. package/dist/usage/parse.js +345 -0
  130. package/dist/usage/types.d.ts +103 -0
  131. package/dist/usage/types.js +2 -0
  132. package/docs/getting-started.md +20 -0
  133. package/docs/guides/configuration.md +33 -0
  134. package/docs/guides/cost-benchmark.md +88 -0
  135. package/docs/guides/plugin-migration.md +65 -0
  136. package/docs/guides/smart-figma.md +51 -0
  137. package/docs/guides/source-reading.md +35 -0
  138. package/docs/guides/ui-conventions-template.md +39 -0
  139. package/docs/guides/worker-evidence.md +73 -0
  140. package/docs/guides/workflow.md +74 -0
  141. package/docs/maintainers/architecture.md +27 -0
  142. package/docs/maintainers/plugin-runtime-contract.md +55 -0
  143. package/package.json +78 -0
  144. package/plugin.json +9 -0
  145. package/presets/backend-service.yaml +19 -0
  146. package/presets/flutter-mobile/plugin-hooks.json +68 -0
  147. package/presets/flutter-mobile/ui-conventions.md +122 -0
  148. package/presets/flutter-mobile.yaml +205 -0
  149. package/presets/web-product.yaml +23 -0
  150. package/skills/opsx-apply/SKILL.md +35 -0
  151. package/skills/opsx-apply/nodes/blocked.md +7 -0
  152. package/skills/opsx-apply/nodes/code-task.md +29 -0
  153. package/skills/opsx-apply/nodes/gate-a.md +9 -0
  154. package/skills/opsx-apply/nodes/stop.md +5 -0
  155. package/skills/opsx-apply/nodes/task-build.md +50 -0
  156. package/skills/opsx-apply/nodes/task-repair.md +20 -0
  157. package/skills/opsx-apply/nodes/task-scout.md +28 -0
  158. package/skills/opsx-apply/nodes/task-verify.md +38 -0
  159. package/skills/opsx-archive/SKILL.md +24 -0
  160. package/skills/opsx-archive/nodes/archive.md +15 -0
  161. package/skills/opsx-explore/SKILL.md +20 -0
  162. package/skills/opsx-explore/nodes/explore.md +3 -0
  163. package/skills/opsx-fix-verify/SKILL.md +30 -0
  164. package/skills/opsx-fix-verify/nodes/repair.md +11 -0
  165. package/skills/opsx-fix-verify/nodes/reverify.md +3 -0
  166. package/skills/opsx-fix-verify/nodes/stop.md +3 -0
  167. package/skills/opsx-gate-decision/SKILL.md +20 -0
  168. package/skills/opsx-gate-decision/nodes/decide.md +15 -0
  169. package/skills/opsx-propose/SKILL.md +36 -0
  170. package/skills/opsx-propose/nodes/blocked.md +13 -0
  171. package/skills/opsx-propose/nodes/collect.md +35 -0
  172. package/skills/opsx-propose/nodes/parse.md +72 -0
  173. package/skills/opsx-propose/nodes/plan.md +65 -0
  174. package/skills/opsx-propose/nodes/stop.md +17 -0
  175. package/skills/opsx-propose/nodes/tech.md +77 -0
  176. package/skills/opsx-propose/scripts/read-json-source.mjs +22 -0
  177. package/skills/opsx-resume/SKILL.md +21 -0
  178. package/skills/opsx-resume/nodes/route.md +12 -0
  179. package/skills/opsx-source-update/SKILL.md +23 -0
  180. package/skills/opsx-source-update/nodes/update.md +14 -0
  181. package/skills/opsx-verify/SKILL.md +32 -0
  182. package/skills/opsx-verify/nodes/blocked.md +3 -0
  183. package/skills/opsx-verify/nodes/failed.md +3 -0
  184. package/skills/opsx-verify/nodes/gate-b.md +7 -0
  185. package/skills/opsx-verify/nodes/stop.md +5 -0
  186. package/skills/opsx-verify/nodes/verify.md +10 -0
@@ -0,0 +1,202 @@
1
+ import { z } from 'zod';
2
+ declare const AdapterOperationSchema: z.ZodObject<{
3
+ command: z.ZodString;
4
+ args: z.ZodOptional<z.ZodArray<z.ZodString>>;
5
+ artifact: z.ZodOptional<z.ZodString>;
6
+ timeoutMs: z.ZodOptional<z.ZodNumber>;
7
+ inputDescription: z.ZodOptional<z.ZodString>;
8
+ }, z.core.$strict>;
9
+ declare const AdapterContractSchema: z.ZodObject<{
10
+ id: z.ZodString;
11
+ capability: z.ZodString;
12
+ kind: z.ZodEnum<{
13
+ command: "command";
14
+ skill: "skill";
15
+ deferred: "deferred";
16
+ }>;
17
+ operations: z.ZodObject<{
18
+ collect: z.ZodOptional<z.ZodObject<{
19
+ command: z.ZodString;
20
+ args: z.ZodOptional<z.ZodArray<z.ZodString>>;
21
+ artifact: z.ZodOptional<z.ZodString>;
22
+ timeoutMs: z.ZodOptional<z.ZodNumber>;
23
+ inputDescription: z.ZodOptional<z.ZodString>;
24
+ }, z.core.$strict>>;
25
+ discovery: z.ZodOptional<z.ZodObject<{
26
+ command: z.ZodString;
27
+ args: z.ZodOptional<z.ZodArray<z.ZodString>>;
28
+ artifact: z.ZodOptional<z.ZodString>;
29
+ timeoutMs: z.ZodOptional<z.ZodNumber>;
30
+ inputDescription: z.ZodOptional<z.ZodString>;
31
+ }, z.core.$strict>>;
32
+ read: z.ZodOptional<z.ZodObject<{
33
+ command: z.ZodString;
34
+ args: z.ZodOptional<z.ZodArray<z.ZodString>>;
35
+ artifact: z.ZodOptional<z.ZodString>;
36
+ timeoutMs: z.ZodOptional<z.ZodNumber>;
37
+ inputDescription: z.ZodOptional<z.ZodString>;
38
+ }, z.core.$strict>>;
39
+ preview: z.ZodOptional<z.ZodObject<{
40
+ command: z.ZodString;
41
+ args: z.ZodOptional<z.ZodArray<z.ZodString>>;
42
+ artifact: z.ZodOptional<z.ZodString>;
43
+ timeoutMs: z.ZodOptional<z.ZodNumber>;
44
+ inputDescription: z.ZodOptional<z.ZodString>;
45
+ }, z.core.$strict>>;
46
+ }, z.core.$strict>;
47
+ }, z.core.$strict>;
48
+ declare const HookEntrySchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
49
+ id: z.ZodString;
50
+ type: z.ZodLiteral<"skill">;
51
+ stages: z.ZodArray<z.ZodEnum<{
52
+ parse: "parse";
53
+ tech: "tech";
54
+ plan: "plan";
55
+ execute: "execute";
56
+ verify: "verify";
57
+ }>>;
58
+ path: z.ZodString;
59
+ inputDescription: z.ZodOptional<z.ZodString>;
60
+ }, z.core.$strict>, z.ZodObject<{
61
+ command: z.ZodString;
62
+ args: z.ZodOptional<z.ZodArray<z.ZodString>>;
63
+ timeoutMs: z.ZodOptional<z.ZodNumber>;
64
+ inputDescription: z.ZodOptional<z.ZodString>;
65
+ id: z.ZodString;
66
+ type: z.ZodLiteral<"command">;
67
+ stages: z.ZodArray<z.ZodEnum<{
68
+ parse: "parse";
69
+ tech: "tech";
70
+ plan: "plan";
71
+ execute: "execute";
72
+ verify: "verify";
73
+ }>>;
74
+ }, z.core.$strict>], "type">;
75
+ export declare const PluginIdentitySchema: z.ZodObject<{
76
+ name: z.ZodString;
77
+ marketplace: z.ZodString;
78
+ resolvedSha: z.ZodString;
79
+ digest: z.ZodString;
80
+ }, z.core.$strict>;
81
+ export type PluginIdentity = z.infer<typeof PluginIdentitySchema>;
82
+ export type HookEntry = z.infer<typeof HookEntrySchema>;
83
+ export interface ResolvedEntry<T> {
84
+ entry: T;
85
+ pluginRoot: string;
86
+ identity: PluginIdentity;
87
+ }
88
+ declare const PluginManifestSchema: z.ZodObject<{
89
+ name: z.ZodString;
90
+ version: z.ZodOptional<z.ZodString>;
91
+ description: z.ZodOptional<z.ZodString>;
92
+ extensions: z.ZodOptional<z.ZodObject<{
93
+ 'org.openspec': z.ZodOptional<z.ZodObject<{
94
+ adapters: z.ZodOptional<z.ZodArray<z.ZodObject<{
95
+ id: z.ZodString;
96
+ capability: z.ZodString;
97
+ kind: z.ZodEnum<{
98
+ command: "command";
99
+ skill: "skill";
100
+ deferred: "deferred";
101
+ }>;
102
+ operations: z.ZodObject<{
103
+ collect: z.ZodOptional<z.ZodObject<{
104
+ command: z.ZodString;
105
+ args: z.ZodOptional<z.ZodArray<z.ZodString>>;
106
+ artifact: z.ZodOptional<z.ZodString>;
107
+ timeoutMs: z.ZodOptional<z.ZodNumber>;
108
+ inputDescription: z.ZodOptional<z.ZodString>;
109
+ }, z.core.$strict>>;
110
+ discovery: z.ZodOptional<z.ZodObject<{
111
+ command: z.ZodString;
112
+ args: z.ZodOptional<z.ZodArray<z.ZodString>>;
113
+ artifact: z.ZodOptional<z.ZodString>;
114
+ timeoutMs: z.ZodOptional<z.ZodNumber>;
115
+ inputDescription: z.ZodOptional<z.ZodString>;
116
+ }, z.core.$strict>>;
117
+ read: z.ZodOptional<z.ZodObject<{
118
+ command: z.ZodString;
119
+ args: z.ZodOptional<z.ZodArray<z.ZodString>>;
120
+ artifact: z.ZodOptional<z.ZodString>;
121
+ timeoutMs: z.ZodOptional<z.ZodNumber>;
122
+ inputDescription: z.ZodOptional<z.ZodString>;
123
+ }, z.core.$strict>>;
124
+ preview: z.ZodOptional<z.ZodObject<{
125
+ command: z.ZodString;
126
+ args: z.ZodOptional<z.ZodArray<z.ZodString>>;
127
+ artifact: z.ZodOptional<z.ZodString>;
128
+ timeoutMs: z.ZodOptional<z.ZodNumber>;
129
+ inputDescription: z.ZodOptional<z.ZodString>;
130
+ }, z.core.$strict>>;
131
+ }, z.core.$strict>;
132
+ }, z.core.$strict>>>;
133
+ hooks: z.ZodOptional<z.ZodArray<z.ZodDiscriminatedUnion<[z.ZodObject<{
134
+ id: z.ZodString;
135
+ type: z.ZodLiteral<"skill">;
136
+ stages: z.ZodArray<z.ZodEnum<{
137
+ parse: "parse";
138
+ tech: "tech";
139
+ plan: "plan";
140
+ execute: "execute";
141
+ verify: "verify";
142
+ }>>;
143
+ path: z.ZodString;
144
+ inputDescription: z.ZodOptional<z.ZodString>;
145
+ }, z.core.$strict>, z.ZodObject<{
146
+ command: z.ZodString;
147
+ args: z.ZodOptional<z.ZodArray<z.ZodString>>;
148
+ timeoutMs: z.ZodOptional<z.ZodNumber>;
149
+ inputDescription: z.ZodOptional<z.ZodString>;
150
+ id: z.ZodString;
151
+ type: z.ZodLiteral<"command">;
152
+ stages: z.ZodArray<z.ZodEnum<{
153
+ parse: "parse";
154
+ tech: "tech";
155
+ plan: "plan";
156
+ execute: "execute";
157
+ verify: "verify";
158
+ }>>;
159
+ }, z.core.$strict>], "type">>>;
160
+ }, z.core.$strict>>;
161
+ }, z.core.$catchall<z.ZodUnknown>>>;
162
+ }, z.core.$loose>;
163
+ declare const MarketplaceLockSchema: z.ZodObject<{
164
+ version: z.ZodLiteral<1>;
165
+ marketplaces: z.ZodArray<z.ZodObject<{
166
+ name: z.ZodString;
167
+ repo: z.ZodOptional<z.ZodString>;
168
+ ref: z.ZodString;
169
+ resolvedSha: z.ZodString;
170
+ digest: z.ZodString;
171
+ cachePath: z.ZodString;
172
+ }, z.core.$strict>>;
173
+ plugins: z.ZodArray<z.ZodObject<{
174
+ name: z.ZodString;
175
+ marketplace: z.ZodString;
176
+ resolvedSha: z.ZodString;
177
+ digest: z.ZodString;
178
+ }, z.core.$strict>>;
179
+ }, z.core.$strict>;
180
+ export type AdapterOperation = z.infer<typeof AdapterOperationSchema>;
181
+ export type AdapterContract = z.infer<typeof AdapterContractSchema>;
182
+ export type PluginManifest = z.infer<typeof PluginManifestSchema>;
183
+ export type MarketplaceLock = z.infer<typeof MarketplaceLockSchema>;
184
+ export declare function openspecHome(): string;
185
+ export declare function marketplaceCacheRoot(): string;
186
+ export declare function marketplaceCacheDir(name: string, resolvedSha?: string): string;
187
+ export declare function marketplaceLockPath(projectRoot: string): string;
188
+ export declare function readMarketplaceLock(projectRoot: string): MarketplaceLock;
189
+ export declare function marketplaceRootForProject(projectRoot: string, name: string): string;
190
+ export declare function resolveAdapter(projectRoot: string, selector: string): AdapterContract;
191
+ export declare function resolveAdapterEntry(projectRoot: string, selector: string): ResolvedEntry<AdapterContract>;
192
+ export declare function resolveHookEntry(projectRoot: string, selector: string): ResolvedEntry<HookEntry>;
193
+ export declare function listCachedAdapters(projectRoot: string, marketplace: string): AdapterContract[];
194
+ export declare function pluginExists(projectRoot: string, marketplace: string, pluginName: string): boolean;
195
+ export declare function discoverPluginDirs(root: string): string[];
196
+ export declare function readManifest(pluginDir: string): PluginManifest;
197
+ export declare function parseSelector(selector: string): {
198
+ id: string;
199
+ marketplace: string;
200
+ };
201
+ export {};
202
+ //# sourceMappingURL=adapters.d.ts.map
@@ -0,0 +1,203 @@
1
+ import path from 'node:path';
2
+ import os from 'node:os';
3
+ import { z } from 'zod';
4
+ import { exists, listDirs, readJson } from '../shared/fs.js';
5
+ import { AppError } from '../shared/errors.js';
6
+ import { assertPathToken, resolveInside } from '../shared/paths.js';
7
+ import { HOOK_STAGES } from '../config/schema.js';
8
+ import { digestDirectory } from '../shared/hash.js';
9
+ const ArtifactPathSchema = z.string().min(1).refine((value) => {
10
+ const normalized = value.replace(/\\/g, '/');
11
+ return !normalized.startsWith('/') && !/^[A-Za-z]:\//.test(normalized) && !normalized.split('/').includes('..');
12
+ }, 'adapter artifact 必须是 change 内相对路径');
13
+ const AdapterOperationSchema = z.object({
14
+ command: z.string().min(1),
15
+ args: z.array(z.string()).optional(),
16
+ artifact: ArtifactPathSchema.optional(),
17
+ timeoutMs: z.number().int().positive().max(3600000).optional(),
18
+ inputDescription: z.string().optional(),
19
+ }).strict();
20
+ const AdapterContractSchema = z.object({
21
+ id: z.string().min(1),
22
+ capability: z.string().min(1),
23
+ kind: z.enum(['command', 'skill', 'deferred']),
24
+ operations: z.object({
25
+ collect: AdapterOperationSchema.optional(),
26
+ discovery: AdapterOperationSchema.optional(),
27
+ read: AdapterOperationSchema.optional(),
28
+ preview: AdapterOperationSchema.optional(),
29
+ }).strict(),
30
+ }).strict();
31
+ const PluginStagesSchema = z.array(z.enum(HOOK_STAGES).exclude(['collect'])).min(1)
32
+ .refine((stages) => new Set(stages).size === stages.length, '插件 stages 不能重复');
33
+ const PluginSkillPathSchema = ArtifactPathSchema.refine((value) => !/[\\\0]/.test(value) && value.endsWith('/SKILL.md'), '技能路径必须是插件内目录的 SKILL.md');
34
+ const HookEntrySchema = z.discriminatedUnion('type', [
35
+ z.object({ id: z.string().min(1), type: z.literal('skill'), stages: PluginStagesSchema, path: PluginSkillPathSchema, inputDescription: z.string().optional() }).strict(),
36
+ AdapterOperationSchema.omit({ artifact: true }).extend({ id: z.string().min(1), type: z.literal('command'), stages: PluginStagesSchema }).strict(),
37
+ ]);
38
+ export const PluginIdentitySchema = z.object({
39
+ name: z.string(), marketplace: z.string(), resolvedSha: z.string(), digest: z.string(),
40
+ }).strict();
41
+ const PluginManifestSchema = z.object({
42
+ name: z.string().min(1),
43
+ version: z.string().optional(),
44
+ description: z.string().optional(),
45
+ extensions: z.object({
46
+ 'org.openspec': z.object({ adapters: z.array(AdapterContractSchema).optional(), hooks: z.array(HookEntrySchema).optional() }).strict().optional(),
47
+ }).catchall(z.unknown()).optional(),
48
+ }).passthrough();
49
+ const MarketplaceLockSchema = z.object({
50
+ version: z.literal(1),
51
+ marketplaces: z.array(z.object({
52
+ name: z.string(),
53
+ repo: z.string().optional(),
54
+ ref: z.string(),
55
+ resolvedSha: z.string(),
56
+ digest: z.string(),
57
+ cachePath: z.string(),
58
+ }).strict()),
59
+ plugins: z.array(z.object({
60
+ name: z.string(),
61
+ marketplace: z.string(),
62
+ resolvedSha: z.string(),
63
+ digest: z.string(),
64
+ }).strict()),
65
+ }).strict().superRefine((lock, ctx) => {
66
+ const marketplaces = new Set();
67
+ for (const [index, item] of lock.marketplaces.entries()) {
68
+ if (marketplaces.has(item.name))
69
+ ctx.addIssue({ code: 'custom', path: ['marketplaces', index, 'name'], message: `重复 marketplace: ${item.name}` });
70
+ marketplaces.add(item.name);
71
+ }
72
+ const plugins = new Set();
73
+ for (const [index, item] of lock.plugins.entries()) {
74
+ const selector = `${item.name}@${item.marketplace}`;
75
+ if (plugins.has(selector))
76
+ ctx.addIssue({ code: 'custom', path: ['plugins', index], message: `重复 plugin: ${selector}` });
77
+ plugins.add(selector);
78
+ }
79
+ });
80
+ export function openspecHome() {
81
+ const configured = process.env.OPENSPEC_HOME;
82
+ return configured ? path.resolve(configured) : path.join(os.homedir(), '.openspec', 'v2');
83
+ }
84
+ export function marketplaceCacheRoot() {
85
+ return resolveInside(openspecHome(), 'marketplaces', 'marketplace_cache_outside_home');
86
+ }
87
+ export function marketplaceCacheDir(name, resolvedSha) {
88
+ assertPathToken(name, 'marketplace name');
89
+ const root = resolveInside(marketplaceCacheRoot(), name, 'marketplace_cache_outside_home');
90
+ return resolvedSha ? resolveInside(root, resolvedSha, 'marketplace_cache_path_invalid') : root;
91
+ }
92
+ export function marketplaceLockPath(projectRoot) {
93
+ return resolveInside(projectRoot, 'openspec/marketplace-lock.json', 'marketplace_lock_outside_project');
94
+ }
95
+ export function readMarketplaceLock(projectRoot) {
96
+ const file = marketplaceLockPath(projectRoot);
97
+ if (!exists(file))
98
+ return { version: 1, marketplaces: [], plugins: [] };
99
+ const parsed = MarketplaceLockSchema.safeParse(readJson(file));
100
+ if (!parsed.success)
101
+ throw new AppError('invalid_marketplace_lock', 'marketplace lock 不合法', parsed.error.issues);
102
+ return parsed.data;
103
+ }
104
+ export function marketplaceRootForProject(projectRoot, name) {
105
+ const record = readMarketplaceLock(projectRoot).marketplaces.find((item) => item.name === name);
106
+ if (!record)
107
+ throw new AppError('marketplace_not_locked', `项目未锁定 marketplace: ${name}`);
108
+ const expected = marketplaceCacheDir(name, record.resolvedSha);
109
+ if (path.resolve(record.cachePath) !== path.resolve(expected)) {
110
+ throw new AppError('marketplace_lock_path_mismatch', `marketplace lock 路径不匹配: ${name}`);
111
+ }
112
+ if (!exists(expected))
113
+ throw new AppError('marketplace_cache_missing', `marketplace cache 不存在: ${name}@${record.resolvedSha}`);
114
+ if (digestDirectory(expected) !== record.digest) {
115
+ throw new AppError('marketplace_digest_mismatch', `marketplace cache digest 不匹配: ${name}@${record.resolvedSha}`);
116
+ }
117
+ return expected;
118
+ }
119
+ export function resolveAdapter(projectRoot, selector) {
120
+ return resolveAdapterEntry(projectRoot, selector).entry;
121
+ }
122
+ export function resolveAdapterEntry(projectRoot, selector) {
123
+ return resolveEntry(projectRoot, selector, 'adapters');
124
+ }
125
+ export function resolveHookEntry(projectRoot, selector) {
126
+ return resolveEntry(projectRoot, selector, 'hooks');
127
+ }
128
+ function resolveEntry(projectRoot, selector, kind) {
129
+ const { id, marketplace } = parseSelector(selector);
130
+ const root = marketplaceRootForProject(projectRoot, marketplace);
131
+ const lock = readMarketplaceLock(projectRoot);
132
+ const market = lock.marketplaces.find((item) => item.name === marketplace);
133
+ const matches = [];
134
+ for (const pluginRoot of discoverPluginDirs(root)) {
135
+ const manifest = readManifest(pluginRoot);
136
+ for (const entry of manifest.extensions?.['org.openspec']?.[kind] ?? []) {
137
+ if (entry.id !== id)
138
+ continue;
139
+ const digest = digestDirectory(pluginRoot);
140
+ const record = lock.plugins.find((item) => item.name === manifest.name && item.marketplace === marketplace);
141
+ if (record && (record.resolvedSha !== market.resolvedSha || record.digest !== digest)) {
142
+ throw new AppError('plugin_identity_mismatch', `plugin lock 与文件不匹配: ${manifest.name}@${marketplace}`);
143
+ }
144
+ matches.push({ entry, pluginRoot, identity: { name: manifest.name, marketplace, resolvedSha: market.resolvedSha, digest } });
145
+ }
146
+ }
147
+ const label = kind === 'adapters' ? 'adapter' : 'hook';
148
+ if (matches.length > 1)
149
+ throw new AppError(`${label}_conflict`, `${label} 重复: ${selector}`);
150
+ if (!matches[0])
151
+ throw new AppError(`${label}_not_installed`, `未找到 ${label} ${selector}(项目锁定 ref=${market.ref},resolvedSha=${market.resolvedSha})`);
152
+ return matches[0];
153
+ }
154
+ export function listCachedAdapters(projectRoot, marketplace) {
155
+ const root = marketplaceRootForProject(projectRoot, marketplace);
156
+ const adapters = [];
157
+ const ids = new Set();
158
+ for (const plugin of discoverPluginDirs(root)) {
159
+ for (const adapter of readManifest(plugin).extensions?.['org.openspec']?.adapters ?? []) {
160
+ if (ids.has(adapter.id))
161
+ throw new AppError('adapter_conflict', `marketplace 中存在重复 adapter: ${adapter.id}`);
162
+ ids.add(adapter.id);
163
+ adapters.push(adapter);
164
+ }
165
+ }
166
+ return adapters;
167
+ }
168
+ export function pluginExists(projectRoot, marketplace, pluginName) {
169
+ const root = marketplaceRootForProject(projectRoot, marketplace);
170
+ return discoverPluginDirs(root).some((dir) => path.basename(dir) === pluginName || readManifest(dir).name === pluginName);
171
+ }
172
+ export function discoverPluginDirs(root) {
173
+ if (!exists(root))
174
+ return [];
175
+ const pluginsRoot = exists(path.join(root, 'plugins')) ? resolveInside(root, 'plugins') : root;
176
+ const dirs = listDirs(pluginsRoot).map((name) => resolveInside(pluginsRoot, name, 'plugin_path_outside_marketplace'));
177
+ if (exists(path.join(root, 'plugin.json')))
178
+ dirs.unshift(root);
179
+ return dirs.filter((dir) => exists(path.join(dir, 'plugin.json')));
180
+ }
181
+ export function readManifest(pluginDir) {
182
+ const parsed = PluginManifestSchema.safeParse(readJson(path.join(pluginDir, 'plugin.json')));
183
+ if (!parsed.success)
184
+ throw new AppError('invalid_plugin_manifest', `plugin manifest 不合法: ${pluginDir}`, parsed.error.issues);
185
+ const extension = parsed.data.extensions?.['org.openspec'];
186
+ const ids = new Set();
187
+ for (const entry of [...(extension?.adapters ?? []), ...(extension?.hooks ?? [])]) {
188
+ if (ids.has(entry.id))
189
+ throw new AppError('plugin_entry_conflict', `插件重复入口: ${entry.id}`);
190
+ ids.add(entry.id);
191
+ }
192
+ return parsed.data;
193
+ }
194
+ export function parseSelector(selector) {
195
+ const separator = selector.lastIndexOf('@');
196
+ const id = separator > 0 ? selector.slice(0, separator) : '';
197
+ const marketplace = separator > 0 ? selector.slice(separator + 1) : '';
198
+ if (!id || !marketplace) {
199
+ throw new AppError('invalid_adapter_selector', `adapter 选择器必须是 id@marketplace,收到: ${selector}`);
200
+ }
201
+ return { id, marketplace };
202
+ }
203
+ //# sourceMappingURL=adapters.js.map
@@ -0,0 +1,9 @@
1
+ /**
2
+ * 内置 marketplace 只提供默认名称与源地址,不声明版本:ref 由宿主 config 或显式 --ref 决定,
3
+ * 核心代码不替宿主钉插件版本(插件发版不需要 OpenSpec 跟版)。
4
+ */
5
+ export declare const BUILTIN_MARKETPLACE: {
6
+ readonly name: "mobile-marketplace";
7
+ readonly repo: "git@gitlab.bitrue.com:flutter_app/mobile_marketplace.git";
8
+ };
9
+ //# sourceMappingURL=builtin.d.ts.map
@@ -0,0 +1,9 @@
1
+ /**
2
+ * 内置 marketplace 只提供默认名称与源地址,不声明版本:ref 由宿主 config 或显式 --ref 决定,
3
+ * 核心代码不替宿主钉插件版本(插件发版不需要 OpenSpec 跟版)。
4
+ */
5
+ export const BUILTIN_MARKETPLACE = {
6
+ name: 'mobile-marketplace',
7
+ repo: 'git@gitlab.bitrue.com:flutter_app/mobile_marketplace.git',
8
+ };
9
+ //# sourceMappingURL=builtin.js.map
@@ -0,0 +1,38 @@
1
+ import { z } from 'zod';
2
+ export type HostId = 'claude' | 'codex' | 'cursor';
3
+ export type InstallStatus = 'installed' | 'pending_user_action' | 'skipped_by_user' | 'failed';
4
+ declare const InstallResultSchema: z.ZodObject<{
5
+ plugin: z.ZodString;
6
+ marketplace: z.ZodString;
7
+ host: z.ZodEnum<{
8
+ claude: "claude";
9
+ codex: "codex";
10
+ cursor: "cursor";
11
+ }>;
12
+ status: z.ZodEnum<{
13
+ failed: "failed";
14
+ installed: "installed";
15
+ pending_user_action: "pending_user_action";
16
+ skipped_by_user: "skipped_by_user";
17
+ }>;
18
+ method: z.ZodEnum<{
19
+ ui: "ui";
20
+ native: "native";
21
+ }>;
22
+ digest: z.ZodString;
23
+ resolvedSha: z.ZodString;
24
+ target: z.ZodString;
25
+ error: z.ZodOptional<z.ZodString>;
26
+ }, z.core.$strict>;
27
+ export type InstallResult = z.infer<typeof InstallResultSchema>;
28
+ export declare function installPlugin(input: {
29
+ plugin: string;
30
+ marketplace: string;
31
+ host: HostId;
32
+ projectRoot: string;
33
+ }): InstallResult;
34
+ export declare function writeInstallReceipt(projectRoot: string, results: InstallResult[]): void;
35
+ export declare function readInstallReceipt(projectRoot: string): InstallResult[];
36
+ export declare function pluginFileCount(pluginDir: string): number;
37
+ export {};
38
+ //# sourceMappingURL=install.d.ts.map
@@ -0,0 +1,108 @@
1
+ import path from 'node:path';
2
+ import { z } from 'zod';
3
+ import { exists, listFilesRecursive, readJson, writeJson } from '../shared/fs.js';
4
+ import { digestDirectory } from '../shared/hash.js';
5
+ import { AppError } from '../shared/errors.js';
6
+ import { execFile } from '../shared/exec.js';
7
+ import { installClaudePlugin } from '../delivery/claude-install.js';
8
+ import { assertPathToken, resolveInside } from '../shared/paths.js';
9
+ import { discoverPluginDirs, marketplaceRootForProject, readManifest, readMarketplaceLock, } from './adapters.js';
10
+ const InstallResultSchema = z.object({
11
+ plugin: z.string(),
12
+ marketplace: z.string(),
13
+ host: z.enum(['claude', 'codex', 'cursor']),
14
+ status: z.enum(['installed', 'pending_user_action', 'skipped_by_user', 'failed']),
15
+ method: z.enum(['native', 'ui']),
16
+ digest: z.string(),
17
+ resolvedSha: z.string(),
18
+ target: z.string(),
19
+ error: z.string().optional(),
20
+ }).strict();
21
+ const InstallReceiptSchema = z.object({
22
+ installedAt: z.string(),
23
+ results: z.array(InstallResultSchema),
24
+ }).strict();
25
+ export function installPlugin(input) {
26
+ assertPathToken(input.plugin, 'plugin name');
27
+ assertPathToken(input.marketplace, 'marketplace name');
28
+ const marketplaceRoot = marketplaceRootForProject(input.projectRoot, input.marketplace);
29
+ const pluginDir = findPluginDir(marketplaceRoot, input.plugin);
30
+ if (!pluginDir)
31
+ throw new AppError('plugin_not_found', `找不到插件 ${input.plugin}@${input.marketplace}`);
32
+ const digest = digestDirectory(pluginDir);
33
+ const projectLock = readMarketplaceLock(input.projectRoot);
34
+ const lock = projectLock.marketplaces.find((item) => item.name === input.marketplace);
35
+ if (!lock)
36
+ throw new AppError('marketplace_not_locked', `项目未锁定 marketplace: ${input.marketplace}`);
37
+ const pluginLock = projectLock.plugins.find((item) => item.name === input.plugin && item.marketplace === input.marketplace);
38
+ if (!pluginLock)
39
+ throw new AppError('plugin_not_locked', `插件尚未写入项目 lock: ${input.plugin}@${input.marketplace}`);
40
+ if (pluginLock.resolvedSha !== lock.resolvedSha || pluginLock.digest !== digest) {
41
+ throw new AppError('plugin_digest_mismatch', `插件与项目 lock 不匹配: ${input.plugin}@${input.marketplace}`);
42
+ }
43
+ const selector = `${input.plugin}@${input.marketplace}`;
44
+ if (input.host === 'cursor') {
45
+ return InstallResultSchema.parse({
46
+ plugin: input.plugin,
47
+ marketplace: input.marketplace,
48
+ host: input.host,
49
+ status: 'pending_user_action',
50
+ method: 'ui',
51
+ digest,
52
+ resolvedSha: lock.resolvedSha,
53
+ target: `Cursor Customize → Plugins → ${selector}`,
54
+ });
55
+ }
56
+ // Claude 用 local scope(.claude/settings.local.json,不入库):marketplace 缓存路径是本机绝对路径
57
+ const add = input.host === 'claude'
58
+ ? execFile('claude', ['plugin', 'marketplace', 'add', marketplaceRoot, '--scope', 'local'], { cwd: input.projectRoot })
59
+ : execFile('codex', ['plugin', 'marketplace', 'add', marketplaceRoot, '--json'], { cwd: input.projectRoot });
60
+ if (!add.ok)
61
+ return failedResult(input, digest, lock.resolvedSha, selector, add.stderr || add.stdout);
62
+ const install = input.host === 'claude'
63
+ ? installClaudePlugin(input.projectRoot, selector, pluginDir)
64
+ : execFile('codex', ['plugin', 'add', selector, '--json'], { cwd: input.projectRoot });
65
+ if (!install.ok)
66
+ return failedResult(input, digest, lock.resolvedSha, selector, install.stderr || install.stdout);
67
+ return InstallResultSchema.parse({
68
+ plugin: input.plugin,
69
+ marketplace: input.marketplace,
70
+ host: input.host,
71
+ status: 'installed',
72
+ method: 'native',
73
+ digest,
74
+ resolvedSha: lock.resolvedSha,
75
+ target: selector,
76
+ });
77
+ }
78
+ function failedResult(input, digest, resolvedSha, target, error) {
79
+ return InstallResultSchema.parse({
80
+ ...input,
81
+ status: 'failed',
82
+ method: 'native',
83
+ digest,
84
+ resolvedSha,
85
+ target,
86
+ error,
87
+ });
88
+ }
89
+ function findPluginDir(root, plugin) {
90
+ return discoverPluginDirs(root).find((dir) => path.basename(dir) === plugin || readManifest(dir).name === plugin) ?? null;
91
+ }
92
+ export function writeInstallReceipt(projectRoot, results) {
93
+ const file = resolveInside(projectRoot, 'openspec/.runtime/plugin-installs.json', 'plugin_receipt_outside_project');
94
+ writeJson(file, InstallReceiptSchema.parse({ installedAt: new Date().toISOString(), results }));
95
+ }
96
+ export function readInstallReceipt(projectRoot) {
97
+ const file = resolveInside(projectRoot, 'openspec/.runtime/plugin-installs.json', 'plugin_receipt_outside_project');
98
+ if (!exists(file))
99
+ return [];
100
+ const parsed = InstallReceiptSchema.safeParse(readJson(file));
101
+ if (!parsed.success)
102
+ throw new AppError('invalid_plugin_install_receipt', '插件安装记录不合法', parsed.error.issues);
103
+ return parsed.data.results;
104
+ }
105
+ export function pluginFileCount(pluginDir) {
106
+ return listFilesRecursive(pluginDir).length;
107
+ }
108
+ //# sourceMappingURL=install.js.map
@@ -0,0 +1,71 @@
1
+ import { z } from 'zod';
2
+ declare const RecordSchema: z.ZodObject<{
3
+ name: z.ZodString;
4
+ repo: z.ZodOptional<z.ZodString>;
5
+ ref: z.ZodString;
6
+ resolvedSha: z.ZodString;
7
+ digest: z.ZodString;
8
+ cachePath: z.ZodString;
9
+ updatedAt: z.ZodString;
10
+ }, z.core.$strict>;
11
+ declare const StateSchema: z.ZodObject<{
12
+ marketplaces: z.ZodArray<z.ZodObject<{
13
+ name: z.ZodString;
14
+ repo: z.ZodOptional<z.ZodString>;
15
+ ref: z.ZodString;
16
+ resolvedSha: z.ZodString;
17
+ digest: z.ZodString;
18
+ cachePath: z.ZodString;
19
+ updatedAt: z.ZodString;
20
+ }, z.core.$strict>>;
21
+ }, z.core.$strict>;
22
+ export type MarketplaceRecord = z.infer<typeof RecordSchema>;
23
+ export type MarketplaceState = z.infer<typeof StateSchema>;
24
+ export interface DependencyCheck {
25
+ kind: 'marketplace' | 'plugin';
26
+ id: string;
27
+ ok: boolean;
28
+ code?: string;
29
+ digest?: string;
30
+ resolvedSha?: string;
31
+ /** 插件 manifest 声明的版本与宿主要求的最低版本;仅在配置了 minVersion 时出现 */
32
+ version?: string;
33
+ minVersion?: string;
34
+ }
35
+ /**
36
+ * 用户级缓存索引只是派生数据(项目 lock 才是权威入口)。
37
+ * 旧格式或损坏的索引不能阻塞 init:保留仍合法的记录,其余丢弃,原文件备份为 state.json.invalid-<时间戳>。
38
+ */
39
+ export declare function loadMarketplaceState(): MarketplaceState;
40
+ export declare function addMarketplace(input: {
41
+ projectRoot?: string;
42
+ name?: string;
43
+ repo?: string;
44
+ ref?: string;
45
+ dir?: string;
46
+ }): MarketplaceRecord;
47
+ export declare function listMarketplaces(projectRoot?: string): MarketplaceRecord[];
48
+ export declare function ensurePresetMarketplaces(projectRoot: string, marketplaces: Array<{
49
+ name: string;
50
+ repo: string;
51
+ ref: string;
52
+ }>): MarketplaceRecord[];
53
+ /** 插件版本不低于宿主声明的 minVersion;manifest 缺版本或无法解析时视为不满足。 */
54
+ export declare function pluginVersionSatisfies(version: string | undefined, minVersion: string | undefined): boolean;
55
+ export declare function assertPluginsPresent(projectRoot: string, plugins: Array<{
56
+ name: string;
57
+ marketplace: string;
58
+ minVersion?: string;
59
+ }>): string[];
60
+ export declare function inspectMarketplaceDependencies(projectRoot: string, marketplaces: Array<{
61
+ name: string;
62
+ repo: string;
63
+ ref: string;
64
+ }>, plugins: Array<{
65
+ name: string;
66
+ marketplace: string;
67
+ minVersion?: string;
68
+ }>): DependencyCheck[];
69
+ export declare function isImmutableRef(ref: string): boolean;
70
+ export {};
71
+ //# sourceMappingURL=registry.d.ts.map