@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,114 @@
1
+ import { readFileSync, statSync } from 'node:fs';
2
+ import { z } from 'zod';
3
+ import { exists, readText } from '../shared/fs.js';
4
+ import { AppError } from '../shared/errors.js';
5
+ import { isInsideDir, isOpsxInternal, normalizeRel, resolveInside } from '../shared/paths.js';
6
+ import { requireChange } from './change.js';
7
+ import { eventsPath } from './state.js';
8
+ export const PendingWorkSchema = z.object({
9
+ changeName: z.string(),
10
+ items: z.array(z.object({
11
+ category: z.string(),
12
+ source: z.enum(['code', 'worker']),
13
+ file: z.string(),
14
+ line: z.number().int().positive(),
15
+ text: z.string(),
16
+ marker: z.enum(['TODO', 'FIXME']).optional(),
17
+ workerId: z.string().optional(),
18
+ stepId: z.string().optional(),
19
+ }).strict()),
20
+ skippedFiles: z.array(z.object({ file: z.string(), reason: z.string() }).strict()),
21
+ }).strict();
22
+ export function reviewScopedFiles(config, files) {
23
+ const scope = config.verify?.reviewScope;
24
+ return [...new Set(files.map(normalizeRel))].filter((file) => {
25
+ if (isOpsxInternal(file))
26
+ return false;
27
+ if (scope?.includeDirs?.length && !scope.includeDirs.some((dir) => isInsideDir(file, dir)))
28
+ return false;
29
+ return !scope?.excludeDirs?.some((dir) => isInsideDir(file, dir));
30
+ }).sort();
31
+ }
32
+ /** 只读当前 changed-set;历史 Worker 声明单独展示,不推断其已解决。 */
33
+ export function collectPendingWork(projectRoot, changeName, config) {
34
+ const state = requireChange(projectRoot, changeName);
35
+ const result = { changeName, items: [], skippedFiles: [] };
36
+ for (const file of reviewScopedFiles(config, state.engineChangedFiles)) {
37
+ const absolute = resolveInside(projectRoot, file, 'todo_path_outside_project');
38
+ if (!exists(absolute) || !statSync(absolute).isFile()) {
39
+ result.skippedFiles.push({ file, reason: '文件已删除或不是普通文件' });
40
+ continue;
41
+ }
42
+ const data = readFileSync(absolute);
43
+ if (data.includes(0)) {
44
+ result.skippedFiles.push({ file, reason: '二进制文件' });
45
+ continue;
46
+ }
47
+ for (const [index, line] of data.toString('utf8').split(/\r?\n/).entries()) {
48
+ for (const match of markers(line)) {
49
+ result.items.push({ source: 'code', file, line: index + 1, ...match });
50
+ }
51
+ }
52
+ }
53
+ const events = eventsPath(projectRoot, changeName);
54
+ if (exists(events)) {
55
+ for (const [index, line] of readText(events).split(/\r?\n/).entries()) {
56
+ if (!line.trim())
57
+ continue;
58
+ let event;
59
+ try {
60
+ event = JSON.parse(line);
61
+ }
62
+ catch {
63
+ throw new AppError('invalid_events', `events.jsonl 第 ${index + 1} 行无法解析`);
64
+ }
65
+ if (event.type !== 'worker.accepted' || typeof event.payload?.notes !== 'string')
66
+ continue;
67
+ const { notes, workerId, stepId } = event.payload;
68
+ let pendingSection = false;
69
+ for (const noteLine of notes.split(/\r?\n/)) {
70
+ const heading = /^\s*#{1,6}\s+/.test(noteLine)
71
+ || /^\s*[^::]+[::]\s*$/.test(noteLine);
72
+ const pending = /未能满足|占位/.test(noteLine) && !/(无|没有|无需|不需要|禁止|不得)\s*(占位|未能满足)|(?:未能满足|占位)\s*[::]\s*无/.test(noteLine);
73
+ if (heading)
74
+ pendingSection = pending;
75
+ const found = markers(noteLine);
76
+ if ((!found.length && !(pending || pendingSection)) || !noteLine.trim())
77
+ continue;
78
+ // 章节标题用于确定范围,本身不是一条待办。
79
+ if (heading && !found.length)
80
+ continue;
81
+ const base = { source: 'worker', file: `openspec/changes/${changeName}/.opsx/events.jsonl`, line: index + 1,
82
+ workerId: typeof workerId === 'string' ? workerId : undefined, stepId: typeof stepId === 'string' ? stepId : undefined };
83
+ if (found.length)
84
+ result.items.push(...found.map((item) => ({ ...base, ...item })));
85
+ else
86
+ result.items.push({ ...base, category: 'worker', text: noteLine.trim() });
87
+ }
88
+ }
89
+ }
90
+ return result;
91
+ }
92
+ function markers(line) {
93
+ const matches = [...line.matchAll(/\b(TODO|FIXME)\b(?:\(([^)\r\n]+)\))?\s*[::]?\s*/g)];
94
+ return matches.map((match, i) => ({
95
+ marker: match[1],
96
+ category: match[2]?.trim() || 'general',
97
+ text: line.slice(match.index + match[0].length, matches[i + 1]?.index).replace(/\s*\*\/\s*$/, '').trim(),
98
+ }));
99
+ }
100
+ export function renderPendingWork(pending) {
101
+ const lines = [`# Pending work — ${pending.changeName}`, '', '非阻塞待办;Worker 项是验收时的历史声明,需人工核实是否已解决。'];
102
+ for (const category of [...new Set(pending.items.map((item) => item.category))].sort()) {
103
+ lines.push('', `## ${category}`);
104
+ for (const item of pending.items.filter((entry) => entry.category === category)) {
105
+ lines.push(`- ${item.file}:${item.line}: ${item.text}${item.source === 'worker' ? ` [worker ${item.stepId ?? item.workerId ?? ''}]` : ''}`);
106
+ }
107
+ }
108
+ if (!pending.items.length)
109
+ lines.push('', '无待办。');
110
+ if (pending.skippedFiles.length)
111
+ lines.push('', '## 未扫描文件', ...pending.skippedFiles.map((item) => `- ${item.file}: ${item.reason}`));
112
+ return `${lines.join('\n')}\n`;
113
+ }
114
+ //# sourceMappingURL=todo.js.map
@@ -0,0 +1,297 @@
1
+ import { z } from 'zod';
2
+ import { type PluginIdentity } from '../marketplace/adapters.js';
3
+ import { type PluginBinding } from '../hooks/plugin-runtime.js';
4
+ export declare const STATE_VERSION = 1;
5
+ export declare const PACKET_VERSION = 1;
6
+ export declare const NODES: {
7
+ readonly COLLECT: "collect";
8
+ readonly PARSE: "parse";
9
+ readonly TECH: "tech";
10
+ readonly PLAN: "plan";
11
+ readonly GATE_A: "gate_a";
12
+ readonly CODE: "code";
13
+ readonly GATE_B: "gate_b";
14
+ readonly VERIFY: "verify";
15
+ readonly GATE_C: "gate_c";
16
+ readonly COMPLETED: "completed";
17
+ };
18
+ export type Node = (typeof NODES)[keyof typeof NODES];
19
+ export declare const GATE_IDS: readonly ["gate-A", "gate-B", "gate-C"];
20
+ export type GateId = (typeof GATE_IDS)[number];
21
+ export type GateStatus = 'pending' | 'approved' | 'rejected';
22
+ export type WorkerResultStatus = 'completed' | 'failed' | 'blocked';
23
+ export type TaskStatus = 'pending' | 'completed';
24
+ export type SourceCapability = 'requirements' | 'interfaces' | 'design';
25
+ export interface GateState {
26
+ status: GateStatus;
27
+ comment?: string;
28
+ reason?: string;
29
+ updatedAt?: string;
30
+ }
31
+ export interface TaskState {
32
+ id: string;
33
+ title: string;
34
+ category: string;
35
+ type?: 'code' | 'interface-codegen';
36
+ dependsOn: string[];
37
+ status: TaskStatus;
38
+ completedAt?: string;
39
+ }
40
+ /** 详情以 task-plan.json 为唯一来源,不写入调度状态。 */
41
+ export interface TaskPlanTask extends TaskState {
42
+ refScope?: string[];
43
+ notes?: string;
44
+ capability?: string;
45
+ operation?: string;
46
+ provider?: string;
47
+ sourceRefs?: string[];
48
+ designRefs?: string[];
49
+ }
50
+ export interface LastIssue {
51
+ type: 'worker_failed' | 'worker_blocked' | 'gate_rejected';
52
+ stepId?: string;
53
+ gateId?: GateId;
54
+ fixable?: boolean;
55
+ questions?: string[];
56
+ reason?: string;
57
+ updatedAt: string;
58
+ }
59
+ export interface ActiveWorker {
60
+ workerId: string;
61
+ stepId: string;
62
+ packet: string;
63
+ expectedResult: string;
64
+ createdAt: string;
65
+ }
66
+ export interface SourceUpdate {
67
+ fromRevision: number;
68
+ startedAt: string;
69
+ previousNode: Node;
70
+ hadCompletedTasks: boolean;
71
+ backupPath: string;
72
+ completedTaskIds: string[];
73
+ changedSourceIds: string[];
74
+ }
75
+ export interface VerifyRepair {
76
+ sourceVerifyRunId: string;
77
+ requestedAt: string;
78
+ reason: string;
79
+ }
80
+ export interface ChangeState {
81
+ version: number;
82
+ changeName: string;
83
+ node: Node;
84
+ sourceRevision: number;
85
+ collectedRevision: number;
86
+ gates: Record<GateId, GateState>;
87
+ tasks: TaskState[];
88
+ engineChangedFiles: string[];
89
+ /** Original baseline and file ownership survive cancellation and source revisions. */
90
+ codeChanges?: {
91
+ baseline: string;
92
+ filesByStep: Record<string, string[]>;
93
+ pendingSteps?: string[];
94
+ };
95
+ activeWorker: ActiveWorker | null;
96
+ techSkipped: boolean;
97
+ lastIssue: LastIssue | null;
98
+ sourceUpdate: SourceUpdate | null;
99
+ verifyRunId: string | null;
100
+ repair: VerifyRepair | null;
101
+ createdAt: string;
102
+ updatedAt: string;
103
+ }
104
+ export interface SourceItem {
105
+ id: string;
106
+ capability: SourceCapability;
107
+ kind: 'url' | 'text' | 'file';
108
+ locator: string;
109
+ artifact?: string;
110
+ artifactSha256?: string;
111
+ adapter?: {
112
+ use: string;
113
+ identity: PluginIdentity;
114
+ };
115
+ /** CLI-owned checkpoint, present only during an unfinished collect. */
116
+ collection?: {
117
+ fingerprint: string;
118
+ artifactSha256: string;
119
+ };
120
+ }
121
+ export interface SourcesLedger {
122
+ revision: number;
123
+ notes: string;
124
+ items: SourceItem[];
125
+ }
126
+ export interface WorkerPacket {
127
+ version: number;
128
+ workerId: string;
129
+ stepId: string;
130
+ changeName: string;
131
+ node: Node;
132
+ task?: Pick<TaskPlanTask, 'id' | 'title' | 'category' | 'type' | 'refScope' | 'dependsOn' | 'notes' | 'sourceRefs' | 'capability' | 'operation' | 'provider' | 'designRefs'>;
133
+ initContext?: TaskInitContext;
134
+ executionEvidence?: true;
135
+ executionEvidenceVersion?: 2;
136
+ planNotes?: string;
137
+ allowedCategories?: string[];
138
+ skipTechAllowed?: boolean;
139
+ refs: {
140
+ [key: string]: string | DesignSourceRef[] | undefined;
141
+ design?: DesignSourceRef[];
142
+ };
143
+ expectedResult: string;
144
+ /** Worker blocked 后用户的答复;再次 create 复用 packet 时 Worker 必须按此完成产物 */
145
+ decisions?: WorkerDecision[];
146
+ /** verify Worker:本 run 需要登记证据的 verify 阶段 hook(command 由 `verify commands` 执行) */
147
+ verifyHooks?: ExecuteHookDescriptor[];
148
+ }
149
+ export interface DesignSourceRef {
150
+ sourceId: string;
151
+ kind: SourceItem['kind'];
152
+ locator?: string;
153
+ artifact?: string;
154
+ }
155
+ export interface WorkerDecision {
156
+ questions: string[];
157
+ answers: string[];
158
+ answeredAt: string;
159
+ }
160
+ export interface WorkerResult {
161
+ workerId: string;
162
+ stepId: string;
163
+ status: WorkerResultStatus;
164
+ changedFiles?: string[];
165
+ skipTech?: boolean;
166
+ notes?: string;
167
+ questions?: string[];
168
+ artifacts?: string[];
169
+ executeHookResults?: ExecuteHookResult[];
170
+ }
171
+ export interface InitRule {
172
+ source: 'category' | 'task';
173
+ text: string;
174
+ }
175
+ export type ExecuteHookDescriptor = {
176
+ purpose?: string;
177
+ plugin?: PluginBinding;
178
+ } & ({
179
+ id: string;
180
+ type: 'notes';
181
+ required: boolean;
182
+ notes: string;
183
+ }
184
+ /** path:在项目锁定的 marketplace 插件里找到的 SKILL.md 绝对路径;宿主未注册该 skill 时 Worker 可直接读取 */
185
+ | {
186
+ id: string;
187
+ type: 'skill';
188
+ required: boolean;
189
+ name: string;
190
+ path?: string;
191
+ } | {
192
+ id: string;
193
+ type: 'file';
194
+ required: boolean;
195
+ path: string;
196
+ } | {
197
+ id: string;
198
+ type: 'check';
199
+ required: boolean;
200
+ category: string;
201
+ description: string;
202
+ } | {
203
+ id: string;
204
+ type: 'command';
205
+ required: boolean;
206
+ command: string;
207
+ });
208
+ /** parse/plan/code Worker 共用的初始化上下文;只有 code Worker 带 category。 */
209
+ export interface TaskInitContext {
210
+ category?: string;
211
+ rules: InitRule[];
212
+ hooks: ExecuteHookDescriptor[];
213
+ }
214
+ export interface ExecuteHookResult {
215
+ id: string;
216
+ status: 'passed' | 'failed' | 'skipped';
217
+ notes?: string;
218
+ }
219
+ export type NextAction = {
220
+ action: 'add_sources';
221
+ changeName: string;
222
+ } | {
223
+ action: 'collect_sources';
224
+ changeName: string;
225
+ } | {
226
+ action: 'create_worker';
227
+ changeName: string;
228
+ stepId: string;
229
+ } | {
230
+ action: 'retry_worker';
231
+ changeName: string;
232
+ stepId: string;
233
+ } | {
234
+ action: 'await_gate';
235
+ changeName: string;
236
+ gateId: GateId;
237
+ } | {
238
+ action: 'need_user_input';
239
+ changeName: string;
240
+ questions?: string[];
241
+ } | {
242
+ action: 'fix_verify';
243
+ changeName: string;
244
+ } | {
245
+ action: 'completed';
246
+ changeName: string;
247
+ } | {
248
+ action: 'archive';
249
+ changeName: string;
250
+ };
251
+ export declare const WORKFLOWS: readonly ["opsx-propose", "opsx-explore", "opsx-apply", "opsx-verify", "opsx-fix-verify", "opsx-resume", "opsx-source-update", "opsx-archive", "opsx-gate-decision"];
252
+ export type WorkflowName = (typeof WORKFLOWS)[number];
253
+ /** 接受旧 state 的详情字段;读取和保存均投影为精简调度状态。 */
254
+ export declare const TaskStateSchema: z.ZodPipe<z.ZodObject<{
255
+ id: z.ZodString;
256
+ title: z.ZodString;
257
+ category: z.ZodString;
258
+ type: z.ZodOptional<z.ZodEnum<{
259
+ code: "code";
260
+ "interface-codegen": "interface-codegen";
261
+ }>>;
262
+ refScope: z.ZodOptional<z.ZodArray<z.ZodString>>;
263
+ dependsOn: z.ZodArray<z.ZodString>;
264
+ notes: z.ZodOptional<z.ZodString>;
265
+ status: z.ZodEnum<{
266
+ completed: "completed";
267
+ pending: "pending";
268
+ }>;
269
+ completedAt: z.ZodOptional<z.ZodString>;
270
+ changedFiles: z.ZodOptional<z.ZodArray<z.ZodString>>;
271
+ capability: z.ZodOptional<z.ZodString>;
272
+ operation: z.ZodOptional<z.ZodString>;
273
+ provider: z.ZodOptional<z.ZodString>;
274
+ sourceRefs: z.ZodOptional<z.ZodArray<z.ZodString>>;
275
+ designRefs: z.ZodOptional<z.ZodPipe<z.ZodArray<z.ZodString>, z.ZodTransform<string[], string[]>>>;
276
+ }, z.core.$strict>, z.ZodTransform<TaskState, {
277
+ id: string;
278
+ title: string;
279
+ category: string;
280
+ dependsOn: string[];
281
+ status: "completed" | "pending";
282
+ type?: "code" | "interface-codegen" | undefined;
283
+ refScope?: string[] | undefined;
284
+ notes?: string | undefined;
285
+ completedAt?: string | undefined;
286
+ changedFiles?: string[] | undefined;
287
+ capability?: string | undefined;
288
+ operation?: string | undefined;
289
+ provider?: string | undefined;
290
+ sourceRefs?: string[] | undefined;
291
+ designRefs?: string[] | undefined;
292
+ }>>;
293
+ export declare const ChangeStateSchema: z.ZodType<ChangeState>;
294
+ export declare const SourcesLedgerSchema: z.ZodType<SourcesLedger>;
295
+ export declare const WorkerPacketSchema: z.ZodType<WorkerPacket>;
296
+ export declare const WorkerResultSchema: z.ZodType<WorkerResult>;
297
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1,227 @@
1
+ import { z } from 'zod';
2
+ import { PluginIdentitySchema } from '../marketplace/adapters.js';
3
+ import { PluginBindingSchema } from '../hooks/plugin-runtime.js';
4
+ export const STATE_VERSION = 1;
5
+ export const PACKET_VERSION = 1;
6
+ export const NODES = {
7
+ COLLECT: 'collect',
8
+ PARSE: 'parse',
9
+ TECH: 'tech',
10
+ PLAN: 'plan',
11
+ GATE_A: 'gate_a',
12
+ CODE: 'code',
13
+ GATE_B: 'gate_b',
14
+ VERIFY: 'verify',
15
+ GATE_C: 'gate_c',
16
+ COMPLETED: 'completed',
17
+ };
18
+ export const GATE_IDS = ['gate-A', 'gate-B', 'gate-C'];
19
+ export const WORKFLOWS = [
20
+ 'opsx-propose',
21
+ 'opsx-explore',
22
+ 'opsx-apply',
23
+ 'opsx-verify',
24
+ 'opsx-fix-verify',
25
+ 'opsx-resume',
26
+ 'opsx-source-update',
27
+ 'opsx-archive',
28
+ 'opsx-gate-decision',
29
+ ];
30
+ const PathTokenSchema = z.string().min(1).refine((value) => value !== '.' && value !== '..' && !/[\\/\0]/.test(value), '标识不能包含路径语义');
31
+ const RelativePathSchema = z.string().min(1).refine((value) => !value.startsWith('/')
32
+ && !value.startsWith('\\')
33
+ && !/^[A-Za-z]:[\\/]/.test(value)
34
+ && !value.includes('\0')
35
+ && !value.split(/[\\/]/).includes('..'), '必须是未越界的相对路径');
36
+ const ChangeNameSchema = z.string().regex(/^[a-z0-9][a-z0-9-]{0,79}$/);
37
+ const GateStateSchema = z.object({
38
+ status: z.enum(['pending', 'approved', 'rejected']),
39
+ comment: z.string().optional(),
40
+ reason: z.string().optional(),
41
+ updatedAt: z.string().optional(),
42
+ }).strict();
43
+ const TaskPlanTaskSchema = z.object({
44
+ id: PathTokenSchema,
45
+ title: z.string().min(1),
46
+ category: z.string().min(1),
47
+ type: z.enum(['code', 'interface-codegen']).optional(),
48
+ refScope: z.array(RelativePathSchema).optional(),
49
+ dependsOn: z.array(PathTokenSchema),
50
+ notes: z.string().optional(),
51
+ status: z.enum(['pending', 'completed']),
52
+ completedAt: z.string().optional(),
53
+ changedFiles: z.array(RelativePathSchema).optional(),
54
+ capability: z.string().optional(),
55
+ operation: z.string().optional(),
56
+ provider: z.string().optional(),
57
+ sourceRefs: z.array(PathTokenSchema).optional(),
58
+ designRefs: z.array(z.string().trim().min(1)).transform((refs) => [...new Set(refs)]).optional(),
59
+ }).strict();
60
+ /** 接受旧 state 的详情字段;读取和保存均投影为精简调度状态。 */
61
+ export const TaskStateSchema = TaskPlanTaskSchema.transform(({ id, title, category, type, dependsOn, status, completedAt }) => ({ id, title, category, type, dependsOn, status, completedAt }));
62
+ const LastIssueSchema = z.object({
63
+ type: z.enum(['worker_failed', 'worker_blocked', 'gate_rejected']),
64
+ stepId: PathTokenSchema.optional(),
65
+ gateId: z.enum(GATE_IDS).optional(),
66
+ fixable: z.boolean().optional(),
67
+ questions: z.array(z.string()).optional(),
68
+ reason: z.string().optional(),
69
+ updatedAt: z.string(),
70
+ }).strict();
71
+ const ActiveWorkerSchema = z.object({
72
+ workerId: PathTokenSchema,
73
+ stepId: PathTokenSchema,
74
+ packet: RelativePathSchema,
75
+ expectedResult: RelativePathSchema,
76
+ createdAt: z.string(),
77
+ }).strict();
78
+ const SourceUpdateSchema = z.object({
79
+ fromRevision: z.number().int().nonnegative(),
80
+ startedAt: z.string(),
81
+ previousNode: z.enum(Object.values(NODES)),
82
+ hadCompletedTasks: z.boolean(),
83
+ backupPath: z.literal('.opsx/source-update-backup'),
84
+ completedTaskIds: z.array(PathTokenSchema),
85
+ changedSourceIds: z.array(PathTokenSchema),
86
+ }).strict();
87
+ const VerifyRepairSchema = z.object({
88
+ sourceVerifyRunId: PathTokenSchema,
89
+ requestedAt: z.string(),
90
+ reason: z.string(),
91
+ }).strict();
92
+ export const ChangeStateSchema = z.object({
93
+ version: z.literal(STATE_VERSION),
94
+ changeName: ChangeNameSchema,
95
+ node: z.enum(Object.values(NODES)),
96
+ sourceRevision: z.number().int().nonnegative(),
97
+ collectedRevision: z.number().int().nonnegative(),
98
+ gates: z.object({
99
+ 'gate-A': GateStateSchema,
100
+ 'gate-B': GateStateSchema,
101
+ 'gate-C': GateStateSchema,
102
+ }).strict(),
103
+ tasks: z.array(TaskStateSchema),
104
+ engineChangedFiles: z.array(RelativePathSchema),
105
+ codeChanges: z.object({ baseline: RelativePathSchema, filesByStep: z.record(PathTokenSchema, z.array(RelativePathSchema)), pendingSteps: z.array(PathTokenSchema).optional() }).strict().optional(),
106
+ activeWorker: ActiveWorkerSchema.nullable(),
107
+ techSkipped: z.boolean(),
108
+ lastIssue: LastIssueSchema.nullable(),
109
+ sourceUpdate: SourceUpdateSchema.nullable(),
110
+ verifyRunId: PathTokenSchema.nullable(),
111
+ repair: VerifyRepairSchema.nullable(),
112
+ createdAt: z.string(),
113
+ updatedAt: z.string(),
114
+ }).strict().superRefine((state, ctx) => {
115
+ if (state.collectedRevision > state.sourceRevision) {
116
+ ctx.addIssue({ code: 'custom', path: ['collectedRevision'], message: 'collectedRevision 不能大于 sourceRevision' });
117
+ }
118
+ const taskIds = new Set();
119
+ for (const [index, task] of state.tasks.entries()) {
120
+ if (taskIds.has(task.id))
121
+ ctx.addIssue({ code: 'custom', path: ['tasks', index, 'id'], message: `重复 task id: ${task.id}` });
122
+ taskIds.add(task.id);
123
+ }
124
+ if (state.activeWorker) {
125
+ const workerPrefix = `openspec/changes/${state.changeName}/.opsx/workers/`;
126
+ if (!state.activeWorker.packet.startsWith(workerPrefix) || !state.activeWorker.expectedResult.startsWith(workerPrefix)) {
127
+ ctx.addIssue({ code: 'custom', path: ['activeWorker'], message: 'active Worker 路径不属于当前 change' });
128
+ }
129
+ if (state.activeWorker.stepId === 'verify' && state.verifyRunId !== state.activeWorker.workerId) {
130
+ ctx.addIssue({ code: 'custom', path: ['verifyRunId'], message: 'verifyRunId 与 active verify Worker 不一致' });
131
+ }
132
+ }
133
+ });
134
+ const SourceItemSchema = z.object({
135
+ id: PathTokenSchema,
136
+ capability: z.enum(['requirements', 'interfaces', 'design']),
137
+ kind: z.enum(['url', 'text', 'file']),
138
+ locator: z.string().min(1),
139
+ artifact: RelativePathSchema.optional(),
140
+ artifactSha256: z.string().regex(/^[a-f0-9]{64}$/).optional(),
141
+ adapter: z.object({ use: z.string(), identity: PluginIdentitySchema }).strict().optional(),
142
+ collection: z.object({ fingerprint: z.string().regex(/^[a-f0-9]{64}$/), artifactSha256: z.string().regex(/^[a-f0-9]{64}$/) }).strict().optional(),
143
+ }).strict();
144
+ export const SourcesLedgerSchema = z.object({
145
+ revision: z.number().int().nonnegative(),
146
+ notes: z.string(),
147
+ items: z.array(SourceItemSchema),
148
+ }).strict().superRefine((ledger, ctx) => {
149
+ const ids = new Set();
150
+ for (const [index, item] of ledger.items.entries()) {
151
+ if (ids.has(item.id))
152
+ ctx.addIssue({ code: 'custom', path: ['items', index, 'id'], message: `重复 source id: ${item.id}` });
153
+ ids.add(item.id);
154
+ }
155
+ });
156
+ const ExecuteHookDescriptorSchema = z.discriminatedUnion('type', [
157
+ z.object({ purpose: z.string().optional(), plugin: PluginBindingSchema.optional(), id: PathTokenSchema, type: z.literal('notes'), required: z.boolean(), notes: z.string() }).strict(),
158
+ z.object({ purpose: z.string().optional(), plugin: PluginBindingSchema.optional(), id: PathTokenSchema, type: z.literal('skill'), required: z.boolean(), name: z.string(), path: z.string().optional() }).strict(),
159
+ z.object({ purpose: z.string().optional(), plugin: PluginBindingSchema.optional(), id: PathTokenSchema, type: z.literal('file'), required: z.boolean(), path: RelativePathSchema }).strict(),
160
+ z.object({ purpose: z.string().optional(), plugin: PluginBindingSchema.optional(), id: PathTokenSchema, type: z.literal('check'), required: z.boolean(), category: z.string(), description: z.string() }).strict(),
161
+ z.object({ purpose: z.string().optional(), plugin: PluginBindingSchema.optional(), id: PathTokenSchema, type: z.literal('command'), required: z.boolean(), command: z.string() }).strict(),
162
+ ]);
163
+ const TaskInitContextSchema = z.object({
164
+ category: z.string().optional(),
165
+ rules: z.array(z.object({ source: z.enum(['category', 'task']), text: z.string() }).strict()),
166
+ hooks: z.array(ExecuteHookDescriptorSchema),
167
+ }).strict();
168
+ const PacketTaskSchema = TaskPlanTaskSchema.pick({
169
+ id: true,
170
+ title: true,
171
+ category: true,
172
+ type: true,
173
+ refScope: true,
174
+ dependsOn: true,
175
+ notes: true,
176
+ sourceRefs: true,
177
+ designRefs: true,
178
+ capability: true,
179
+ operation: true,
180
+ provider: true,
181
+ });
182
+ export const WorkerPacketSchema = z.object({
183
+ version: z.literal(PACKET_VERSION),
184
+ workerId: PathTokenSchema,
185
+ stepId: PathTokenSchema,
186
+ changeName: ChangeNameSchema,
187
+ node: z.enum(Object.values(NODES)),
188
+ task: PacketTaskSchema.optional(),
189
+ initContext: TaskInitContextSchema.optional(),
190
+ executionEvidence: z.literal(true).optional(),
191
+ executionEvidenceVersion: z.literal(2).optional(),
192
+ planNotes: z.string().optional(),
193
+ allowedCategories: z.array(z.string()).optional(),
194
+ skipTechAllowed: z.boolean().optional(),
195
+ refs: z.object({
196
+ design: z.array(z.object({
197
+ sourceId: PathTokenSchema,
198
+ kind: z.enum(['url', 'file', 'text']),
199
+ locator: z.string().min(1).optional(),
200
+ artifact: RelativePathSchema.optional(),
201
+ }).strict()).optional(),
202
+ }).catchall(RelativePathSchema),
203
+ expectedResult: RelativePathSchema,
204
+ decisions: z.array(z.object({
205
+ questions: z.array(z.string()),
206
+ answers: z.array(z.string().min(1)).min(1),
207
+ answeredAt: z.string(),
208
+ }).strict()).optional(),
209
+ verifyHooks: z.array(ExecuteHookDescriptorSchema).optional(),
210
+ }).strict();
211
+ const ExecuteHookResultSchema = z.object({
212
+ id: PathTokenSchema,
213
+ status: z.enum(['passed', 'failed', 'skipped']),
214
+ notes: z.string().optional(),
215
+ }).strict();
216
+ export const WorkerResultSchema = z.object({
217
+ workerId: PathTokenSchema,
218
+ stepId: PathTokenSchema,
219
+ status: z.enum(['completed', 'failed', 'blocked']),
220
+ changedFiles: z.array(RelativePathSchema).optional(),
221
+ skipTech: z.boolean().optional(),
222
+ notes: z.string().optional(),
223
+ questions: z.array(z.string()).optional(),
224
+ artifacts: z.array(z.string()).optional(),
225
+ executeHookResults: z.array(ExecuteHookResultSchema).optional(),
226
+ }).strict();
227
+ //# sourceMappingURL=types.js.map