@clawplays/ospec-cli 0.1.1
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/.ospec/templates/hooks/post-merge +8 -0
- package/.ospec/templates/hooks/pre-commit +8 -0
- package/LICENSE +21 -0
- package/README.md +549 -0
- package/README.zh-CN.md +549 -0
- package/assets/for-ai/en-US/ai-guide.md +98 -0
- package/assets/for-ai/en-US/execution-protocol.md +64 -0
- package/assets/for-ai/zh-CN/ai-guide.md +102 -0
- package/assets/for-ai/zh-CN/execution-protocol.md +68 -0
- package/assets/git-hooks/post-merge +12 -0
- package/assets/git-hooks/pre-commit +12 -0
- package/assets/global-skills/claude/ospec-change/SKILL.md +116 -0
- package/assets/global-skills/codex/ospec-change/SKILL.md +117 -0
- package/assets/global-skills/codex/ospec-change/agents/openai.yaml +7 -0
- package/assets/global-skills/codex/ospec-change/skill.yaml +19 -0
- package/assets/project-conventions/en-US/development-guide.md +32 -0
- package/assets/project-conventions/en-US/naming-conventions.md +51 -0
- package/assets/project-conventions/en-US/skill-conventions.md +40 -0
- package/assets/project-conventions/en-US/workflow-conventions.md +70 -0
- package/assets/project-conventions/zh-CN/development-guide.md +32 -0
- package/assets/project-conventions/zh-CN/naming-conventions.md +51 -0
- package/assets/project-conventions/zh-CN/skill-conventions.md +40 -0
- package/assets/project-conventions/zh-CN/workflow-conventions.md +74 -0
- package/dist/adapters/codex-stitch-adapter.js +420 -0
- package/dist/adapters/gemini-stitch-adapter.js +408 -0
- package/dist/adapters/playwright-checkpoint-adapter.js +2260 -0
- package/dist/advanced/BatchOperations.d.ts +36 -0
- package/dist/advanced/BatchOperations.js +159 -0
- package/dist/advanced/CachingLayer.d.ts +66 -0
- package/dist/advanced/CachingLayer.js +136 -0
- package/dist/advanced/FeatureUpdater.d.ts +46 -0
- package/dist/advanced/FeatureUpdater.js +151 -0
- package/dist/advanced/PerformanceMonitor.d.ts +52 -0
- package/dist/advanced/PerformanceMonitor.js +129 -0
- package/dist/advanced/StatePersistence.d.ts +61 -0
- package/dist/advanced/StatePersistence.js +168 -0
- package/dist/advanced/index.d.ts +14 -0
- package/dist/advanced/index.js +22 -0
- package/dist/cli/commands/config.d.ts +5 -0
- package/dist/cli/commands/config.js +6 -0
- package/dist/cli/commands/feature.d.ts +5 -0
- package/dist/cli/commands/feature.js +6 -0
- package/dist/cli/commands/index.d.ts +5 -0
- package/dist/cli/commands/index.js +6 -0
- package/dist/cli/commands/project.d.ts +5 -0
- package/dist/cli/commands/project.js +6 -0
- package/dist/cli/commands/validate.d.ts +5 -0
- package/dist/cli/commands/validate.js +6 -0
- package/dist/cli/index.d.ts +5 -0
- package/dist/cli/index.js +6 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.js +1007 -0
- package/dist/commands/ArchiveCommand.d.ts +14 -0
- package/dist/commands/ArchiveCommand.js +241 -0
- package/dist/commands/BaseCommand.d.ts +33 -0
- package/dist/commands/BaseCommand.js +46 -0
- package/dist/commands/BatchCommand.d.ts +5 -0
- package/dist/commands/BatchCommand.js +42 -0
- package/dist/commands/ChangesCommand.d.ts +3 -0
- package/dist/commands/ChangesCommand.js +71 -0
- package/dist/commands/DocsCommand.d.ts +5 -0
- package/dist/commands/DocsCommand.js +118 -0
- package/dist/commands/FinalizeCommand.d.ts +3 -0
- package/dist/commands/FinalizeCommand.js +24 -0
- package/dist/commands/IndexCommand.d.ts +5 -0
- package/dist/commands/IndexCommand.js +57 -0
- package/dist/commands/InitCommand.d.ts +5 -0
- package/dist/commands/InitCommand.js +65 -0
- package/dist/commands/NewCommand.d.ts +11 -0
- package/dist/commands/NewCommand.js +262 -0
- package/dist/commands/PluginsCommand.d.ts +58 -0
- package/dist/commands/PluginsCommand.js +2491 -0
- package/dist/commands/ProgressCommand.d.ts +5 -0
- package/dist/commands/ProgressCommand.js +103 -0
- package/dist/commands/QueueCommand.d.ts +10 -0
- package/dist/commands/QueueCommand.js +147 -0
- package/dist/commands/RunCommand.d.ts +13 -0
- package/dist/commands/RunCommand.js +200 -0
- package/dist/commands/SkillCommand.d.ts +31 -0
- package/dist/commands/SkillCommand.js +1216 -0
- package/dist/commands/SkillsCommand.d.ts +5 -0
- package/dist/commands/SkillsCommand.js +68 -0
- package/dist/commands/StatusCommand.d.ts +6 -0
- package/dist/commands/StatusCommand.js +140 -0
- package/dist/commands/UpdateCommand.d.ts +8 -0
- package/dist/commands/UpdateCommand.js +251 -0
- package/dist/commands/VerifyCommand.d.ts +5 -0
- package/dist/commands/VerifyCommand.js +278 -0
- package/dist/commands/WorkflowCommand.d.ts +12 -0
- package/dist/commands/WorkflowCommand.js +150 -0
- package/dist/commands/index.d.ts +43 -0
- package/dist/commands/index.js +85 -0
- package/dist/core/constants.d.ts +41 -0
- package/dist/core/constants.js +73 -0
- package/dist/core/errors.d.ts +36 -0
- package/dist/core/errors.js +72 -0
- package/dist/core/index.d.ts +7 -0
- package/dist/core/index.js +23 -0
- package/dist/core/types.d.ts +369 -0
- package/dist/core/types.js +3 -0
- package/dist/index.d.ts +11 -0
- package/dist/index.js +27 -0
- package/dist/presets/ProjectPresets.d.ts +41 -0
- package/dist/presets/ProjectPresets.js +190 -0
- package/dist/scaffolds/ProjectScaffoldPresets.d.ts +20 -0
- package/dist/scaffolds/ProjectScaffoldPresets.js +151 -0
- package/dist/services/ConfigManager.d.ts +14 -0
- package/dist/services/ConfigManager.js +386 -0
- package/dist/services/FeatureManager.d.ts +5 -0
- package/dist/services/FeatureManager.js +6 -0
- package/dist/services/FileService.d.ts +21 -0
- package/dist/services/FileService.js +152 -0
- package/dist/services/IndexBuilder.d.ts +12 -0
- package/dist/services/IndexBuilder.js +130 -0
- package/dist/services/Logger.d.ts +20 -0
- package/dist/services/Logger.js +48 -0
- package/dist/services/ProjectAssetRegistry.d.ts +12 -0
- package/dist/services/ProjectAssetRegistry.js +96 -0
- package/dist/services/ProjectAssetService.d.ts +49 -0
- package/dist/services/ProjectAssetService.js +223 -0
- package/dist/services/ProjectScaffoldCommandService.d.ts +73 -0
- package/dist/services/ProjectScaffoldCommandService.js +159 -0
- package/dist/services/ProjectScaffoldService.d.ts +44 -0
- package/dist/services/ProjectScaffoldService.js +507 -0
- package/dist/services/ProjectService.d.ts +209 -0
- package/dist/services/ProjectService.js +13239 -0
- package/dist/services/QueueService.d.ts +17 -0
- package/dist/services/QueueService.js +142 -0
- package/dist/services/RunService.d.ts +40 -0
- package/dist/services/RunService.js +420 -0
- package/dist/services/SkillParser.d.ts +30 -0
- package/dist/services/SkillParser.js +88 -0
- package/dist/services/StateManager.d.ts +16 -0
- package/dist/services/StateManager.js +127 -0
- package/dist/services/TemplateEngine.d.ts +43 -0
- package/dist/services/TemplateEngine.js +119 -0
- package/dist/services/TemplateGenerator.d.ts +40 -0
- package/dist/services/TemplateGenerator.js +273 -0
- package/dist/services/ValidationService.d.ts +19 -0
- package/dist/services/ValidationService.js +44 -0
- package/dist/services/Validator.d.ts +5 -0
- package/dist/services/Validator.js +6 -0
- package/dist/services/index.d.ts +52 -0
- package/dist/services/index.js +91 -0
- package/dist/services/templates/ExecutionTemplateBuilder.d.ts +12 -0
- package/dist/services/templates/ExecutionTemplateBuilder.js +300 -0
- package/dist/services/templates/ProjectTemplateBuilder.d.ts +38 -0
- package/dist/services/templates/ProjectTemplateBuilder.js +1897 -0
- package/dist/services/templates/TemplateBuilderBase.d.ts +19 -0
- package/dist/services/templates/TemplateBuilderBase.js +60 -0
- package/dist/services/templates/TemplateInputFactory.d.ts +16 -0
- package/dist/services/templates/TemplateInputFactory.js +298 -0
- package/dist/services/templates/templateTypes.d.ts +90 -0
- package/dist/services/templates/templateTypes.js +3 -0
- package/dist/tools/build-index.js +632 -0
- package/dist/utils/DateUtils.d.ts +18 -0
- package/dist/utils/DateUtils.js +40 -0
- package/dist/utils/PathUtils.d.ts +9 -0
- package/dist/utils/PathUtils.js +66 -0
- package/dist/utils/StringUtils.d.ts +26 -0
- package/dist/utils/StringUtils.js +47 -0
- package/dist/utils/helpers.d.ts +5 -0
- package/dist/utils/helpers.js +6 -0
- package/dist/utils/index.d.ts +7 -0
- package/dist/utils/index.js +23 -0
- package/dist/utils/logger.d.ts +5 -0
- package/dist/utils/logger.js +6 -0
- package/dist/utils/path.d.ts +5 -0
- package/dist/utils/path.js +6 -0
- package/dist/utils/subcommandHelp.d.ts +11 -0
- package/dist/utils/subcommandHelp.js +119 -0
- package/dist/workflow/ArchiveGate.d.ts +30 -0
- package/dist/workflow/ArchiveGate.js +93 -0
- package/dist/workflow/ConfigurableWorkflow.d.ts +89 -0
- package/dist/workflow/ConfigurableWorkflow.js +186 -0
- package/dist/workflow/HookSystem.d.ts +38 -0
- package/dist/workflow/HookSystem.js +66 -0
- package/dist/workflow/IndexRegenerator.d.ts +49 -0
- package/dist/workflow/IndexRegenerator.js +147 -0
- package/dist/workflow/PluginWorkflowComposer.d.ts +138 -0
- package/dist/workflow/PluginWorkflowComposer.js +239 -0
- package/dist/workflow/SkillUpdateEngine.d.ts +26 -0
- package/dist/workflow/SkillUpdateEngine.js +113 -0
- package/dist/workflow/VerificationSystem.d.ts +24 -0
- package/dist/workflow/VerificationSystem.js +116 -0
- package/dist/workflow/WorkflowEngine.d.ts +15 -0
- package/dist/workflow/WorkflowEngine.js +57 -0
- package/dist/workflow/index.d.ts +19 -0
- package/dist/workflow/index.js +32 -0
- package/package.json +78 -0
- package/scripts/postinstall.js +43 -0
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { QueuedChangeStatusItem } from '../core/types';
|
|
2
|
+
import { FileService } from './FileService';
|
|
3
|
+
import { ProjectService } from './ProjectService';
|
|
4
|
+
export declare class QueueService {
|
|
5
|
+
private fileService;
|
|
6
|
+
private projectService;
|
|
7
|
+
constructor(fileService: FileService, projectService: ProjectService);
|
|
8
|
+
listQueuedChangeNames(rootDir: string): Promise<string[]>;
|
|
9
|
+
getQueuedChanges(rootDir: string): Promise<QueuedChangeStatusItem[]>;
|
|
10
|
+
activateQueuedChange(rootDir: string, changeName: string, activationSource?: string): Promise<QueuedChangeStatusItem>;
|
|
11
|
+
activateNextQueuedChange(rootDir: string, activationSource?: string): Promise<QueuedChangeStatusItem | null>;
|
|
12
|
+
private buildQueuedChangeStatusItem;
|
|
13
|
+
private extractDescription;
|
|
14
|
+
private toRelativePath;
|
|
15
|
+
}
|
|
16
|
+
export declare function createQueueService(fileService: FileService, projectService: ProjectService): QueueService;
|
|
17
|
+
//# sourceMappingURL=QueueService.d.ts.map
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.QueueService = void 0;
|
|
7
|
+
exports.createQueueService = createQueueService;
|
|
8
|
+
const fs_extra_1 = __importDefault(require("fs-extra"));
|
|
9
|
+
const gray_matter_1 = __importDefault(require("gray-matter"));
|
|
10
|
+
const path_1 = __importDefault(require("path"));
|
|
11
|
+
const constants_1 = require("../core/constants");
|
|
12
|
+
class QueueService {
|
|
13
|
+
constructor(fileService, projectService) {
|
|
14
|
+
this.fileService = fileService;
|
|
15
|
+
this.projectService = projectService;
|
|
16
|
+
}
|
|
17
|
+
async listQueuedChangeNames(rootDir) {
|
|
18
|
+
const queuedChanges = await this.getQueuedChanges(rootDir);
|
|
19
|
+
return queuedChanges.map(change => change.name);
|
|
20
|
+
}
|
|
21
|
+
async getQueuedChanges(rootDir) {
|
|
22
|
+
const queuedDir = path_1.default.join(rootDir, constants_1.DIR_NAMES.CHANGES, constants_1.DIR_NAMES.QUEUED);
|
|
23
|
+
if (!(await this.fileService.exists(queuedDir))) {
|
|
24
|
+
return [];
|
|
25
|
+
}
|
|
26
|
+
const entries = await fs_extra_1.default.readdir(queuedDir, { withFileTypes: true });
|
|
27
|
+
const queuedChanges = [];
|
|
28
|
+
for (const entry of entries) {
|
|
29
|
+
if (!entry.isDirectory()) {
|
|
30
|
+
continue;
|
|
31
|
+
}
|
|
32
|
+
const item = await this.buildQueuedChangeStatusItem(rootDir, path_1.default.join(queuedDir, entry.name));
|
|
33
|
+
if (item) {
|
|
34
|
+
queuedChanges.push(item);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
queuedChanges.sort((left, right) => {
|
|
38
|
+
const leftQueuedAt = left.queuedAt ?? '';
|
|
39
|
+
const rightQueuedAt = right.queuedAt ?? '';
|
|
40
|
+
if (leftQueuedAt && rightQueuedAt && leftQueuedAt !== rightQueuedAt) {
|
|
41
|
+
return leftQueuedAt.localeCompare(rightQueuedAt);
|
|
42
|
+
}
|
|
43
|
+
if (leftQueuedAt && !rightQueuedAt) {
|
|
44
|
+
return -1;
|
|
45
|
+
}
|
|
46
|
+
if (!leftQueuedAt && rightQueuedAt) {
|
|
47
|
+
return 1;
|
|
48
|
+
}
|
|
49
|
+
return left.name.localeCompare(right.name);
|
|
50
|
+
});
|
|
51
|
+
return queuedChanges;
|
|
52
|
+
}
|
|
53
|
+
async activateQueuedChange(rootDir, changeName, activationSource = 'queue') {
|
|
54
|
+
const activeNames = await this.projectService.listActiveChangeNames(rootDir);
|
|
55
|
+
if (activeNames.length > 0) {
|
|
56
|
+
throw new Error(`Cannot activate queued change while active changes exist: ${activeNames.join(', ')}`);
|
|
57
|
+
}
|
|
58
|
+
const queuedPath = path_1.default.join(rootDir, constants_1.DIR_NAMES.CHANGES, constants_1.DIR_NAMES.QUEUED, changeName);
|
|
59
|
+
if (!(await this.fileService.exists(queuedPath))) {
|
|
60
|
+
throw new Error(`Queued change not found: ${changeName}`);
|
|
61
|
+
}
|
|
62
|
+
const activeRoot = path_1.default.join(rootDir, constants_1.DIR_NAMES.CHANGES, constants_1.DIR_NAMES.ACTIVE);
|
|
63
|
+
const activePath = path_1.default.join(activeRoot, changeName);
|
|
64
|
+
if (await this.fileService.exists(activePath)) {
|
|
65
|
+
throw new Error(`Active change already exists: ${changeName}`);
|
|
66
|
+
}
|
|
67
|
+
await this.fileService.ensureDir(activeRoot);
|
|
68
|
+
await this.fileService.move(queuedPath, activePath);
|
|
69
|
+
const statePath = path_1.default.join(activePath, constants_1.FILE_NAMES.STATE);
|
|
70
|
+
const state = await this.fileService.readJSON(statePath);
|
|
71
|
+
state.status = 'draft';
|
|
72
|
+
state.current_step = 'write_proposal';
|
|
73
|
+
state.blocked_by = ['missing_proposal'];
|
|
74
|
+
state.activated_at = new Date().toISOString();
|
|
75
|
+
state.activation_source = activationSource;
|
|
76
|
+
state.last_updated = new Date().toISOString();
|
|
77
|
+
await this.fileService.writeJSON(statePath, state);
|
|
78
|
+
await this.updateFrontmatterStatus(path_1.default.join(activePath, constants_1.FILE_NAMES.PROPOSAL), 'active');
|
|
79
|
+
await this.updateFrontmatterStatus(path_1.default.join(activePath, constants_1.FILE_NAMES.VERIFICATION), 'verifying');
|
|
80
|
+
const item = await this.buildQueuedChangeStatusItem(rootDir, activePath);
|
|
81
|
+
if (!item) {
|
|
82
|
+
throw new Error(`Activated change state could not be read: ${changeName}`);
|
|
83
|
+
}
|
|
84
|
+
return item;
|
|
85
|
+
}
|
|
86
|
+
async activateNextQueuedChange(rootDir, activationSource = 'runner') {
|
|
87
|
+
const queuedChanges = await this.getQueuedChanges(rootDir);
|
|
88
|
+
if (queuedChanges.length === 0) {
|
|
89
|
+
return null;
|
|
90
|
+
}
|
|
91
|
+
return this.activateQueuedChange(rootDir, queuedChanges[0].name, activationSource);
|
|
92
|
+
}
|
|
93
|
+
async buildQueuedChangeStatusItem(rootDir, changeDir) {
|
|
94
|
+
const statePath = path_1.default.join(changeDir, constants_1.FILE_NAMES.STATE);
|
|
95
|
+
if (!(await this.fileService.exists(statePath))) {
|
|
96
|
+
return null;
|
|
97
|
+
}
|
|
98
|
+
const state = await this.fileService.readJSON(statePath);
|
|
99
|
+
const proposalPath = path_1.default.join(changeDir, constants_1.FILE_NAMES.PROPOSAL);
|
|
100
|
+
let flags = [];
|
|
101
|
+
let description = 'No description yet';
|
|
102
|
+
if (await this.fileService.exists(proposalPath)) {
|
|
103
|
+
const proposal = (0, gray_matter_1.default)(await this.fileService.readFile(proposalPath));
|
|
104
|
+
flags = Array.isArray(proposal.data.flags) ? proposal.data.flags : [];
|
|
105
|
+
description = this.extractDescription(proposal.content);
|
|
106
|
+
}
|
|
107
|
+
return {
|
|
108
|
+
name: state.feature || path_1.default.basename(changeDir),
|
|
109
|
+
path: this.toRelativePath(rootDir, changeDir),
|
|
110
|
+
status: state.status,
|
|
111
|
+
currentStep: state.current_step,
|
|
112
|
+
flags,
|
|
113
|
+
description,
|
|
114
|
+
queuedAt: typeof state.queued_at === 'string' ? state.queued_at : null,
|
|
115
|
+
source: typeof state.queue_source === 'string' ? state.queue_source : null,
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
extractDescription(content) {
|
|
119
|
+
const lines = String(content || '')
|
|
120
|
+
.split(/\r?\n/)
|
|
121
|
+
.map(line => line.trim())
|
|
122
|
+
.filter(Boolean)
|
|
123
|
+
.filter(line => !line.startsWith('#'));
|
|
124
|
+
return lines[0] || 'No description yet';
|
|
125
|
+
}
|
|
126
|
+
async updateFrontmatterStatus(filePath, status) {
|
|
127
|
+
if (!(await this.fileService.exists(filePath))) {
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
130
|
+
const document = (0, gray_matter_1.default)(await this.fileService.readFile(filePath));
|
|
131
|
+
document.data.status = status;
|
|
132
|
+
await this.fileService.writeFile(filePath, gray_matter_1.default.stringify(document.content, document.data));
|
|
133
|
+
}
|
|
134
|
+
toRelativePath(rootDir, targetPath) {
|
|
135
|
+
return path_1.default.relative(rootDir, targetPath).replace(/\\/g, '/');
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
exports.QueueService = QueueService;
|
|
139
|
+
function createQueueService(fileService, projectService) {
|
|
140
|
+
return new QueueService(fileService, projectService);
|
|
141
|
+
}
|
|
142
|
+
//# sourceMappingURL=QueueService.js.map
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { QueueRunProfileId, QueueRunStatusReport } from '../core/types';
|
|
2
|
+
import { FileService } from './FileService';
|
|
3
|
+
import { ProjectService } from './ProjectService';
|
|
4
|
+
import { QueueService } from './QueueService';
|
|
5
|
+
export declare class RunService {
|
|
6
|
+
private fileService;
|
|
7
|
+
private projectService;
|
|
8
|
+
private queueService;
|
|
9
|
+
constructor(fileService: FileService, projectService: ProjectService, queueService: QueueService);
|
|
10
|
+
start(rootDir: string, options?: {
|
|
11
|
+
profileId?: QueueRunProfileId;
|
|
12
|
+
}): Promise<QueueRunStatusReport>;
|
|
13
|
+
resume(rootDir: string): Promise<QueueRunStatusReport>;
|
|
14
|
+
step(rootDir: string): Promise<QueueRunStatusReport>;
|
|
15
|
+
stop(rootDir: string): Promise<QueueRunStatusReport>;
|
|
16
|
+
getStatusReport(rootDir: string): Promise<QueueRunStatusReport>;
|
|
17
|
+
getLogTail(rootDir: string, lineCount?: number): Promise<string[]>;
|
|
18
|
+
private buildStatusReport;
|
|
19
|
+
private synchronizeRun;
|
|
20
|
+
private recordCompletedChange;
|
|
21
|
+
private createRun;
|
|
22
|
+
private touchRun;
|
|
23
|
+
private normalizeProfileId;
|
|
24
|
+
private requireCurrentRun;
|
|
25
|
+
private getCurrentRun;
|
|
26
|
+
private saveRun;
|
|
27
|
+
private appendLogEvents;
|
|
28
|
+
private readLogTail;
|
|
29
|
+
private assertRunnableRepository;
|
|
30
|
+
private findArchivedChangePath;
|
|
31
|
+
private ensureRunDirectories;
|
|
32
|
+
private getCurrentRunPath;
|
|
33
|
+
private getHistoryRunPath;
|
|
34
|
+
private resolveRunFilePath;
|
|
35
|
+
private buildActiveInstruction;
|
|
36
|
+
private describeRunStage;
|
|
37
|
+
private getIdleInstruction;
|
|
38
|
+
}
|
|
39
|
+
export declare function createRunService(fileService: FileService, projectService: ProjectService, queueService: QueueService): RunService;
|
|
40
|
+
//# sourceMappingURL=RunService.d.ts.map
|
|
@@ -0,0 +1,420 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.RunService = void 0;
|
|
7
|
+
exports.createRunService = createRunService;
|
|
8
|
+
const fs_extra_1 = __importDefault(require("fs-extra"));
|
|
9
|
+
const path_1 = __importDefault(require("path"));
|
|
10
|
+
const constants_1 = require("../core/constants");
|
|
11
|
+
const RUN_PROFILES = {
|
|
12
|
+
'manual-safe': {
|
|
13
|
+
autoFinalize: false,
|
|
14
|
+
},
|
|
15
|
+
'archive-chain': {
|
|
16
|
+
autoFinalize: true,
|
|
17
|
+
},
|
|
18
|
+
};
|
|
19
|
+
class RunService {
|
|
20
|
+
constructor(fileService, projectService, queueService) {
|
|
21
|
+
this.fileService = fileService;
|
|
22
|
+
this.projectService = projectService;
|
|
23
|
+
this.queueService = queueService;
|
|
24
|
+
}
|
|
25
|
+
async start(rootDir, options = {}) {
|
|
26
|
+
const resolvedRootDir = path_1.default.resolve(rootDir);
|
|
27
|
+
await this.ensureRunDirectories(resolvedRootDir);
|
|
28
|
+
const existingRun = await this.getCurrentRun(resolvedRootDir);
|
|
29
|
+
if (existingRun && ['running', 'paused', 'failed'].includes(existingRun.status)) {
|
|
30
|
+
throw new Error(`A queue run already exists with status "${existingRun.status}". Use "ospec run resume" or "ospec run stop" first.`);
|
|
31
|
+
}
|
|
32
|
+
await this.assertRunnableRepository(resolvedRootDir);
|
|
33
|
+
const profileId = this.normalizeProfileId(options.profileId);
|
|
34
|
+
const run = this.createRun(resolvedRootDir, profileId);
|
|
35
|
+
const synchronized = await this.synchronizeRun(resolvedRootDir, run, {
|
|
36
|
+
allowActivateNext: true,
|
|
37
|
+
});
|
|
38
|
+
await this.saveRun(resolvedRootDir, synchronized.run);
|
|
39
|
+
await this.appendLogEvents(resolvedRootDir, synchronized.run, [
|
|
40
|
+
`Queue run started with manual-bridge and profile ${profileId}.`,
|
|
41
|
+
...synchronized.events,
|
|
42
|
+
]);
|
|
43
|
+
return this.buildStatusReport(resolvedRootDir, synchronized.run);
|
|
44
|
+
}
|
|
45
|
+
async resume(rootDir) {
|
|
46
|
+
const resolvedRootDir = path_1.default.resolve(rootDir);
|
|
47
|
+
const run = await this.requireCurrentRun(resolvedRootDir);
|
|
48
|
+
if (!['paused', 'failed'].includes(run.status)) {
|
|
49
|
+
throw new Error(`Run resume requires a paused or failed run. Current status is "${run.status}".`);
|
|
50
|
+
}
|
|
51
|
+
run.status = 'running';
|
|
52
|
+
run.stoppedAt = null;
|
|
53
|
+
run.completedAt = null;
|
|
54
|
+
const synchronized = await this.synchronizeRun(resolvedRootDir, run, {
|
|
55
|
+
allowActivateNext: true,
|
|
56
|
+
});
|
|
57
|
+
await this.saveRun(resolvedRootDir, synchronized.run);
|
|
58
|
+
await this.appendLogEvents(resolvedRootDir, synchronized.run, [
|
|
59
|
+
'Queue run resumed.',
|
|
60
|
+
...synchronized.events,
|
|
61
|
+
]);
|
|
62
|
+
return this.buildStatusReport(resolvedRootDir, synchronized.run);
|
|
63
|
+
}
|
|
64
|
+
async step(rootDir) {
|
|
65
|
+
const resolvedRootDir = path_1.default.resolve(rootDir);
|
|
66
|
+
const run = await this.requireCurrentRun(resolvedRootDir);
|
|
67
|
+
if (run.status !== 'running') {
|
|
68
|
+
throw new Error(`Run step requires a running run. Current status is "${run.status}".`);
|
|
69
|
+
}
|
|
70
|
+
const profile = RUN_PROFILES[run.profileId];
|
|
71
|
+
const events = [];
|
|
72
|
+
const initialSync = await this.synchronizeRun(resolvedRootDir, run, {
|
|
73
|
+
allowActivateNext: true,
|
|
74
|
+
});
|
|
75
|
+
events.push(...initialSync.events);
|
|
76
|
+
if (initialSync.run.status === 'running' && initialSync.run.currentChangePath) {
|
|
77
|
+
const activePath = path_1.default.join(resolvedRootDir, initialSync.run.currentChangePath);
|
|
78
|
+
const activeChange = await this.projectService.getActiveChangeStatusItem(activePath);
|
|
79
|
+
if (profile.autoFinalize && activeChange.archiveReady) {
|
|
80
|
+
const finalized = await this.projectService.finalizeChange(activePath);
|
|
81
|
+
this.recordCompletedChange(initialSync.run, activeChange.name, finalized.archivePath, 'auto-finalized');
|
|
82
|
+
initialSync.run.currentChange = null;
|
|
83
|
+
initialSync.run.currentChangePath = null;
|
|
84
|
+
initialSync.run.failedChange = null;
|
|
85
|
+
initialSync.run.lastInstruction = `Change ${activeChange.name} was finalized and archived to ${finalized.archivePath}.`;
|
|
86
|
+
events.push(`Auto-finalized and archived ${activeChange.name} to ${finalized.archivePath}.`);
|
|
87
|
+
}
|
|
88
|
+
else if (profile.autoFinalize) {
|
|
89
|
+
initialSync.run.lastInstruction =
|
|
90
|
+
`Continue ${activeChange.path} manually. When it becomes archive-ready, run "ospec run step" and ospec will finalize it on that explicit step.`;
|
|
91
|
+
events.push(`Checked ${activeChange.name}; archive-chain is waiting for the change to become archive-ready.`);
|
|
92
|
+
}
|
|
93
|
+
else {
|
|
94
|
+
initialSync.run.lastInstruction =
|
|
95
|
+
`Manual-safe run is attached to ${activeChange.path}. Continue the change manually, then run "ospec run step" when you want ospec to re-check queue progress.`;
|
|
96
|
+
events.push(`Checked ${activeChange.name}; manual-safe mode leaves the active change lifecycle fully manual.`);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
const finalSync = await this.synchronizeRun(resolvedRootDir, initialSync.run, {
|
|
100
|
+
allowActivateNext: true,
|
|
101
|
+
});
|
|
102
|
+
events.push(...finalSync.events);
|
|
103
|
+
await this.saveRun(resolvedRootDir, finalSync.run);
|
|
104
|
+
if (events.length > 0) {
|
|
105
|
+
await this.appendLogEvents(resolvedRootDir, finalSync.run, events);
|
|
106
|
+
}
|
|
107
|
+
return this.buildStatusReport(resolvedRootDir, finalSync.run);
|
|
108
|
+
}
|
|
109
|
+
async stop(rootDir) {
|
|
110
|
+
const resolvedRootDir = path_1.default.resolve(rootDir);
|
|
111
|
+
const run = await this.requireCurrentRun(resolvedRootDir);
|
|
112
|
+
if (run.status === 'completed') {
|
|
113
|
+
throw new Error('The current queue run is already completed.');
|
|
114
|
+
}
|
|
115
|
+
const synchronized = await this.synchronizeRun(resolvedRootDir, run, {
|
|
116
|
+
allowActivateNext: false,
|
|
117
|
+
});
|
|
118
|
+
synchronized.run.status = 'paused';
|
|
119
|
+
synchronized.run.stoppedAt = new Date().toISOString();
|
|
120
|
+
synchronized.run.lastInstruction = synchronized.run.currentChange
|
|
121
|
+
? 'Queue run paused. Finish the current change manually, then use "ospec run resume" when you want ospec to continue tracking the queue.'
|
|
122
|
+
: 'Queue run paused. Use "ospec run resume" when you want ospec to continue with the queue.';
|
|
123
|
+
await this.saveRun(resolvedRootDir, synchronized.run);
|
|
124
|
+
await this.appendLogEvents(resolvedRootDir, synchronized.run, [
|
|
125
|
+
...synchronized.events,
|
|
126
|
+
'Queue run paused by user request.',
|
|
127
|
+
]);
|
|
128
|
+
return this.buildStatusReport(resolvedRootDir, synchronized.run);
|
|
129
|
+
}
|
|
130
|
+
async getStatusReport(rootDir) {
|
|
131
|
+
const resolvedRootDir = path_1.default.resolve(rootDir);
|
|
132
|
+
const run = await this.getCurrentRun(resolvedRootDir);
|
|
133
|
+
if (!run) {
|
|
134
|
+
return this.buildStatusReport(resolvedRootDir, null);
|
|
135
|
+
}
|
|
136
|
+
const synchronized = await this.synchronizeRun(resolvedRootDir, run, {
|
|
137
|
+
allowActivateNext: false,
|
|
138
|
+
});
|
|
139
|
+
await this.saveRun(resolvedRootDir, synchronized.run);
|
|
140
|
+
if (synchronized.events.length > 0) {
|
|
141
|
+
await this.appendLogEvents(resolvedRootDir, synchronized.run, synchronized.events);
|
|
142
|
+
}
|
|
143
|
+
return this.buildStatusReport(resolvedRootDir, synchronized.run);
|
|
144
|
+
}
|
|
145
|
+
async getLogTail(rootDir, lineCount = 20) {
|
|
146
|
+
const resolvedRootDir = path_1.default.resolve(rootDir);
|
|
147
|
+
const run = await this.getCurrentRun(resolvedRootDir);
|
|
148
|
+
if (!run) {
|
|
149
|
+
return [];
|
|
150
|
+
}
|
|
151
|
+
return this.readLogTail(resolvedRootDir, run.logPath, lineCount);
|
|
152
|
+
}
|
|
153
|
+
async buildStatusReport(rootDir, run) {
|
|
154
|
+
const [activeNames, queuedChanges, logTail] = await Promise.all([
|
|
155
|
+
this.projectService.listActiveChangeNames(rootDir),
|
|
156
|
+
this.queueService.getQueuedChanges(rootDir),
|
|
157
|
+
run ? this.readLogTail(rootDir, run.logPath, 20) : Promise.resolve([]),
|
|
158
|
+
]);
|
|
159
|
+
const activeChange = activeNames.length === 1
|
|
160
|
+
? {
|
|
161
|
+
name: activeNames[0],
|
|
162
|
+
path: `changes/active/${activeNames[0]}`,
|
|
163
|
+
status: (await this.fileService.readJSON(path_1.default.join(rootDir, constants_1.DIR_NAMES.CHANGES, constants_1.DIR_NAMES.ACTIVE, activeNames[0], constants_1.FILE_NAMES.STATE))).status,
|
|
164
|
+
}
|
|
165
|
+
: null;
|
|
166
|
+
return {
|
|
167
|
+
currentRun: run,
|
|
168
|
+
stage: this.describeRunStage(run),
|
|
169
|
+
activeChange,
|
|
170
|
+
queuedChanges,
|
|
171
|
+
logTail,
|
|
172
|
+
nextInstruction: run?.lastInstruction ?? this.getIdleInstruction(queuedChanges.length),
|
|
173
|
+
};
|
|
174
|
+
}
|
|
175
|
+
async synchronizeRun(rootDir, run, options) {
|
|
176
|
+
const events = [];
|
|
177
|
+
const activeNames = await this.projectService.listActiveChangeNames(rootDir);
|
|
178
|
+
if (activeNames.length > 1) {
|
|
179
|
+
run.status = 'failed';
|
|
180
|
+
run.failedChange = {
|
|
181
|
+
name: activeNames.join(', '),
|
|
182
|
+
path: 'changes/active',
|
|
183
|
+
status: 'draft',
|
|
184
|
+
recordedAt: new Date().toISOString(),
|
|
185
|
+
note: 'multiple-active-changes',
|
|
186
|
+
};
|
|
187
|
+
run.lastInstruction =
|
|
188
|
+
'Multiple active changes were detected. Resolve the repository back to a single active change before resuming the queue runner.';
|
|
189
|
+
run.remainingChanges = await this.queueService.listQueuedChangeNames(rootDir);
|
|
190
|
+
events.push(`Runner entered failed state because multiple active changes were detected: ${activeNames.join(', ')}.`);
|
|
191
|
+
return {
|
|
192
|
+
run: this.touchRun(run),
|
|
193
|
+
events,
|
|
194
|
+
};
|
|
195
|
+
}
|
|
196
|
+
const activeName = activeNames[0] ?? null;
|
|
197
|
+
if (run.currentChange && run.currentChange !== activeName) {
|
|
198
|
+
const archivedPath = await this.findArchivedChangePath(rootDir, run.currentChange);
|
|
199
|
+
if (archivedPath) {
|
|
200
|
+
this.recordCompletedChange(run, run.currentChange, archivedPath, 'archived-observed');
|
|
201
|
+
events.push(`Observed archived change: ${run.currentChange}.`);
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
if (activeName) {
|
|
205
|
+
if (run.currentChange !== activeName) {
|
|
206
|
+
events.push(`Runner attached to active change ${activeName}.`);
|
|
207
|
+
}
|
|
208
|
+
run.currentChange = activeName;
|
|
209
|
+
run.currentChangePath = `changes/active/${activeName}`;
|
|
210
|
+
if (run.status !== 'failed') {
|
|
211
|
+
run.failedChange = null;
|
|
212
|
+
}
|
|
213
|
+
run.completedAt = null;
|
|
214
|
+
}
|
|
215
|
+
else {
|
|
216
|
+
run.currentChange = null;
|
|
217
|
+
run.currentChangePath = null;
|
|
218
|
+
if (options.allowActivateNext && run.status === 'running') {
|
|
219
|
+
const nextChange = await this.queueService.activateNextQueuedChange(rootDir, 'runner');
|
|
220
|
+
if (nextChange) {
|
|
221
|
+
run.currentChange = nextChange.name;
|
|
222
|
+
run.currentChangePath = nextChange.path;
|
|
223
|
+
run.failedChange = null;
|
|
224
|
+
events.push(`Activated next queued change: ${nextChange.name}.`);
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
run.remainingChanges = await this.queueService.listQueuedChangeNames(rootDir);
|
|
229
|
+
if (!run.currentChange) {
|
|
230
|
+
if (run.status === 'running' && run.remainingChanges.length === 0) {
|
|
231
|
+
run.status = 'completed';
|
|
232
|
+
run.completedAt = new Date().toISOString();
|
|
233
|
+
run.lastInstruction = 'Queue run completed. No queued changes remain.';
|
|
234
|
+
events.push('Queue run completed.');
|
|
235
|
+
}
|
|
236
|
+
else if (run.status !== 'completed') {
|
|
237
|
+
run.lastInstruction = run.remainingChanges.length > 0
|
|
238
|
+
? 'No active change is attached right now. Run "ospec run step" to activate or continue the next queued change.'
|
|
239
|
+
: 'No active or queued changes are available right now.';
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
else if (run.status === 'running') {
|
|
243
|
+
run.lastInstruction = this.buildActiveInstruction(run.currentChangePath, run.profileId);
|
|
244
|
+
}
|
|
245
|
+
return {
|
|
246
|
+
run: this.touchRun(run),
|
|
247
|
+
events,
|
|
248
|
+
};
|
|
249
|
+
}
|
|
250
|
+
recordCompletedChange(run, changeName, archivePath, note = null) {
|
|
251
|
+
if (run.completedChanges.some(item => item.name === changeName && item.path === archivePath)) {
|
|
252
|
+
return;
|
|
253
|
+
}
|
|
254
|
+
run.completedChanges.push({
|
|
255
|
+
name: changeName,
|
|
256
|
+
path: archivePath,
|
|
257
|
+
status: 'archived',
|
|
258
|
+
recordedAt: new Date().toISOString(),
|
|
259
|
+
note,
|
|
260
|
+
});
|
|
261
|
+
}
|
|
262
|
+
createRun(rootDir, profileId) {
|
|
263
|
+
const now = new Date().toISOString();
|
|
264
|
+
const id = `run-${now.replace(/[:.]/g, '-')}`;
|
|
265
|
+
return {
|
|
266
|
+
id,
|
|
267
|
+
status: 'running',
|
|
268
|
+
executor: 'manual-bridge',
|
|
269
|
+
profileId,
|
|
270
|
+
mode: 'single-active-sequential',
|
|
271
|
+
projectPath: rootDir,
|
|
272
|
+
startedAt: now,
|
|
273
|
+
updatedAt: now,
|
|
274
|
+
stoppedAt: null,
|
|
275
|
+
completedAt: null,
|
|
276
|
+
currentChange: null,
|
|
277
|
+
currentChangePath: null,
|
|
278
|
+
completedChanges: [],
|
|
279
|
+
remainingChanges: [],
|
|
280
|
+
failedChange: null,
|
|
281
|
+
logPath: `.ospec/${constants_1.DIR_NAMES.RUNS}/${constants_1.DIR_NAMES.LOGS}/${id}.log`,
|
|
282
|
+
lastInstruction: null,
|
|
283
|
+
};
|
|
284
|
+
}
|
|
285
|
+
touchRun(run) {
|
|
286
|
+
return {
|
|
287
|
+
...run,
|
|
288
|
+
updatedAt: new Date().toISOString(),
|
|
289
|
+
};
|
|
290
|
+
}
|
|
291
|
+
normalizeProfileId(profileId) {
|
|
292
|
+
const normalized = String(profileId || 'manual-safe').trim();
|
|
293
|
+
if (normalized === 'manual-safe' || normalized === 'archive-chain') {
|
|
294
|
+
return normalized;
|
|
295
|
+
}
|
|
296
|
+
throw new Error(`Unsupported run profile: ${profileId}`);
|
|
297
|
+
}
|
|
298
|
+
async requireCurrentRun(rootDir) {
|
|
299
|
+
const run = await this.getCurrentRun(rootDir);
|
|
300
|
+
if (!run) {
|
|
301
|
+
throw new Error('No queue run exists for this project yet. Use "ospec run start" first.');
|
|
302
|
+
}
|
|
303
|
+
return run;
|
|
304
|
+
}
|
|
305
|
+
async getCurrentRun(rootDir) {
|
|
306
|
+
const currentRunPath = this.getCurrentRunPath(rootDir);
|
|
307
|
+
if (!(await this.fileService.exists(currentRunPath))) {
|
|
308
|
+
return null;
|
|
309
|
+
}
|
|
310
|
+
return this.fileService.readJSON(currentRunPath);
|
|
311
|
+
}
|
|
312
|
+
async saveRun(rootDir, run) {
|
|
313
|
+
await this.ensureRunDirectories(rootDir);
|
|
314
|
+
await this.fileService.writeJSON(this.getCurrentRunPath(rootDir), run);
|
|
315
|
+
await this.fileService.writeJSON(this.getHistoryRunPath(rootDir, run.id), run);
|
|
316
|
+
}
|
|
317
|
+
async appendLogEvents(rootDir, run, events) {
|
|
318
|
+
if (events.length === 0) {
|
|
319
|
+
return;
|
|
320
|
+
}
|
|
321
|
+
const logPath = this.resolveRunFilePath(rootDir, run.logPath);
|
|
322
|
+
await this.fileService.ensureDir(path_1.default.dirname(logPath));
|
|
323
|
+
const lines = events.map(event => `[${new Date().toISOString()}] ${event}`).join('\n');
|
|
324
|
+
await fs_extra_1.default.appendFile(logPath, `${lines}\n`, 'utf8');
|
|
325
|
+
}
|
|
326
|
+
async readLogTail(rootDir, logPath, lineCount) {
|
|
327
|
+
const resolvedLogPath = this.resolveRunFilePath(rootDir, logPath);
|
|
328
|
+
if (!(await this.fileService.exists(resolvedLogPath))) {
|
|
329
|
+
return [];
|
|
330
|
+
}
|
|
331
|
+
const content = await this.fileService.readFile(resolvedLogPath);
|
|
332
|
+
return content
|
|
333
|
+
.split(/\r?\n/)
|
|
334
|
+
.map(line => line.trimEnd())
|
|
335
|
+
.filter(Boolean)
|
|
336
|
+
.slice(-lineCount);
|
|
337
|
+
}
|
|
338
|
+
async assertRunnableRepository(rootDir) {
|
|
339
|
+
const activeNames = await this.projectService.listActiveChangeNames(rootDir);
|
|
340
|
+
if (activeNames.length > 1) {
|
|
341
|
+
throw new Error(`Queue runner requires single-active mode, but ${activeNames.length} active changes were found: ${activeNames.join(', ')}.`);
|
|
342
|
+
}
|
|
343
|
+
const queuedNames = await this.queueService.listQueuedChangeNames(rootDir);
|
|
344
|
+
if (activeNames.length === 0 && queuedNames.length === 0) {
|
|
345
|
+
throw new Error('No active or queued changes are available for execution.');
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
async findArchivedChangePath(rootDir, changeName) {
|
|
349
|
+
const archivedDir = path_1.default.join(rootDir, constants_1.DIR_NAMES.CHANGES, constants_1.DIR_NAMES.ARCHIVED);
|
|
350
|
+
if (!(await this.fileService.exists(archivedDir))) {
|
|
351
|
+
return null;
|
|
352
|
+
}
|
|
353
|
+
const entries = await fs_extra_1.default.readdir(archivedDir, { withFileTypes: true });
|
|
354
|
+
const matched = entries
|
|
355
|
+
.filter(entry => entry.isDirectory() && entry.name.endsWith(`-${changeName}`))
|
|
356
|
+
.map(entry => entry.name)
|
|
357
|
+
.sort()
|
|
358
|
+
.at(-1);
|
|
359
|
+
return matched ? `changes/archived/${matched}` : null;
|
|
360
|
+
}
|
|
361
|
+
async ensureRunDirectories(rootDir) {
|
|
362
|
+
await Promise.all([
|
|
363
|
+
this.fileService.ensureDir(path_1.default.join(rootDir, '.ospec', constants_1.DIR_NAMES.RUNS)),
|
|
364
|
+
this.fileService.ensureDir(path_1.default.join(rootDir, '.ospec', constants_1.DIR_NAMES.RUNS, constants_1.DIR_NAMES.HISTORY)),
|
|
365
|
+
this.fileService.ensureDir(path_1.default.join(rootDir, '.ospec', constants_1.DIR_NAMES.RUNS, constants_1.DIR_NAMES.LOGS)),
|
|
366
|
+
]);
|
|
367
|
+
}
|
|
368
|
+
getCurrentRunPath(rootDir) {
|
|
369
|
+
return path_1.default.join(rootDir, '.ospec', constants_1.DIR_NAMES.RUNS, 'current.json');
|
|
370
|
+
}
|
|
371
|
+
getHistoryRunPath(rootDir, runId) {
|
|
372
|
+
return path_1.default.join(rootDir, '.ospec', constants_1.DIR_NAMES.RUNS, constants_1.DIR_NAMES.HISTORY, `${runId}.json`);
|
|
373
|
+
}
|
|
374
|
+
resolveRunFilePath(rootDir, targetPath) {
|
|
375
|
+
return path_1.default.isAbsolute(targetPath) ? targetPath : path_1.default.join(rootDir, targetPath);
|
|
376
|
+
}
|
|
377
|
+
buildActiveInstruction(changePath, profileId) {
|
|
378
|
+
if (profileId === 'archive-chain') {
|
|
379
|
+
return [
|
|
380
|
+
`Archive-chain is attached to ${changePath}.`,
|
|
381
|
+
'Complete the change manually and keep the protocol docs current.',
|
|
382
|
+
'Run "ospec run step" when it becomes archive-ready and ospec will finalize/archive it on that explicit step.',
|
|
383
|
+
].join(' ');
|
|
384
|
+
}
|
|
385
|
+
return [
|
|
386
|
+
`Manual-safe is attached to ${changePath}.`,
|
|
387
|
+
'Complete the change manually.',
|
|
388
|
+
'Run "ospec run step" when you want ospec to re-check queue progress.',
|
|
389
|
+
].join(' ');
|
|
390
|
+
}
|
|
391
|
+
describeRunStage(run) {
|
|
392
|
+
if (!run) {
|
|
393
|
+
return null;
|
|
394
|
+
}
|
|
395
|
+
if (run.status === 'completed') {
|
|
396
|
+
return 'queue-complete';
|
|
397
|
+
}
|
|
398
|
+
if (run.status === 'paused') {
|
|
399
|
+
return 'paused';
|
|
400
|
+
}
|
|
401
|
+
if (run.status === 'failed') {
|
|
402
|
+
return run.failedChange?.note === 'multiple-active-changes' ? 'failed:multiple-active' : 'failed';
|
|
403
|
+
}
|
|
404
|
+
if (!run.currentChangePath) {
|
|
405
|
+
return run.remainingChanges.length > 0 ? 'awaiting-activation' : 'idle';
|
|
406
|
+
}
|
|
407
|
+
return run.profileId === 'archive-chain' ? 'active:archive-chain' : 'active:manual-safe';
|
|
408
|
+
}
|
|
409
|
+
getIdleInstruction(queuedCount) {
|
|
410
|
+
if (queuedCount > 0) {
|
|
411
|
+
return 'No queue run is active. Run "ospec run start" to begin processing queued changes.';
|
|
412
|
+
}
|
|
413
|
+
return 'No queue run is active.';
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
exports.RunService = RunService;
|
|
417
|
+
function createRunService(fileService, projectService, queueService) {
|
|
418
|
+
return new RunService(fileService, projectService, queueService);
|
|
419
|
+
}
|
|
420
|
+
//# sourceMappingURL=RunService.js.map
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SKILL 解析服务
|
|
3
|
+
*/
|
|
4
|
+
import { SkillFrontmatter, SkillSection } from '../core/types';
|
|
5
|
+
interface ParsedSkillFrontmatter {
|
|
6
|
+
data: SkillFrontmatter;
|
|
7
|
+
content: string;
|
|
8
|
+
}
|
|
9
|
+
export declare class SkillParser {
|
|
10
|
+
/**
|
|
11
|
+
* 解析 SKILL.md 的前置信息和内容
|
|
12
|
+
*/
|
|
13
|
+
parseFrontmatter(content: string): ParsedSkillFrontmatter;
|
|
14
|
+
/**
|
|
15
|
+
* 提取 Markdown 中的标题结构
|
|
16
|
+
*/
|
|
17
|
+
extractSections(content: string): Record<string, SkillSection>;
|
|
18
|
+
/**
|
|
19
|
+
* 完整解析 SKILL.md 文件
|
|
20
|
+
*/
|
|
21
|
+
parseSkillFile(content: string): {
|
|
22
|
+
frontmatter: SkillFrontmatter;
|
|
23
|
+
sections: Record<string, SkillSection>;
|
|
24
|
+
content: string;
|
|
25
|
+
};
|
|
26
|
+
private extractDocumentTitle;
|
|
27
|
+
}
|
|
28
|
+
export declare const skillParser: SkillParser;
|
|
29
|
+
export {};
|
|
30
|
+
//# sourceMappingURL=SkillParser.d.ts.map
|