@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.
- package/.agents/plugins/marketplace.json +13 -0
- package/.claude-plugin/marketplace.json +12 -0
- package/.claude-plugin/plugin.json +9 -0
- package/.codex-plugin/plugin.json +9 -0
- package/LICENSE +21 -0
- package/README.md +111 -0
- package/bin/openspec.js +3 -0
- package/dist/cli/commands/change.d.ts +3 -0
- package/dist/cli/commands/change.js +37 -0
- package/dist/cli/commands/ext.d.ts +3 -0
- package/dist/cli/commands/ext.js +196 -0
- package/dist/cli/commands/figma.d.ts +3 -0
- package/dist/cli/commands/figma.js +73 -0
- package/dist/cli/commands/init.d.ts +3 -0
- package/dist/cli/commands/init.js +71 -0
- package/dist/cli/commands/opsx.d.ts +3 -0
- package/dist/cli/commands/opsx.js +324 -0
- package/dist/cli/commands/usage.d.ts +3 -0
- package/dist/cli/commands/usage.js +21 -0
- package/dist/cli/output.d.ts +8 -0
- package/dist/cli/output.js +17 -0
- package/dist/cli/program.d.ts +4 -0
- package/dist/cli/program.js +42 -0
- package/dist/cli/register.d.ts +3 -0
- package/dist/cli/register.js +13 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +3 -0
- package/dist/config/load.d.ts +7 -0
- package/dist/config/load.js +55 -0
- package/dist/config/presets.d.ts +129 -0
- package/dist/config/presets.js +125 -0
- package/dist/config/schema.d.ts +281 -0
- package/dist/config/schema.js +178 -0
- package/dist/core/archive.d.ts +5 -0
- package/dist/core/archive.js +33 -0
- package/dist/core/change.d.ts +12 -0
- package/dist/core/change.js +69 -0
- package/dist/core/code-changes.d.ts +9 -0
- package/dist/core/code-changes.js +31 -0
- package/dist/core/flow.d.ts +6 -0
- package/dist/core/flow.js +99 -0
- package/dist/core/gates.d.ts +5 -0
- package/dist/core/gates.js +71 -0
- package/dist/core/instructions.d.ts +21 -0
- package/dist/core/instructions.js +102 -0
- package/dist/core/source-operations.d.ts +25 -0
- package/dist/core/source-operations.js +94 -0
- package/dist/core/sources.d.ts +33 -0
- package/dist/core/sources.js +234 -0
- package/dist/core/state.d.ts +15 -0
- package/dist/core/state.js +75 -0
- package/dist/core/status.d.ts +31 -0
- package/dist/core/status.js +22 -0
- package/dist/core/tasks.d.ts +15 -0
- package/dist/core/tasks.js +143 -0
- package/dist/core/todo.d.ts +31 -0
- package/dist/core/todo.js +114 -0
- package/dist/core/types.d.ts +297 -0
- package/dist/core/types.js +227 -0
- package/dist/core/verify.d.ts +229 -0
- package/dist/core/verify.js +391 -0
- package/dist/core/worker-evidence.d.ts +71 -0
- package/dist/core/worker-evidence.js +253 -0
- package/dist/core/worker.d.ts +22 -0
- package/dist/core/worker.js +478 -0
- package/dist/delivery/claude-install.d.ts +4 -0
- package/dist/delivery/claude-install.js +44 -0
- package/dist/delivery/distribution.d.ts +3 -0
- package/dist/delivery/distribution.js +53 -0
- package/dist/delivery/skills.d.ts +36 -0
- package/dist/delivery/skills.js +178 -0
- package/dist/figma/cache.d.ts +19 -0
- package/dist/figma/cache.js +115 -0
- package/dist/figma/claude.d.ts +14 -0
- package/dist/figma/claude.js +121 -0
- package/dist/figma/collect.d.ts +3 -0
- package/dist/figma/collect.js +57 -0
- package/dist/figma/index.d.ts +35 -0
- package/dist/figma/index.js +178 -0
- package/dist/figma/media.d.ts +5 -0
- package/dist/figma/media.js +77 -0
- package/dist/figma/response.d.ts +9 -0
- package/dist/figma/response.js +27 -0
- package/dist/hooks/command-context.d.ts +19 -0
- package/dist/hooks/command-context.js +33 -0
- package/dist/hooks/engine.d.ts +61 -0
- package/dist/hooks/engine.js +314 -0
- package/dist/hooks/plugin-runtime.d.ts +188 -0
- package/dist/hooks/plugin-runtime.js +198 -0
- package/dist/index.d.ts +99 -0
- package/dist/index.js +185 -0
- package/dist/marketplace/adapters.d.ts +202 -0
- package/dist/marketplace/adapters.js +203 -0
- package/dist/marketplace/builtin.d.ts +9 -0
- package/dist/marketplace/builtin.js +9 -0
- package/dist/marketplace/install.d.ts +38 -0
- package/dist/marketplace/install.js +108 -0
- package/dist/marketplace/registry.d.ts +71 -0
- package/dist/marketplace/registry.js +342 -0
- package/dist/marketplace/upgrade.d.ts +39 -0
- package/dist/marketplace/upgrade.js +104 -0
- package/dist/shared/command-log.d.ts +9 -0
- package/dist/shared/command-log.js +55 -0
- package/dist/shared/errors.d.ts +7 -0
- package/dist/shared/errors.js +14 -0
- package/dist/shared/exec.d.ts +16 -0
- package/dist/shared/exec.js +86 -0
- package/dist/shared/fs.d.ts +18 -0
- package/dist/shared/fs.js +93 -0
- package/dist/shared/git.d.ts +22 -0
- package/dist/shared/git.js +132 -0
- package/dist/shared/hash.d.ts +5 -0
- package/dist/shared/hash.js +29 -0
- package/dist/shared/paths.d.ts +16 -0
- package/dist/shared/paths.js +97 -0
- package/dist/shared/pkg.d.ts +3 -0
- package/dist/shared/pkg.js +23 -0
- package/dist/shared/version.d.ts +9 -0
- package/dist/shared/version.js +34 -0
- package/dist/usage/analyze.d.ts +5 -0
- package/dist/usage/analyze.js +212 -0
- package/dist/usage/claude.d.ts +9 -0
- package/dist/usage/claude.js +59 -0
- package/dist/usage/collect.d.ts +11 -0
- package/dist/usage/collect.js +71 -0
- package/dist/usage/doctor.d.ts +72 -0
- package/dist/usage/doctor.js +121 -0
- package/dist/usage/parse.d.ts +28 -0
- package/dist/usage/parse.js +345 -0
- package/dist/usage/types.d.ts +103 -0
- package/dist/usage/types.js +2 -0
- package/docs/getting-started.md +20 -0
- package/docs/guides/configuration.md +33 -0
- package/docs/guides/cost-benchmark.md +88 -0
- package/docs/guides/plugin-migration.md +65 -0
- package/docs/guides/smart-figma.md +51 -0
- package/docs/guides/source-reading.md +35 -0
- package/docs/guides/ui-conventions-template.md +39 -0
- package/docs/guides/worker-evidence.md +73 -0
- package/docs/guides/workflow.md +74 -0
- package/docs/maintainers/architecture.md +27 -0
- package/docs/maintainers/plugin-runtime-contract.md +55 -0
- package/package.json +78 -0
- package/plugin.json +9 -0
- package/presets/backend-service.yaml +19 -0
- package/presets/flutter-mobile/plugin-hooks.json +68 -0
- package/presets/flutter-mobile/ui-conventions.md +122 -0
- package/presets/flutter-mobile.yaml +205 -0
- package/presets/web-product.yaml +23 -0
- package/skills/opsx-apply/SKILL.md +35 -0
- package/skills/opsx-apply/nodes/blocked.md +7 -0
- package/skills/opsx-apply/nodes/code-task.md +29 -0
- package/skills/opsx-apply/nodes/gate-a.md +9 -0
- package/skills/opsx-apply/nodes/stop.md +5 -0
- package/skills/opsx-apply/nodes/task-build.md +50 -0
- package/skills/opsx-apply/nodes/task-repair.md +20 -0
- package/skills/opsx-apply/nodes/task-scout.md +28 -0
- package/skills/opsx-apply/nodes/task-verify.md +38 -0
- package/skills/opsx-archive/SKILL.md +24 -0
- package/skills/opsx-archive/nodes/archive.md +15 -0
- package/skills/opsx-explore/SKILL.md +20 -0
- package/skills/opsx-explore/nodes/explore.md +3 -0
- package/skills/opsx-fix-verify/SKILL.md +30 -0
- package/skills/opsx-fix-verify/nodes/repair.md +11 -0
- package/skills/opsx-fix-verify/nodes/reverify.md +3 -0
- package/skills/opsx-fix-verify/nodes/stop.md +3 -0
- package/skills/opsx-gate-decision/SKILL.md +20 -0
- package/skills/opsx-gate-decision/nodes/decide.md +15 -0
- package/skills/opsx-propose/SKILL.md +36 -0
- package/skills/opsx-propose/nodes/blocked.md +13 -0
- package/skills/opsx-propose/nodes/collect.md +35 -0
- package/skills/opsx-propose/nodes/parse.md +72 -0
- package/skills/opsx-propose/nodes/plan.md +65 -0
- package/skills/opsx-propose/nodes/stop.md +17 -0
- package/skills/opsx-propose/nodes/tech.md +77 -0
- package/skills/opsx-propose/scripts/read-json-source.mjs +22 -0
- package/skills/opsx-resume/SKILL.md +21 -0
- package/skills/opsx-resume/nodes/route.md +12 -0
- package/skills/opsx-source-update/SKILL.md +23 -0
- package/skills/opsx-source-update/nodes/update.md +14 -0
- package/skills/opsx-verify/SKILL.md +32 -0
- package/skills/opsx-verify/nodes/blocked.md +3 -0
- package/skills/opsx-verify/nodes/failed.md +3 -0
- package/skills/opsx-verify/nodes/gate-b.md +7 -0
- package/skills/opsx-verify/nodes/stop.md +5 -0
- package/skills/opsx-verify/nodes/verify.md +10 -0
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
import { exists, listDirs, readText, writeText } from '../shared/fs.js';
|
|
3
|
+
import { AppError } from '../shared/errors.js';
|
|
4
|
+
import { archiveDir, changeDir, OPENSPEC_DIR, resolveInside } from '../shared/paths.js';
|
|
5
|
+
import { createState, hasState, loadState, saveState } from './state.js';
|
|
6
|
+
export function changeRoot(projectRoot, changeName) {
|
|
7
|
+
assertChangeName(changeName);
|
|
8
|
+
return resolveInside(projectRoot, changeDir(changeName), 'change_path_outside_project');
|
|
9
|
+
}
|
|
10
|
+
export function createChange(projectRoot, name, description) {
|
|
11
|
+
assertChangeName(name);
|
|
12
|
+
const dir = changeRoot(projectRoot, name);
|
|
13
|
+
if (exists(dir))
|
|
14
|
+
throw new AppError('change_exists', `change 已存在: ${name}`);
|
|
15
|
+
writeText(path.join(dir, '.openspec.yaml'), 'schema: spec-product\n');
|
|
16
|
+
if (description)
|
|
17
|
+
writeText(path.join(dir, 'README.md'), `${description}\n`);
|
|
18
|
+
const state = createState(name);
|
|
19
|
+
saveState(projectRoot, state);
|
|
20
|
+
return state;
|
|
21
|
+
}
|
|
22
|
+
export function listChangeNames(projectRoot) {
|
|
23
|
+
return listDirs(resolveInside(projectRoot, path.posix.join(OPENSPEC_DIR, 'changes'), 'changes_path_outside_project')).sort();
|
|
24
|
+
}
|
|
25
|
+
export function requireChange(projectRoot, changeName) {
|
|
26
|
+
if (!exists(changeRoot(projectRoot, changeName))) {
|
|
27
|
+
throw new AppError('change_not_found', `找不到 change: ${changeName}`);
|
|
28
|
+
}
|
|
29
|
+
if (!hasState(projectRoot, changeName)) {
|
|
30
|
+
throw new AppError('change_state_missing', `change 缺少权威状态文件: ${changeName}`);
|
|
31
|
+
}
|
|
32
|
+
return loadState(projectRoot, changeName);
|
|
33
|
+
}
|
|
34
|
+
export function resolveChangeName(projectRoot, requested) {
|
|
35
|
+
if (requested)
|
|
36
|
+
return requested;
|
|
37
|
+
const names = listChangeNames(projectRoot);
|
|
38
|
+
if (names.length === 1)
|
|
39
|
+
return names[0];
|
|
40
|
+
throw new AppError('change_ambiguous', '请通过 --change 指定 change 名称', { names });
|
|
41
|
+
}
|
|
42
|
+
export function artifactPath(projectRoot, changeName, relative) {
|
|
43
|
+
return resolveInside(changeRoot(projectRoot, changeName), relative, 'artifact_path_outside_change');
|
|
44
|
+
}
|
|
45
|
+
export function artifactRel(changeName, relative) {
|
|
46
|
+
assertChangeName(changeName);
|
|
47
|
+
resolveInside('/__openspec_change__', relative, 'artifact_path_outside_change');
|
|
48
|
+
return `${changeDir(changeName)}/${relative}`;
|
|
49
|
+
}
|
|
50
|
+
export function stampArchivePath(projectRoot, changeName) {
|
|
51
|
+
const day = new Date().toISOString().slice(0, 10);
|
|
52
|
+
let stamp = day;
|
|
53
|
+
let index = 1;
|
|
54
|
+
while (exists(resolveInside(projectRoot, archiveDir(stamp, changeName), 'archive_path_outside_project'))) {
|
|
55
|
+
index += 1;
|
|
56
|
+
stamp = `${day}-${index}`;
|
|
57
|
+
}
|
|
58
|
+
return archiveDir(stamp, changeName);
|
|
59
|
+
}
|
|
60
|
+
export function assertChangeName(name) {
|
|
61
|
+
if (!/^[a-z0-9][a-z0-9-]*$/.test(name) || name.length > 80) {
|
|
62
|
+
throw new AppError('invalid_change_name', 'change 名称仅允许小写字母、数字与连字符');
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
export function readOptional(projectRoot, changeName, relative) {
|
|
66
|
+
const file = artifactPath(projectRoot, changeName, relative);
|
|
67
|
+
return exists(file) ? readText(file) : null;
|
|
68
|
+
}
|
|
69
|
+
//# sourceMappingURL=change.js.map
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { ChangeState, WorkerPacket } from './types.js';
|
|
2
|
+
/** A retry's new edits and the task's retained, still-reviewable output are distinct. */
|
|
3
|
+
export declare function codeChangeScope(root: string, state: ChangeState, packet: Pick<WorkerPacket, 'workerId' | 'stepId'>): {
|
|
4
|
+
workerChangedFiles: string[];
|
|
5
|
+
changedFiles: string[];
|
|
6
|
+
changeChangedFiles: string[];
|
|
7
|
+
};
|
|
8
|
+
export declare function retainCodeChanges(root: string, state: ChangeState, packet: Pick<WorkerPacket, 'workerId' | 'stepId'>, files: string[], pending?: boolean): void;
|
|
9
|
+
//# sourceMappingURL=code-changes.d.ts.map
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { exists, readJson, writeJson } from '../shared/fs.js';
|
|
2
|
+
import { changedFilesSince, parseDiffBaseline } from '../shared/git.js';
|
|
3
|
+
import { resolveInside } from '../shared/paths.js';
|
|
4
|
+
import { AppError } from '../shared/errors.js';
|
|
5
|
+
import { artifactRel } from './change.js';
|
|
6
|
+
const union = (...lists) => [...new Set(lists.flat())].sort();
|
|
7
|
+
/** A retry's new edits and the task's retained, still-reviewable output are distinct. */
|
|
8
|
+
export function codeChangeScope(root, state, packet) {
|
|
9
|
+
const relative = artifactRel(state.changeName, `.opsx/workers/${packet.workerId}.baseline.json`);
|
|
10
|
+
const file = resolveInside(root, relative);
|
|
11
|
+
if (!exists(file))
|
|
12
|
+
throw new AppError('worker_baseline_missing', `缺少 worker baseline: ${packet.workerId}`);
|
|
13
|
+
const workerChangedFiles = changedFilesSince(root, parseDiffBaseline(readJson(file)));
|
|
14
|
+
if (state.codeChanges) {
|
|
15
|
+
// Preserve the original change baseline and reject HEAD drift across cancellations.
|
|
16
|
+
changedFilesSince(root, parseDiffBaseline(readJson(resolveInside(root, state.codeChanges.baseline))));
|
|
17
|
+
}
|
|
18
|
+
const retained = state.codeChanges?.filesByStep[packet.stepId] ?? [];
|
|
19
|
+
const changedFiles = union(workerChangedFiles, retained);
|
|
20
|
+
return { workerChangedFiles, changedFiles, changeChangedFiles: union(state.engineChangedFiles, changedFiles) };
|
|
21
|
+
}
|
|
22
|
+
export function retainCodeChanges(root, state, packet, files, pending = false) {
|
|
23
|
+
state.codeChanges ??= { baseline: artifactRel(state.changeName, `.opsx/workers/${packet.workerId}.baseline.json`), filesByStep: {} };
|
|
24
|
+
state.codeChanges.filesByStep[packet.stepId] = union(state.codeChanges.filesByStep[packet.stepId] ?? [], files);
|
|
25
|
+
state.engineChangedFiles = union(state.engineChangedFiles, files);
|
|
26
|
+
state.codeChanges.pendingSteps = pending
|
|
27
|
+
? union(state.codeChanges.pendingSteps ?? [], files.length ? [packet.stepId] : [])
|
|
28
|
+
: (state.codeChanges.pendingSteps ?? []).filter((step) => step !== packet.stepId);
|
|
29
|
+
writeJson(resolveInside(root, artifactRel(state.changeName, '.opsx/code-changes.json')), state.codeChanges);
|
|
30
|
+
}
|
|
31
|
+
//# sourceMappingURL=code-changes.js.map
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { type ChangeState, type GateId, type NextAction, type Node } from './types.js';
|
|
2
|
+
export declare function nextAction(state: ChangeState): NextAction;
|
|
3
|
+
export declare function assertNode(state: ChangeState, allowed: Node[], action: string): void;
|
|
4
|
+
export declare function assertStepAllowed(state: ChangeState, stepId: string): void;
|
|
5
|
+
export declare function currentGate(state: ChangeState): GateId | null;
|
|
6
|
+
//# sourceMappingURL=flow.d.ts.map
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import { AppError } from '../shared/errors.js';
|
|
2
|
+
import { NODES } from './types.js';
|
|
3
|
+
import { nextPendingTask } from './tasks.js';
|
|
4
|
+
export function nextAction(state) {
|
|
5
|
+
if (state.lastIssue?.type === 'worker_blocked') {
|
|
6
|
+
return { action: 'need_user_input', changeName: state.changeName, questions: state.lastIssue.questions };
|
|
7
|
+
}
|
|
8
|
+
if (state.lastIssue?.type === 'worker_failed') {
|
|
9
|
+
if (state.lastIssue.stepId === 'verify' && state.lastIssue.fixable) {
|
|
10
|
+
return { action: 'fix_verify', changeName: state.changeName };
|
|
11
|
+
}
|
|
12
|
+
return {
|
|
13
|
+
action: 'retry_worker',
|
|
14
|
+
changeName: state.changeName,
|
|
15
|
+
stepId: state.lastIssue.stepId ?? currentStep(state),
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
switch (state.node) {
|
|
19
|
+
case NODES.COLLECT:
|
|
20
|
+
if (state.sourceRevision === 0)
|
|
21
|
+
return { action: 'add_sources', changeName: state.changeName };
|
|
22
|
+
if (state.collectedRevision < state.sourceRevision) {
|
|
23
|
+
return { action: 'collect_sources', changeName: state.changeName };
|
|
24
|
+
}
|
|
25
|
+
return { action: 'create_worker', changeName: state.changeName, stepId: 'parse' };
|
|
26
|
+
case NODES.PARSE:
|
|
27
|
+
return { action: 'create_worker', changeName: state.changeName, stepId: 'parse' };
|
|
28
|
+
case NODES.TECH:
|
|
29
|
+
return { action: 'create_worker', changeName: state.changeName, stepId: 'tech' };
|
|
30
|
+
case NODES.PLAN:
|
|
31
|
+
return { action: 'create_worker', changeName: state.changeName, stepId: 'plan' };
|
|
32
|
+
case NODES.GATE_A:
|
|
33
|
+
return { action: 'await_gate', changeName: state.changeName, gateId: 'gate-A' };
|
|
34
|
+
case NODES.CODE: {
|
|
35
|
+
if (state.repair) {
|
|
36
|
+
return { action: 'create_worker', changeName: state.changeName, stepId: 'repair-verify' };
|
|
37
|
+
}
|
|
38
|
+
const task = nextPendingTask(state);
|
|
39
|
+
return task
|
|
40
|
+
? { action: 'create_worker', changeName: state.changeName, stepId: `code-${task.id}` }
|
|
41
|
+
: { action: 'await_gate', changeName: state.changeName, gateId: 'gate-B' };
|
|
42
|
+
}
|
|
43
|
+
case NODES.GATE_B:
|
|
44
|
+
return { action: 'await_gate', changeName: state.changeName, gateId: 'gate-B' };
|
|
45
|
+
case NODES.VERIFY:
|
|
46
|
+
return { action: 'create_worker', changeName: state.changeName, stepId: 'verify' };
|
|
47
|
+
case NODES.GATE_C:
|
|
48
|
+
return { action: 'await_gate', changeName: state.changeName, gateId: 'gate-C' };
|
|
49
|
+
case NODES.COMPLETED:
|
|
50
|
+
return { action: 'archive', changeName: state.changeName };
|
|
51
|
+
default:
|
|
52
|
+
return { action: 'add_sources', changeName: state.changeName };
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
export function assertNode(state, allowed, action) {
|
|
56
|
+
if (!allowed.includes(state.node)) {
|
|
57
|
+
throw new AppError('invalid_node', `${action} 只能在 ${allowed.join(' / ')} 执行,当前节点是 ${state.node}`);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
export function assertStepAllowed(state, stepId) {
|
|
61
|
+
if (state.lastIssue?.type === 'worker_failed' || state.lastIssue?.type === 'worker_blocked') {
|
|
62
|
+
if (state.lastIssue.stepId === 'verify' && state.lastIssue.fixable) {
|
|
63
|
+
throw new AppError('fix_verify_required', '验证失败且可修复,请先运行 openspec verify fix');
|
|
64
|
+
}
|
|
65
|
+
if (state.lastIssue.stepId === stepId)
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
if (stepId === 'parse')
|
|
69
|
+
assertNode(state, [NODES.PARSE, NODES.COLLECT], 'worker create parse');
|
|
70
|
+
else if (stepId === 'tech')
|
|
71
|
+
assertNode(state, [NODES.TECH], 'worker create tech');
|
|
72
|
+
else if (stepId === 'plan')
|
|
73
|
+
assertNode(state, [NODES.PLAN], 'worker create plan');
|
|
74
|
+
else if (stepId.startsWith('code-'))
|
|
75
|
+
assertNode(state, [NODES.CODE], `worker create ${stepId}`);
|
|
76
|
+
else if (stepId === 'repair-verify') {
|
|
77
|
+
assertNode(state, [NODES.CODE], 'worker create repair-verify');
|
|
78
|
+
if (!state.repair)
|
|
79
|
+
throw new AppError('repair_not_requested', '当前没有 verify repair 请求');
|
|
80
|
+
}
|
|
81
|
+
else if (stepId === 'verify')
|
|
82
|
+
assertNode(state, [NODES.VERIFY], 'worker create verify');
|
|
83
|
+
else
|
|
84
|
+
throw new AppError('unsupported_step', `不支持的 worker step: ${stepId}`);
|
|
85
|
+
}
|
|
86
|
+
export function currentGate(state) {
|
|
87
|
+
if (state.node === NODES.GATE_A)
|
|
88
|
+
return 'gate-A';
|
|
89
|
+
if (state.node === NODES.GATE_B)
|
|
90
|
+
return 'gate-B';
|
|
91
|
+
if (state.node === NODES.GATE_C)
|
|
92
|
+
return 'gate-C';
|
|
93
|
+
return null;
|
|
94
|
+
}
|
|
95
|
+
function currentStep(state) {
|
|
96
|
+
const next = nextAction({ ...state, lastIssue: null });
|
|
97
|
+
return next.action === 'create_worker' || next.action === 'retry_worker' ? next.stepId : 'unknown';
|
|
98
|
+
}
|
|
99
|
+
//# sourceMappingURL=flow.js.map
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import { type ChangeState, type GateId } from './types.js';
|
|
2
|
+
export declare function approveGate(projectRoot: string, changeName: string, gateId: GateId, comment?: string): ChangeState;
|
|
3
|
+
export declare function rejectGate(projectRoot: string, changeName: string, gateId: GateId, reason: string): ChangeState;
|
|
4
|
+
export declare function resetGates(state: ChangeState): void;
|
|
5
|
+
//# sourceMappingURL=gates.d.ts.map
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { AppError } from '../shared/errors.js';
|
|
2
|
+
import { requireChange } from './change.js';
|
|
3
|
+
import { currentGate } from './flow.js';
|
|
4
|
+
import { appendEvent, emptyGates, saveState } from './state.js';
|
|
5
|
+
import { NODES } from './types.js';
|
|
6
|
+
import { commitSourceUpdate } from './sources.js';
|
|
7
|
+
import { loadVerifiedReport } from './verify.js';
|
|
8
|
+
export function approveGate(projectRoot, changeName, gateId, comment = '') {
|
|
9
|
+
const state = requireChange(projectRoot, changeName);
|
|
10
|
+
const current = currentGate(state);
|
|
11
|
+
if (current !== gateId) {
|
|
12
|
+
throw new AppError('gate_not_awaiting', `当前等待的是 ${current ?? '无'},不能批准 ${gateId}`);
|
|
13
|
+
}
|
|
14
|
+
const now = new Date().toISOString();
|
|
15
|
+
state.gates[gateId] = { status: 'approved', comment, updatedAt: now };
|
|
16
|
+
if (gateId === 'gate-A') {
|
|
17
|
+
state.node = NODES.CODE;
|
|
18
|
+
commitSourceUpdate(projectRoot, state);
|
|
19
|
+
}
|
|
20
|
+
if (gateId === 'gate-B')
|
|
21
|
+
state.node = NODES.VERIFY;
|
|
22
|
+
if (gateId === 'gate-C') {
|
|
23
|
+
if (!state.verifyRunId)
|
|
24
|
+
throw new AppError('verify_run_missing', 'Gate-C 缺少当前 verify run');
|
|
25
|
+
const report = loadVerifiedReport(projectRoot, changeName, state.verifyRunId);
|
|
26
|
+
if (report.status !== 'passed')
|
|
27
|
+
throw new AppError('qa_not_passed', 'Gate-C 只接受当前 verify run 的 passed report');
|
|
28
|
+
state.node = NODES.COMPLETED;
|
|
29
|
+
}
|
|
30
|
+
if (state.lastIssue?.type === 'gate_rejected' && state.lastIssue.gateId === gateId) {
|
|
31
|
+
state.lastIssue = null;
|
|
32
|
+
}
|
|
33
|
+
saveState(projectRoot, state);
|
|
34
|
+
appendEvent(projectRoot, changeName, 'gate.approved', { gateId, comment });
|
|
35
|
+
return state;
|
|
36
|
+
}
|
|
37
|
+
export function rejectGate(projectRoot, changeName, gateId, reason) {
|
|
38
|
+
const state = requireChange(projectRoot, changeName);
|
|
39
|
+
const current = currentGate(state);
|
|
40
|
+
if (current !== gateId) {
|
|
41
|
+
throw new AppError('gate_not_awaiting', `当前等待的是 ${current ?? '无'},不能拒绝 ${gateId}`);
|
|
42
|
+
}
|
|
43
|
+
const now = new Date().toISOString();
|
|
44
|
+
state.gates[gateId] = { status: 'rejected', reason, updatedAt: now };
|
|
45
|
+
state.lastIssue = { type: 'gate_rejected', gateId, reason, updatedAt: now };
|
|
46
|
+
if (gateId === 'gate-A') {
|
|
47
|
+
state.node = NODES.PLAN;
|
|
48
|
+
state.tasks = [];
|
|
49
|
+
}
|
|
50
|
+
else {
|
|
51
|
+
for (const task of state.tasks) {
|
|
52
|
+
if (task.status === 'completed')
|
|
53
|
+
task.status = 'pending';
|
|
54
|
+
}
|
|
55
|
+
state.node = NODES.CODE;
|
|
56
|
+
state.verifyRunId = null;
|
|
57
|
+
state.repair = null;
|
|
58
|
+
if (gateId === 'gate-C')
|
|
59
|
+
state.gates['gate-B'] = { status: 'pending' };
|
|
60
|
+
state.gates['gate-C'] = { status: 'pending' };
|
|
61
|
+
if (gateId === 'gate-B')
|
|
62
|
+
state.gates['gate-B'] = { status: 'pending' };
|
|
63
|
+
}
|
|
64
|
+
saveState(projectRoot, state);
|
|
65
|
+
appendEvent(projectRoot, changeName, 'gate.rejected', { gateId, reason });
|
|
66
|
+
return state;
|
|
67
|
+
}
|
|
68
|
+
export function resetGates(state) {
|
|
69
|
+
state.gates = emptyGates();
|
|
70
|
+
}
|
|
71
|
+
//# sourceMappingURL=gates.js.map
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { ChangeState, NextAction, WorkflowName, LastIssue } from './types.js';
|
|
2
|
+
export interface Instructions {
|
|
3
|
+
protocol: 1;
|
|
4
|
+
workflow: WorkflowName;
|
|
5
|
+
changeName: string;
|
|
6
|
+
node: string;
|
|
7
|
+
next: NextAction;
|
|
8
|
+
nodeFile: string;
|
|
9
|
+
progress?: {
|
|
10
|
+
total: number;
|
|
11
|
+
completed: number;
|
|
12
|
+
};
|
|
13
|
+
blocker?: LastIssue;
|
|
14
|
+
}
|
|
15
|
+
export declare function buildInstructions(workflow: WorkflowName, state: ChangeState): Instructions;
|
|
16
|
+
/** Mutation responses carry the next instruction so controllers need no status round trip. */
|
|
17
|
+
export declare function controlSummary(state: ChangeState, fields?: Record<string, unknown>): Instructions & Record<string, unknown>;
|
|
18
|
+
export declare function inferWorkflow(state: Pick<ChangeState, 'node' | 'repair'>): WorkflowName;
|
|
19
|
+
export declare function nodeFileFor(workflow: WorkflowName, next: NextAction, state: ChangeState): string;
|
|
20
|
+
export declare function skillAbs(rel: string): string;
|
|
21
|
+
//# sourceMappingURL=instructions.d.ts.map
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
import { packageRoot } from '../shared/pkg.js';
|
|
3
|
+
import { nextAction } from './flow.js';
|
|
4
|
+
import { taskProgress } from './tasks.js';
|
|
5
|
+
export function buildInstructions(workflow, state) {
|
|
6
|
+
const next = nextAction(state);
|
|
7
|
+
const payload = {
|
|
8
|
+
protocol: 1,
|
|
9
|
+
workflow,
|
|
10
|
+
changeName: state.changeName,
|
|
11
|
+
node: state.node,
|
|
12
|
+
next,
|
|
13
|
+
nodeFile: skillAbs(nodeFileFor(workflow, next, state)),
|
|
14
|
+
};
|
|
15
|
+
if (state.tasks.length > 0)
|
|
16
|
+
payload.progress = taskProgress(state);
|
|
17
|
+
return payload;
|
|
18
|
+
}
|
|
19
|
+
/** Mutation responses carry the next instruction so controllers need no status round trip. */
|
|
20
|
+
export function controlSummary(state, fields = {}) {
|
|
21
|
+
return { ...buildInstructions(inferWorkflow(state), state), ...(state.lastIssue ? { blocker: state.lastIssue } : {}), ...fields };
|
|
22
|
+
}
|
|
23
|
+
export function inferWorkflow(state) {
|
|
24
|
+
if (state.repair && state.node === 'code')
|
|
25
|
+
return 'opsx-fix-verify';
|
|
26
|
+
if (['collect', 'parse', 'tech', 'plan', 'gate_a'].includes(state.node))
|
|
27
|
+
return 'opsx-propose';
|
|
28
|
+
if (['code', 'gate_b'].includes(state.node))
|
|
29
|
+
return 'opsx-apply';
|
|
30
|
+
if (['verify', 'gate_c'].includes(state.node))
|
|
31
|
+
return 'opsx-verify';
|
|
32
|
+
return 'opsx-archive';
|
|
33
|
+
}
|
|
34
|
+
export function nodeFileFor(workflow, next, state) {
|
|
35
|
+
const base = `skills/${workflow}/nodes`;
|
|
36
|
+
const entryNodes = {
|
|
37
|
+
'opsx-source-update': 'update', 'opsx-archive': 'archive', 'opsx-gate-decision': 'decide',
|
|
38
|
+
'opsx-resume': 'route', 'opsx-explore': 'explore',
|
|
39
|
+
};
|
|
40
|
+
if (entryNodes[workflow])
|
|
41
|
+
return `${base}/${entryNodes[workflow]}.md`;
|
|
42
|
+
if (next.action === 'need_user_input' || next.action === 'retry_worker') {
|
|
43
|
+
return workflow === 'opsx-fix-verify' ? `${base}/repair.md` : `${base}/blocked.md`;
|
|
44
|
+
}
|
|
45
|
+
if (workflow === 'opsx-apply') {
|
|
46
|
+
if (next.action === 'await_gate' && next.gateId === 'gate-A')
|
|
47
|
+
return `${base}/gate-a.md`;
|
|
48
|
+
if (next.action === 'create_worker' && next.stepId.startsWith('code-'))
|
|
49
|
+
return `${base}/code-task.md`;
|
|
50
|
+
if (next.action === 'await_gate' && next.gateId === 'gate-B')
|
|
51
|
+
return `${base}/stop.md`;
|
|
52
|
+
return `${base}/blocked.md`;
|
|
53
|
+
}
|
|
54
|
+
if (workflow === 'opsx-propose') {
|
|
55
|
+
if (next.action === 'add_sources' || next.action === 'collect_sources')
|
|
56
|
+
return `${base}/collect.md`;
|
|
57
|
+
if (next.action === 'create_worker' && next.stepId === 'parse')
|
|
58
|
+
return `${base}/parse.md`;
|
|
59
|
+
if (next.action === 'create_worker' && next.stepId === 'tech')
|
|
60
|
+
return `${base}/tech.md`;
|
|
61
|
+
if (next.action === 'create_worker' && next.stepId === 'plan')
|
|
62
|
+
return `${base}/plan.md`;
|
|
63
|
+
if (next.action === 'await_gate' && next.gateId === 'gate-A')
|
|
64
|
+
return `${base}/stop.md`;
|
|
65
|
+
return `${base}/blocked.md`;
|
|
66
|
+
}
|
|
67
|
+
if (workflow === 'opsx-verify') {
|
|
68
|
+
if (next.action === 'await_gate' && next.gateId === 'gate-B')
|
|
69
|
+
return `${base}/gate-b.md`;
|
|
70
|
+
if (next.action === 'create_worker' && next.stepId === 'verify')
|
|
71
|
+
return `${base}/verify.md`;
|
|
72
|
+
if (next.action === 'await_gate' && next.gateId === 'gate-C')
|
|
73
|
+
return `${base}/stop.md`;
|
|
74
|
+
if (next.action === 'fix_verify')
|
|
75
|
+
return `${base}/failed.md`;
|
|
76
|
+
return `${base}/blocked.md`;
|
|
77
|
+
}
|
|
78
|
+
if (workflow === 'opsx-fix-verify') {
|
|
79
|
+
if (next.action === 'fix_verify' || (next.action === 'create_worker' && next.stepId === 'repair-verify')) {
|
|
80
|
+
return `${base}/repair.md`;
|
|
81
|
+
}
|
|
82
|
+
if (next.action === 'create_worker' && next.stepId === 'verify')
|
|
83
|
+
return `${base}/reverify.md`;
|
|
84
|
+
return `${base}/stop.md`;
|
|
85
|
+
}
|
|
86
|
+
if (workflow === 'opsx-archive')
|
|
87
|
+
return `${base}/archive.md`;
|
|
88
|
+
if (workflow === 'opsx-source-update')
|
|
89
|
+
return `${base}/update.md`;
|
|
90
|
+
if (workflow === 'opsx-gate-decision')
|
|
91
|
+
return `${base}/decide.md`;
|
|
92
|
+
if (workflow === 'opsx-resume')
|
|
93
|
+
return `${base}/route.md`;
|
|
94
|
+
if (workflow === 'opsx-explore')
|
|
95
|
+
return `${base}/explore.md`;
|
|
96
|
+
void state;
|
|
97
|
+
return `${base}/blocked.md`;
|
|
98
|
+
}
|
|
99
|
+
export function skillAbs(rel) {
|
|
100
|
+
return path.join(packageRoot(), rel);
|
|
101
|
+
}
|
|
102
|
+
//# sourceMappingURL=instructions.js.map
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { type PluginBinding, type PluginContext } from '../hooks/plugin-runtime.js';
|
|
2
|
+
export declare function operationContext(root: string, change: string, binding: PluginBinding, outputDir: string, inputFile?: string, worker?: PluginContext['worker']): {
|
|
3
|
+
context: PluginContext;
|
|
4
|
+
files: string[];
|
|
5
|
+
};
|
|
6
|
+
export declare function readSource(root: string, change: string, sourceId: string, inputFile?: string, use?: string): {
|
|
7
|
+
sourceId: string;
|
|
8
|
+
artifact: string;
|
|
9
|
+
artifactSha256: string;
|
|
10
|
+
raw: string;
|
|
11
|
+
runId?: undefined;
|
|
12
|
+
data?: undefined;
|
|
13
|
+
result?: undefined;
|
|
14
|
+
receipt?: undefined;
|
|
15
|
+
} | {
|
|
16
|
+
sourceId: string;
|
|
17
|
+
runId: `${string}-${string}-${string}-${string}-${string}`;
|
|
18
|
+
data: unknown;
|
|
19
|
+
result: string;
|
|
20
|
+
receipt: string;
|
|
21
|
+
artifact?: undefined;
|
|
22
|
+
artifactSha256?: undefined;
|
|
23
|
+
raw?: undefined;
|
|
24
|
+
};
|
|
25
|
+
//# sourceMappingURL=source-operations.d.ts.map
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
import { randomUUID } from 'node:crypto';
|
|
3
|
+
import { configPathOf } from '../config/load.js';
|
|
4
|
+
import { resolveAdapterEntry } from '../marketplace/adapters.js';
|
|
5
|
+
import { readPluginInput, runPluginCommand, sameIdentity } from '../hooks/plugin-runtime.js';
|
|
6
|
+
import { sha256File } from '../shared/hash.js';
|
|
7
|
+
import { readText, writeJson } from '../shared/fs.js';
|
|
8
|
+
import { AppError } from '../shared/errors.js';
|
|
9
|
+
import { artifactPath, artifactRel, requireChange } from './change.js';
|
|
10
|
+
import { loadLedger } from './sources.js';
|
|
11
|
+
export function operationContext(root, change, binding, outputDir, inputFile, worker) {
|
|
12
|
+
const state = requireChange(root, change);
|
|
13
|
+
const params = readPluginInput(root, binding, inputFile);
|
|
14
|
+
const context = {
|
|
15
|
+
version: 1, operation: binding.operation, projectRoot: root,
|
|
16
|
+
change: { name: change, root: artifactPath(root, change, '.') },
|
|
17
|
+
input: params.input, outputDir, ...(worker ? { worker } : {}),
|
|
18
|
+
};
|
|
19
|
+
const configPath = configPathOf(root);
|
|
20
|
+
if (configPath)
|
|
21
|
+
params.files.push(path.relative(root, configPath));
|
|
22
|
+
if (binding.kind === 'adapter') {
|
|
23
|
+
if (state.sourceRevision !== state.collectedRevision)
|
|
24
|
+
throw new AppError('source_not_collected', '来源尚未完成采集');
|
|
25
|
+
const { sourceId, sourceIds } = params.input;
|
|
26
|
+
const plural = Object.hasOwn(params.input, 'sourceIds');
|
|
27
|
+
const singular = Object.hasOwn(params.input, 'sourceId');
|
|
28
|
+
const ids = plural ? sourceIds : [sourceId];
|
|
29
|
+
if (plural === singular || !Array.isArray(ids) || ids.length === 0
|
|
30
|
+
|| ids.some((id) => typeof id !== 'string' || !id.trim()) || new Set(ids).size !== ids.length) {
|
|
31
|
+
throw new AppError('invalid_source_selection', 'input.sourceId 与非空、无重复的 sourceIds 二选一');
|
|
32
|
+
}
|
|
33
|
+
const ledger = loadLedger(root, change, state.sourceRevision);
|
|
34
|
+
const adapter = resolveAdapterEntry(root, binding.use);
|
|
35
|
+
const sources = ids.map((id) => {
|
|
36
|
+
const item = ledger.items.find((item) => item.id === id);
|
|
37
|
+
if (!item?.artifact)
|
|
38
|
+
throw new AppError('source_artifact_missing', `来源未采集: ${id}`);
|
|
39
|
+
if (item.adapter && (item.adapter.use !== binding.use || !sameIdentity(item.adapter.identity, binding.identity)))
|
|
40
|
+
throw new AppError('source_adapter_mismatch', `操作入口与冻结来源的 adapter 不一致: ${id}`);
|
|
41
|
+
if (adapter.entry.capability !== item.capability)
|
|
42
|
+
throw new AppError('adapter_capability_mismatch', `adapter 与来源 capability 不一致: ${id}`);
|
|
43
|
+
const artifact = artifactPath(root, change, item.artifact);
|
|
44
|
+
const artifactSha256 = sha256File(artifact);
|
|
45
|
+
if (item.artifactSha256 && item.artifactSha256 !== artifactSha256)
|
|
46
|
+
throw new AppError('source_artifact_changed', `冻结来源已变化,请走 source update: ${id}`);
|
|
47
|
+
return { id: item.id, capability: item.capability, kind: item.kind, locator: item.locator, artifact, artifactSha256 };
|
|
48
|
+
});
|
|
49
|
+
if (plural)
|
|
50
|
+
context.sources = sources;
|
|
51
|
+
else
|
|
52
|
+
context.source = sources[0];
|
|
53
|
+
params.files.push(artifactRel(change, `sources/sources-r${state.sourceRevision}.json`));
|
|
54
|
+
}
|
|
55
|
+
return { context, files: params.files };
|
|
56
|
+
}
|
|
57
|
+
export function readSource(root, change, sourceId, inputFile, use) {
|
|
58
|
+
const state = requireChange(root, change);
|
|
59
|
+
if (state.sourceRevision !== state.collectedRevision)
|
|
60
|
+
throw new AppError('source_not_collected', '来源尚未完成采集');
|
|
61
|
+
const source = loadLedger(root, change, state.sourceRevision).items.find((item) => item.id === sourceId);
|
|
62
|
+
if (!source?.artifact)
|
|
63
|
+
throw new AppError('source_artifact_missing', `来源未采集: ${sourceId}`);
|
|
64
|
+
const absolute = artifactPath(root, change, source.artifact);
|
|
65
|
+
const digest = sha256File(absolute);
|
|
66
|
+
if (source.artifactSha256 && source.artifactSha256 !== digest)
|
|
67
|
+
throw new AppError('source_artifact_changed', '冻结来源已变化,请走 source update');
|
|
68
|
+
const selector = use ?? source.adapter?.use;
|
|
69
|
+
if (!selector) {
|
|
70
|
+
if (inputFile)
|
|
71
|
+
throw new AppError('source_reader_unbound', '定向读取需要显式 --use;原文读取无需 input');
|
|
72
|
+
return { sourceId, artifact: source.artifact, artifactSha256: digest, raw: readText(absolute) };
|
|
73
|
+
}
|
|
74
|
+
const resolved = resolveAdapterEntry(root, selector);
|
|
75
|
+
if (!resolved.entry.operations.read) {
|
|
76
|
+
if (use || inputFile)
|
|
77
|
+
throw new AppError('adapter_operation_missing', `adapter 未声明 read: ${selector}`);
|
|
78
|
+
return { sourceId, artifact: source.artifact, artifactSha256: digest, raw: readText(absolute) };
|
|
79
|
+
}
|
|
80
|
+
const params = readPluginInput(root, {}, inputFile);
|
|
81
|
+
if (Object.hasOwn(params.input, 'sourceIds') || (Object.hasOwn(params.input, 'sourceId') && params.input.sourceId !== sourceId)) {
|
|
82
|
+
throw new AppError('invalid_source_selection', 'sources read 的 --source 不能被 input 覆盖或同时提供 sourceIds');
|
|
83
|
+
}
|
|
84
|
+
const binding = { use: selector, kind: 'adapter', operation: 'read', identity: resolved.identity, input: { ...params.input, sourceId } };
|
|
85
|
+
const runId = randomUUID();
|
|
86
|
+
const outputDir = artifactPath(root, change, `.opsx/operations/${runId}`);
|
|
87
|
+
const prepared = operationContext(root, change, binding, outputDir);
|
|
88
|
+
const result = runPluginCommand(root, binding, prepared.context, [...prepared.files, ...params.files]);
|
|
89
|
+
writeJson(path.join(outputDir, 'receipt.json'), { runId, ok: result.ok, code: result.code, ...result.receipt });
|
|
90
|
+
if (!result.ok)
|
|
91
|
+
throw new AppError('source_read_failed', '来源读取失败,查看完整输出', { outputDir, stderr: result.stderrPath });
|
|
92
|
+
return { sourceId, runId, data: result.data, result: path.relative(root, result.stdoutPath), receipt: path.relative(root, path.join(outputDir, 'receipt.json')) };
|
|
93
|
+
}
|
|
94
|
+
//# sourceMappingURL=source-operations.js.map
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import type { ProjectConfig } from '../config/schema.js';
|
|
2
|
+
import { type ChangeState, type SourceCapability, type SourceItem, type SourcesLedger } from './types.js';
|
|
3
|
+
export declare function addSources(projectRoot: string, changeName: string, input: {
|
|
4
|
+
notes?: string;
|
|
5
|
+
sources?: Array<{
|
|
6
|
+
capability: string;
|
|
7
|
+
kind: SourceItem['kind'];
|
|
8
|
+
locator: string;
|
|
9
|
+
}>;
|
|
10
|
+
}): SourcesLedger;
|
|
11
|
+
export declare function collect(projectRoot: string, changeName: string, config: ProjectConfig, options?: {
|
|
12
|
+
refresh?: boolean;
|
|
13
|
+
}): {
|
|
14
|
+
ledger: SourcesLedger;
|
|
15
|
+
artifacts: string[];
|
|
16
|
+
deferred: string[];
|
|
17
|
+
covered: string[];
|
|
18
|
+
warnings: string[];
|
|
19
|
+
};
|
|
20
|
+
export declare function abortUpdate(projectRoot: string, changeName: string, reason: string): SourcesLedger;
|
|
21
|
+
export declare function commitSourceUpdate(projectRoot: string, state: ChangeState): void;
|
|
22
|
+
export declare function loadLedger(projectRoot: string, changeName: string, revision: number): SourcesLedger;
|
|
23
|
+
export declare function parseSourceArg(raw: string): {
|
|
24
|
+
capability: SourceCapability;
|
|
25
|
+
kind: SourceItem['kind'];
|
|
26
|
+
locator: string;
|
|
27
|
+
};
|
|
28
|
+
export declare function parseSourceFileArg(raw: string): {
|
|
29
|
+
capability: SourceCapability;
|
|
30
|
+
kind: 'file';
|
|
31
|
+
locator: string;
|
|
32
|
+
};
|
|
33
|
+
//# sourceMappingURL=sources.d.ts.map
|