owflow 0.5.0 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -21
- package/README.md +448 -261
- package/dist/agents/bottleneck-analyzer.md +332 -329
- package/dist/agents/code-quality-pragmatist.md +353 -354
- package/dist/agents/code-reviewer.md +238 -239
- package/dist/agents/codebase-analysis-reporter.md +245 -246
- package/dist/agents/docs-operator.md +22 -23
- package/dist/agents/e2e-test-verifier.md +447 -448
- package/dist/agents/gap-analyzer.md +501 -502
- package/dist/agents/implementation-completeness-checker.md +219 -220
- package/dist/agents/implementation-planner.md +308 -309
- package/dist/agents/information-gatherer.md +690 -691
- package/dist/agents/production-readiness-checker.md +278 -279
- package/dist/agents/project-analyzer.md +403 -403
- package/dist/agents/reality-assessor.md +365 -366
- package/dist/agents/research-planner.md +449 -450
- package/dist/agents/research-synthesizer.md +402 -403
- package/dist/agents/solution-brainstormer.md +256 -257
- package/dist/agents/solution-designer.md +371 -372
- package/dist/agents/spec-auditor.md +297 -298
- package/dist/agents/specification-creator.md +333 -334
- package/dist/agents/task-classifier.md +449 -450
- package/dist/agents/task-group-implementer.md +283 -284
- package/dist/agents/test-suite-runner.md +194 -195
- package/dist/agents/user-docs-generator.md +508 -509
- package/dist/commands/reviews-code.md +90 -90
- package/dist/commands/reviews-pragmatic.md +99 -99
- package/dist/commands/reviews-production-readiness.md +113 -113
- package/dist/commands/reviews-reality-check.md +110 -110
- package/dist/commands/reviews-spec-audit.md +114 -114
- package/dist/commands/work.md +280 -280
- package/dist/configuration/agents-config.d.ts +2 -0
- package/dist/configuration/agents-config.js +8 -44
- package/dist/configuration/commands-config.d.ts +23 -0
- package/dist/configuration/commands-config.js +17 -17
- package/dist/configuration/skills-config.d.ts +2 -0
- package/dist/configuration/skills-config.js +3 -3
- package/dist/hooks/hooks-diagnostics.d.ts +3 -0
- package/dist/hooks/hooks-diagnostics.js +21 -0
- package/dist/hooks/hooks-persistence.d.ts +17 -0
- package/dist/hooks/hooks-persistence.js +207 -0
- package/dist/hooks/hooks-registry.d.ts +6 -0
- package/dist/hooks/hooks-registry.js +104 -0
- package/dist/hooks/hooks-wrapper.d.ts +25 -0
- package/dist/hooks/hooks-wrapper.js +318 -0
- package/dist/hooks/internal/before-tool.d.ts +6 -0
- package/dist/hooks/internal/before-tool.js +13 -0
- package/dist/hooks/internal/session-compaction.d.ts +9 -0
- package/dist/hooks/{session-compaction.js → internal/session-compaction.js} +9 -7
- package/dist/hooks/runtime/circuit-breaker.d.ts +23 -0
- package/dist/hooks/runtime/circuit-breaker.js +39 -0
- package/dist/hooks/runtime/failure-policy.d.ts +15 -0
- package/dist/hooks/runtime/failure-policy.js +49 -0
- package/dist/hooks/runtime/process-executor.d.ts +33 -0
- package/dist/hooks/runtime/process-executor.js +147 -0
- package/dist/hooks/runtime/status-recorder.d.ts +27 -0
- package/dist/hooks/runtime/status-recorder.js +111 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.js +111 -40
- package/dist/skills/agents-md-generator/SKILL.md +223 -223
- package/dist/skills/codebase-analyzer/SKILL.md +162 -162
- package/dist/skills/codebase-analyzer/references/code-analysis.md +63 -63
- package/dist/skills/codebase-analyzer/references/combined.md +31 -31
- package/dist/skills/codebase-analyzer/references/context-discovery.md +63 -63
- package/dist/skills/codebase-analyzer/references/file-discovery.md +51 -51
- package/dist/skills/codebase-analyzer/references/migration-target.md +23 -23
- package/dist/skills/codebase-analyzer/references/pattern-mining.md +22 -22
- package/dist/skills/dev-analyze/SKILL.md +115 -115
- package/dist/skills/dev-bugfix/SKILL.md +452 -452
- package/dist/skills/dev-finalize/SKILL.md +118 -118
- package/dist/skills/dev-implement/SKILL.md +167 -167
- package/dist/skills/dev-plan/SKILL.md +147 -147
- package/dist/skills/dev-spec/SKILL.md +182 -182
- package/dist/skills/dev-tdd-red/SKILL.md +94 -94
- package/dist/skills/dev-verify/SKILL.md +153 -153
- package/dist/skills/development/SKILL.md +260 -260
- package/dist/skills/diagrams-mermaid/SKILL.md +183 -159
- package/dist/skills/diagrams-mermaid/references/reference.md +220 -148
- package/dist/skills/docs-manager/SKILL.md +379 -379
- package/dist/skills/docs-manager/docs/INDEX.md +199 -199
- package/dist/skills/docs-manager/docs/standards/backend/api.md +25 -25
- package/dist/skills/docs-manager/docs/standards/backend/migrations.md +22 -22
- package/dist/skills/docs-manager/docs/standards/backend/models.md +25 -25
- package/dist/skills/docs-manager/docs/standards/backend/queries.md +22 -22
- package/dist/skills/docs-manager/docs/standards/frontend/accessibility.md +25 -25
- package/dist/skills/docs-manager/docs/standards/frontend/components.md +28 -28
- package/dist/skills/docs-manager/docs/standards/frontend/css.md +16 -16
- package/dist/skills/docs-manager/docs/standards/frontend/responsive.md +28 -28
- package/dist/skills/docs-manager/docs/standards/global/coding-style.md +25 -25
- package/dist/skills/docs-manager/docs/standards/global/commenting.md +10 -10
- package/dist/skills/docs-manager/docs/standards/global/conventions.md +31 -31
- package/dist/skills/docs-manager/docs/standards/global/error-handling.md +22 -22
- package/dist/skills/docs-manager/docs/standards/global/minimal-implementation.md +22 -22
- package/dist/skills/docs-manager/docs/standards/global/validation.md +28 -28
- package/dist/skills/docs-manager/docs/standards/testing/test-writing.md +25 -25
- package/dist/skills/docs-manager/references/agents-md-template.md +29 -29
- package/dist/skills/docs-manager/references/index-md-template.md +73 -73
- package/dist/skills/flow-init/SKILL.md +234 -234
- package/dist/skills/flow-init/references/architecture-template.md +45 -45
- package/dist/skills/flow-init/references/roadmap-templates.md +93 -93
- package/dist/skills/flow-init/references/tech-stack-template.md +70 -70
- package/dist/skills/flow-init/references/vision-templates.md +75 -75
- package/dist/skills/goal-development/SKILL.md +96 -96
- package/dist/skills/goal-migration/SKILL.md +114 -114
- package/dist/skills/goal-performance/SKILL.md +126 -0
- package/dist/skills/goal-research/SKILL.md +118 -118
- package/dist/skills/html-renderer/SKILL.md +173 -173
- package/dist/skills/html-renderer/references/index.html +318 -318
- package/dist/skills/html-renderer/references/styles.css +388 -388
- package/dist/skills/html-renderer/references/tokens.json +104 -104
- package/dist/skills/implementation-plan-executor/SKILL.md +378 -378
- package/dist/skills/implementation-verifier/SKILL.md +307 -307
- package/dist/skills/migration/SKILL.md +268 -268
- package/dist/skills/migration-analyze/SKILL.md +126 -126
- package/dist/skills/migration-finalize/SKILL.md +161 -161
- package/dist/skills/migration-fix/SKILL.md +155 -155
- package/dist/skills/migration-implement/SKILL.md +131 -131
- package/dist/skills/migration-plan/SKILL.md +131 -131
- package/dist/skills/migration-spec/SKILL.md +135 -135
- package/dist/skills/migration-spec/references/migration-strategies.md +430 -430
- package/dist/skills/migration-target/SKILL.md +134 -134
- package/dist/skills/migration-target/references/migration-types.md +489 -489
- package/dist/skills/migration-verify/SKILL.md +184 -184
- package/dist/skills/orchestrator-framework/SKILL.md +65 -65
- package/dist/skills/orchestrator-framework/references/command-namespacing.md +7 -7
- package/dist/skills/orchestrator-framework/references/confirm-or-revise-exception.md +20 -20
- package/dist/skills/orchestrator-framework/references/delegation-rules.md +54 -54
- package/dist/skills/orchestrator-framework/references/dispatcher-handoff.md +27 -27
- package/dist/skills/orchestrator-framework/references/fix-loop-contract.md +65 -65
- package/dist/skills/orchestrator-framework/references/gate-contract.md +122 -117
- package/dist/skills/orchestrator-framework/references/orchestrator-creation-checklist.md +51 -51
- package/dist/skills/orchestrator-framework/references/orchestrator-patterns.md +235 -235
- package/dist/skills/performance/SKILL.md +267 -437
- package/dist/skills/performance-analyze/SKILL.md +129 -0
- package/dist/skills/{performance → performance-analyze}/references/performance-optimization-guide.md +271 -365
- package/dist/skills/performance-finalize/SKILL.md +117 -0
- package/dist/skills/performance-implement/SKILL.md +114 -0
- package/dist/skills/performance-plan/SKILL.md +112 -0
- package/dist/skills/performance-spec/SKILL.md +145 -0
- package/dist/skills/performance-verify/SKILL.md +164 -0
- package/dist/skills/research/SKILL.md +249 -249
- package/dist/skills/research-brainstorm/SKILL.md +120 -120
- package/dist/skills/research-brainstorm/references/brainstorming-techniques.md +84 -84
- package/dist/skills/research-converge/SKILL.md +130 -130
- package/dist/skills/research-design/SKILL.md +146 -146
- package/dist/skills/research-design/references/design-techniques.md +40 -40
- package/dist/skills/research-finalize/SKILL.md +125 -125
- package/dist/skills/research-fork/SKILL.md +190 -190
- package/dist/skills/research-gather/SKILL.md +133 -133
- package/dist/skills/research-plan/SKILL.md +141 -141
- package/dist/skills/research-plan/references/research-methodologies.md +710 -710
- package/dist/skills/research-quick/SKILL.md +139 -139
- package/dist/skills/research-synthesize/SKILL.md +127 -127
- package/dist/skills/rule-reviewer/SKILL.md +334 -334
- package/dist/skills/standards-discover/SKILL.md +260 -260
- package/dist/skills/standards-discover/references/aggregation-strategy.md +76 -76
- package/dist/skills/standards-discover/references/code-pattern-prompt.md +68 -68
- package/dist/skills/standards-discover/references/config-analyzer-prompt.md +67 -67
- package/dist/skills/standards-discover/references/docs-extractor-prompt.md +65 -65
- package/dist/skills/standards-discover/references/external-analyzer-prompt.md +75 -75
- package/dist/skills/standards-update/SKILL.md +207 -207
- package/dist/templates/orchestrator-state-base.yml +17 -17
- package/dist/templates/orchestrator-state-development.yml +95 -95
- package/dist/templates/orchestrator-state-migration.yml +95 -95
- package/dist/templates/orchestrator-state-performance.yml +64 -53
- package/dist/templates/orchestrator-state-research.yml +67 -67
- package/dist/tools/fork_task.d.ts +17 -0
- package/dist/tools/fork_task.js +21 -11
- package/dist/tools/verify_template.d.ts +11 -0
- package/dist/tools/verify_template.js +32 -17
- package/dist/tui/hooks-dialogs.d.ts +20 -0
- package/dist/tui/hooks-dialogs.js +181 -0
- package/dist/tui/hooks-presenter.d.ts +15 -0
- package/dist/tui/hooks-presenter.js +70 -0
- package/dist/tui/hooks-sidebar.d.ts +10 -0
- package/dist/tui/hooks-sidebar.js +104 -0
- package/dist/tui/primitives.d.ts +21 -0
- package/dist/tui/primitives.js +47 -0
- package/dist/tui.d.ts +12 -0
- package/dist/tui.js +129 -0
- package/dist/types/hook-types.d.ts +125 -0
- package/dist/types/hook-types.js +76 -0
- package/dist/types/opencode-types.d.ts +13 -0
- package/dist/utils/file-util.d.ts +2 -0
- package/dist/utils/file-util.js +6 -6
- package/dist/utils/fork-task.d.ts +27 -0
- package/dist/utils/fork-task.js +18 -23
- package/dist/utils/plugin-info.d.ts +3 -0
- package/dist/utils/plugin-info.js +8 -10
- package/dist/utils/workflow-paths.d.ts +17 -0
- package/dist/utils/workflow-paths.js +18 -0
- package/dist/utils/yaml-structure.d.ts +7 -0
- package/dist/utils/yaml-structure.js +7 -11
- package/package.json +68 -48
- package/dist/hooks/before-tool.js +0 -19
package/dist/tui.js
ADDED
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import { readHookStatusSnapshot, readSessionLatestLogs, resolveHooksLogFile, resolveHooksStatusFile } from './hooks/hooks-persistence.js';
|
|
3
|
+
import {} from './types/hook-types.js';
|
|
4
|
+
import { buildHookLogsBody, buildHookRunDetailBody, getActiveDialogHandle, openHookLogPopup } from './tui/hooks-dialogs.js';
|
|
5
|
+
import { buildActiveHooksView, colorForStatus, formatHookLogsMessage, formatHookRunSummary, statusLabelFor, } from './tui/hooks-presenter.js';
|
|
6
|
+
import { materializeSidebar } from './tui/hooks-sidebar.js';
|
|
7
|
+
import { loadSolidRuntime } from './tui/primitives.js';
|
|
8
|
+
export { buildActiveHooksView, buildHookLogsBody, buildHookRunDetailBody, colorForStatus, formatHookLogsMessage, formatHookRunSummary, getActiveDialogHandle, statusLabelFor, };
|
|
9
|
+
const SIDEBAR_SLOT = 'sidebar_content';
|
|
10
|
+
const DEFAULT_STATUS_POLL_MS = 200;
|
|
11
|
+
export const registerActiveHooksSidebar = async (api, directory, loadSolid = loadSolidRuntime) => {
|
|
12
|
+
const solid = await loadSolid();
|
|
13
|
+
if (solid === null) {
|
|
14
|
+
console.warn('[owflow] TUI Solid runtime unavailable; cannot render the active hooks panel.');
|
|
15
|
+
return false;
|
|
16
|
+
}
|
|
17
|
+
const view = buildActiveHooksView(directory);
|
|
18
|
+
const statusFile = resolveHooksStatusFile(directory);
|
|
19
|
+
const handles = [];
|
|
20
|
+
let statuses = new Map();
|
|
21
|
+
let lastMtime;
|
|
22
|
+
const sessionEpochs = new Map();
|
|
23
|
+
const pollingEnabled = process.env.OWFLOW_HOOKS_DISABLED !== '1';
|
|
24
|
+
if (pollingEnabled) {
|
|
25
|
+
const renderer = api.renderer;
|
|
26
|
+
const tick = () => {
|
|
27
|
+
try {
|
|
28
|
+
const stat = fs.statSync(statusFile);
|
|
29
|
+
if (stat.isFile() && lastMtime !== stat.mtimeMs) {
|
|
30
|
+
lastMtime = stat.mtimeMs;
|
|
31
|
+
const result = readHookStatusSnapshot(statusFile);
|
|
32
|
+
if (result.kind === 'ok') {
|
|
33
|
+
statuses = new Map(Object.entries(result.snapshot.hooks));
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
else if (!stat.isFile()) {
|
|
37
|
+
lastMtime = undefined;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
catch {
|
|
41
|
+
lastMtime = undefined;
|
|
42
|
+
}
|
|
43
|
+
for (const handle of handles) {
|
|
44
|
+
if (handle.disposed())
|
|
45
|
+
continue;
|
|
46
|
+
if (handle.applyStatuses(statuses)) {
|
|
47
|
+
renderer.requestRender();
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
for (let i = handles.length - 1; i >= 0; i -= 1) {
|
|
51
|
+
if (handles[i].disposed())
|
|
52
|
+
handles.splice(i, 1);
|
|
53
|
+
}
|
|
54
|
+
};
|
|
55
|
+
const timer = setInterval(tick, DEFAULT_STATUS_POLL_MS);
|
|
56
|
+
timer.unref?.();
|
|
57
|
+
api.lifecycle.onDispose(() => {
|
|
58
|
+
clearInterval(timer);
|
|
59
|
+
getActiveDialogHandle()?.dispose();
|
|
60
|
+
for (const handle of handles) {
|
|
61
|
+
handle.dispose();
|
|
62
|
+
}
|
|
63
|
+
handles.length = 0;
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
else {
|
|
67
|
+
api.lifecycle.onDispose(() => {
|
|
68
|
+
getActiveDialogHandle()?.dispose();
|
|
69
|
+
for (const handle of handles) {
|
|
70
|
+
handle.dispose();
|
|
71
|
+
}
|
|
72
|
+
handles.length = 0;
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
api.slots.register({
|
|
76
|
+
order: 50,
|
|
77
|
+
slots: {
|
|
78
|
+
[SIDEBAR_SLOT]: (ctx, props) => {
|
|
79
|
+
const sessionID = props?.session_id;
|
|
80
|
+
const key = sessionID ?? '';
|
|
81
|
+
const notBefore = sessionEpochs.get(key) ?? (sessionEpochs.set(key, Date.now()), sessionEpochs.get(key));
|
|
82
|
+
const initialStates = new Map();
|
|
83
|
+
if (sessionID !== undefined && sessionID.length > 0) {
|
|
84
|
+
for (const row of view.rows) {
|
|
85
|
+
try {
|
|
86
|
+
const rowLogFile = resolveHooksLogFile(directory, row.name);
|
|
87
|
+
const sessionLogs = readSessionLatestLogs(rowLogFile, sessionID);
|
|
88
|
+
const rec = sessionLogs.get(row.event);
|
|
89
|
+
if (rec)
|
|
90
|
+
initialStates.set(row.event, rec.status);
|
|
91
|
+
}
|
|
92
|
+
catch { }
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
const handle = materializeSidebar(view, ctx.theme.current, solid, (row, theme) => {
|
|
96
|
+
if (pollingEnabled) {
|
|
97
|
+
const rowLogFile = resolveHooksLogFile(directory, row.name);
|
|
98
|
+
openHookLogPopup(api, solid, rowLogFile, row, theme, notBefore, sessionID, statusFile, statuses.get(row.event));
|
|
99
|
+
}
|
|
100
|
+
}, () => api.renderer.requestRender(), pollingEnabled, notBefore, sessionID, initialStates);
|
|
101
|
+
handle.applyStatuses(statuses);
|
|
102
|
+
handles.push(handle);
|
|
103
|
+
if (typeof solid.onCleanup === 'function') {
|
|
104
|
+
try {
|
|
105
|
+
solid.onCleanup(() => {
|
|
106
|
+
handle.dispose();
|
|
107
|
+
const idx = handles.indexOf(handle);
|
|
108
|
+
if (idx !== -1) {
|
|
109
|
+
handles.splice(idx, 1);
|
|
110
|
+
}
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
catch { }
|
|
114
|
+
}
|
|
115
|
+
return handle.root;
|
|
116
|
+
},
|
|
117
|
+
},
|
|
118
|
+
});
|
|
119
|
+
return true;
|
|
120
|
+
};
|
|
121
|
+
const tui = async (api) => {
|
|
122
|
+
const directory = api.state.path.directory;
|
|
123
|
+
if (typeof directory !== 'string' || directory.length === 0) {
|
|
124
|
+
console.warn('[owflow] workspace directory unavailable; cannot render the active hooks panel.');
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
await registerActiveHooksSidebar(api, directory);
|
|
128
|
+
};
|
|
129
|
+
export default { id: 'owflow', tui };
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
export declare enum HookEvent {
|
|
2
|
+
ToolExecuteBefore = "tool.execute.before",
|
|
3
|
+
ToolExecuteAfter = "tool.execute.after",
|
|
4
|
+
ToolDefinition = "tool.definition",
|
|
5
|
+
ChatMessage = "chat.message",
|
|
6
|
+
ChatParams = "chat.params",
|
|
7
|
+
ChatHeaders = "chat.headers",
|
|
8
|
+
CommandExecuteBefore = "command.execute.before",
|
|
9
|
+
ShellEnv = "shell.env",
|
|
10
|
+
ExperimentalChatMessagesTransform = "experimental.chat.messages.transform",
|
|
11
|
+
ExperimentalChatSystemTransform = "experimental.chat.system.transform",
|
|
12
|
+
ExperimentalSessionCompacting = "experimental.session.compacting",
|
|
13
|
+
ExperimentalCompactionAutocontinue = "experimental.compaction.autocontinue",
|
|
14
|
+
ExperimentalTextComplete = "experimental.text.complete",
|
|
15
|
+
Config = "config",
|
|
16
|
+
Event = "event",
|
|
17
|
+
Dispose = "dispose"
|
|
18
|
+
}
|
|
19
|
+
export type DispatchableEvent = `${HookEvent}`;
|
|
20
|
+
export declare const DISPATCHABLE_EVENTS: readonly DispatchableEvent[];
|
|
21
|
+
export declare const HOST_EVENT_NAMES: readonly ["command.executed", "file.edited", "file.watcher.updated", "installation.update-available", "installation.updated", "lsp.client.diagnostics", "lsp.updated", "message.part.removed", "message.part.updated", "message.removed", "message.updated", "permission.replied", "permission.updated", "pty.created", "pty.deleted", "pty.exited", "pty.updated", "server.connected", "server.instance.disposed", "session.compacted", "session.created", "session.deleted", "session.diff", "session.error", "session.idle", "session.status", "session.updated", "todo.updated", "tui.command.execute", "tui.prompt.append", "tui.toast.show", "vcs.branch.updated"];
|
|
22
|
+
export type HostEventName = (typeof HOST_EVENT_NAMES)[number];
|
|
23
|
+
export type HookEventName = DispatchableEvent | HostEventName;
|
|
24
|
+
export declare const HOOK_EVENT_NAMES: readonly HookEventName[];
|
|
25
|
+
export declare const EXCLUDED_EVENT_NAMES: readonly ["permission.ask", "tool", "auth", "provider", "experimental.provider.small_model"];
|
|
26
|
+
export declare const isHostEventName: (value: string) => value is HostEventName;
|
|
27
|
+
/**
|
|
28
|
+
* Validation digest mode for `scanHooks`.
|
|
29
|
+
* - `FailFast`: Throws on any validation failure at startup/runtime to prevent
|
|
30
|
+
* running with invalid or ambiguous hooks (fail-closed contract).
|
|
31
|
+
* - `Report`: Collects failures without throwing, allowing UI surfaces (TUI sidebar)
|
|
32
|
+
* and test suites to safely inspect hook status without crashing the process.
|
|
33
|
+
*/
|
|
34
|
+
export declare enum HookDigestMode {
|
|
35
|
+
FailFast = "fail-fast",
|
|
36
|
+
Report = "report"
|
|
37
|
+
}
|
|
38
|
+
export interface InterpreterResolution {
|
|
39
|
+
readonly resolved?: string;
|
|
40
|
+
readonly expected: string;
|
|
41
|
+
readonly available: boolean;
|
|
42
|
+
}
|
|
43
|
+
export interface HookRegistryEntry {
|
|
44
|
+
readonly path: string;
|
|
45
|
+
readonly event: HookEventName;
|
|
46
|
+
readonly interpreter: InterpreterResolution;
|
|
47
|
+
readonly status: 'registered' | 'will-fail-closed';
|
|
48
|
+
}
|
|
49
|
+
export interface HookValidationFailure {
|
|
50
|
+
readonly path: string;
|
|
51
|
+
readonly error: string;
|
|
52
|
+
}
|
|
53
|
+
export interface HookScan {
|
|
54
|
+
readonly entries: readonly HookRegistryEntry[];
|
|
55
|
+
readonly failures: readonly HookValidationFailure[];
|
|
56
|
+
}
|
|
57
|
+
export declare enum HookRunState {
|
|
58
|
+
IDLE = "idle",
|
|
59
|
+
PENDING = "pending",
|
|
60
|
+
SUCCESS = "success",
|
|
61
|
+
FAILURE = "failure"
|
|
62
|
+
}
|
|
63
|
+
export interface HookRuntimeEntry {
|
|
64
|
+
readonly status: HookRunState;
|
|
65
|
+
readonly startedAt: number;
|
|
66
|
+
readonly finishedAt?: number;
|
|
67
|
+
readonly durationMs?: number;
|
|
68
|
+
readonly reason?: string;
|
|
69
|
+
readonly sessionID?: string;
|
|
70
|
+
}
|
|
71
|
+
export interface HookStatusSnapshot {
|
|
72
|
+
readonly version: 1;
|
|
73
|
+
readonly pid: number;
|
|
74
|
+
readonly workspace: string;
|
|
75
|
+
readonly updatedAt: number;
|
|
76
|
+
readonly hooks: Readonly<Record<string, HookRuntimeEntry>>;
|
|
77
|
+
}
|
|
78
|
+
export type HookStatusReadResult = {
|
|
79
|
+
readonly kind: 'missing' | 'invalid';
|
|
80
|
+
} | {
|
|
81
|
+
readonly kind: 'ok';
|
|
82
|
+
readonly snapshot: HookStatusSnapshot;
|
|
83
|
+
};
|
|
84
|
+
export interface HookRunLogRecord {
|
|
85
|
+
readonly version: 1;
|
|
86
|
+
readonly runId: string;
|
|
87
|
+
readonly pid: number;
|
|
88
|
+
readonly workspace: string;
|
|
89
|
+
readonly event: HookEventName;
|
|
90
|
+
readonly hook: string;
|
|
91
|
+
readonly name: string;
|
|
92
|
+
readonly sessionID?: string;
|
|
93
|
+
readonly startedAt: number;
|
|
94
|
+
readonly finishedAt: number;
|
|
95
|
+
readonly durationMs: number;
|
|
96
|
+
readonly status: HookRunState;
|
|
97
|
+
readonly exitCode?: number | null;
|
|
98
|
+
readonly signal?: string | null;
|
|
99
|
+
readonly reason?: string;
|
|
100
|
+
readonly stdout?: string;
|
|
101
|
+
readonly stderr?: string;
|
|
102
|
+
}
|
|
103
|
+
export type HookLogReadResult = {
|
|
104
|
+
readonly kind: 'missing' | 'invalid';
|
|
105
|
+
} | {
|
|
106
|
+
readonly kind: 'ok';
|
|
107
|
+
readonly records: readonly HookRunLogRecord[];
|
|
108
|
+
readonly truncated: boolean;
|
|
109
|
+
};
|
|
110
|
+
export type Dispatcher = (event: HookEventName, input?: unknown, output?: Record<string, unknown>) => Promise<void>;
|
|
111
|
+
export type HookReportRow = Readonly<Record<'path' | 'event' | 'interpreter' | 'status', string>>;
|
|
112
|
+
export interface HookReportInput {
|
|
113
|
+
readonly entries: readonly HookRegistryEntry[];
|
|
114
|
+
readonly failures?: readonly HookValidationFailure[];
|
|
115
|
+
readonly bypassed?: boolean;
|
|
116
|
+
}
|
|
117
|
+
export interface HooksWrapper {
|
|
118
|
+
readonly dispatch: Dispatcher;
|
|
119
|
+
readonly snapshot: HookReportInput;
|
|
120
|
+
dispose(): void;
|
|
121
|
+
}
|
|
122
|
+
export interface HooksWrapperOptions {
|
|
123
|
+
readonly statusFile?: string;
|
|
124
|
+
readonly logFile?: string;
|
|
125
|
+
}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
export var HookEvent;
|
|
2
|
+
(function (HookEvent) {
|
|
3
|
+
HookEvent["ToolExecuteBefore"] = "tool.execute.before";
|
|
4
|
+
HookEvent["ToolExecuteAfter"] = "tool.execute.after";
|
|
5
|
+
HookEvent["ToolDefinition"] = "tool.definition";
|
|
6
|
+
HookEvent["ChatMessage"] = "chat.message";
|
|
7
|
+
HookEvent["ChatParams"] = "chat.params";
|
|
8
|
+
HookEvent["ChatHeaders"] = "chat.headers";
|
|
9
|
+
HookEvent["CommandExecuteBefore"] = "command.execute.before";
|
|
10
|
+
HookEvent["ShellEnv"] = "shell.env";
|
|
11
|
+
HookEvent["ExperimentalChatMessagesTransform"] = "experimental.chat.messages.transform";
|
|
12
|
+
HookEvent["ExperimentalChatSystemTransform"] = "experimental.chat.system.transform";
|
|
13
|
+
HookEvent["ExperimentalSessionCompacting"] = "experimental.session.compacting";
|
|
14
|
+
HookEvent["ExperimentalCompactionAutocontinue"] = "experimental.compaction.autocontinue";
|
|
15
|
+
HookEvent["ExperimentalTextComplete"] = "experimental.text.complete";
|
|
16
|
+
HookEvent["Config"] = "config";
|
|
17
|
+
HookEvent["Event"] = "event";
|
|
18
|
+
HookEvent["Dispose"] = "dispose";
|
|
19
|
+
})(HookEvent || (HookEvent = {}));
|
|
20
|
+
export const DISPATCHABLE_EVENTS = Object.values(HookEvent);
|
|
21
|
+
export const HOST_EVENT_NAMES = [
|
|
22
|
+
'command.executed',
|
|
23
|
+
'file.edited',
|
|
24
|
+
'file.watcher.updated',
|
|
25
|
+
'installation.update-available',
|
|
26
|
+
'installation.updated',
|
|
27
|
+
'lsp.client.diagnostics',
|
|
28
|
+
'lsp.updated',
|
|
29
|
+
'message.part.removed',
|
|
30
|
+
'message.part.updated',
|
|
31
|
+
'message.removed',
|
|
32
|
+
'message.updated',
|
|
33
|
+
'permission.replied',
|
|
34
|
+
'permission.updated',
|
|
35
|
+
'pty.created',
|
|
36
|
+
'pty.deleted',
|
|
37
|
+
'pty.exited',
|
|
38
|
+
'pty.updated',
|
|
39
|
+
'server.connected',
|
|
40
|
+
'server.instance.disposed',
|
|
41
|
+
'session.compacted',
|
|
42
|
+
'session.created',
|
|
43
|
+
'session.deleted',
|
|
44
|
+
'session.diff',
|
|
45
|
+
'session.error',
|
|
46
|
+
'session.idle',
|
|
47
|
+
'session.status',
|
|
48
|
+
'session.updated',
|
|
49
|
+
'todo.updated',
|
|
50
|
+
'tui.command.execute',
|
|
51
|
+
'tui.prompt.append',
|
|
52
|
+
'tui.toast.show',
|
|
53
|
+
'vcs.branch.updated',
|
|
54
|
+
];
|
|
55
|
+
export const HOOK_EVENT_NAMES = [...DISPATCHABLE_EVENTS, ...HOST_EVENT_NAMES];
|
|
56
|
+
export const EXCLUDED_EVENT_NAMES = ['permission.ask', 'tool', 'auth', 'provider', 'experimental.provider.small_model'];
|
|
57
|
+
export const isHostEventName = (value) => HOST_EVENT_NAMES.includes(value);
|
|
58
|
+
/**
|
|
59
|
+
* Validation digest mode for `scanHooks`.
|
|
60
|
+
* - `FailFast`: Throws on any validation failure at startup/runtime to prevent
|
|
61
|
+
* running with invalid or ambiguous hooks (fail-closed contract).
|
|
62
|
+
* - `Report`: Collects failures without throwing, allowing UI surfaces (TUI sidebar)
|
|
63
|
+
* and test suites to safely inspect hook status without crashing the process.
|
|
64
|
+
*/
|
|
65
|
+
export var HookDigestMode;
|
|
66
|
+
(function (HookDigestMode) {
|
|
67
|
+
HookDigestMode["FailFast"] = "fail-fast";
|
|
68
|
+
HookDigestMode["Report"] = "report";
|
|
69
|
+
})(HookDigestMode || (HookDigestMode = {}));
|
|
70
|
+
export var HookRunState;
|
|
71
|
+
(function (HookRunState) {
|
|
72
|
+
HookRunState["IDLE"] = "idle";
|
|
73
|
+
HookRunState["PENDING"] = "pending";
|
|
74
|
+
HookRunState["SUCCESS"] = "success";
|
|
75
|
+
HookRunState["FAILURE"] = "failure";
|
|
76
|
+
})(HookRunState || (HookRunState = {}));
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export interface OpenCodeConfig {
|
|
2
|
+
agent: any;
|
|
3
|
+
command: Record<string, OpenCodeCommand>;
|
|
4
|
+
skills: any;
|
|
5
|
+
small_model?: string;
|
|
6
|
+
}
|
|
7
|
+
export interface OpenCodeCommand {
|
|
8
|
+
template: string;
|
|
9
|
+
description?: string;
|
|
10
|
+
agent?: string;
|
|
11
|
+
model?: string;
|
|
12
|
+
subtask?: boolean;
|
|
13
|
+
}
|
package/dist/utils/file-util.js
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
import matter from
|
|
2
|
-
import fs from
|
|
3
|
-
import path from
|
|
4
|
-
import { PLUGIN_ROOT } from
|
|
1
|
+
import matter from 'gray-matter';
|
|
2
|
+
import fs from 'node:fs';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import { PLUGIN_ROOT } from './plugin-info.js';
|
|
5
5
|
export const loadMarkdownDir = (dirPath) => {
|
|
6
6
|
const fullPath = path.join(PLUGIN_ROOT, dirPath);
|
|
7
7
|
let files;
|
|
8
8
|
try {
|
|
9
|
-
files = fs.readdirSync(fullPath).filter((f) => f.endsWith(
|
|
9
|
+
files = fs.readdirSync(fullPath).filter((f) => f.endsWith('.md'));
|
|
10
10
|
}
|
|
11
11
|
catch {
|
|
12
12
|
return [];
|
|
@@ -14,7 +14,7 @@ export const loadMarkdownDir = (dirPath) => {
|
|
|
14
14
|
const parsed = [];
|
|
15
15
|
for (const f of files) {
|
|
16
16
|
try {
|
|
17
|
-
const raw = fs.readFileSync(path.join(fullPath, f),
|
|
17
|
+
const raw = fs.readFileSync(path.join(fullPath, f), 'utf8');
|
|
18
18
|
parsed.push(matter(raw));
|
|
19
19
|
}
|
|
20
20
|
catch (e) {
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
export interface ForkTaskInput {
|
|
2
|
+
/** Root of all tasks, e.g. the value of `workflowRelativePath(TASKS_DIRECTORY_NAME)`. */
|
|
3
|
+
taskRoot: string;
|
|
4
|
+
/** Source task: absolute/relative path or a bare identifier resolved under <taskRoot>/research/ */
|
|
5
|
+
source: string;
|
|
6
|
+
/** Kebab-case slug derived by the agent, without date prefix */
|
|
7
|
+
slug: string;
|
|
8
|
+
/** Fork point: completed step slug up to which completed_phases are kept */
|
|
9
|
+
from: string;
|
|
10
|
+
/** Short description of what the fork should explore (stamped into the fork state) */
|
|
11
|
+
intent?: string;
|
|
12
|
+
/** Working directory used to resolve relative paths (defaults to process.cwd()) */
|
|
13
|
+
cwd?: string;
|
|
14
|
+
}
|
|
15
|
+
export interface ForkTaskResult {
|
|
16
|
+
forkPath: string;
|
|
17
|
+
forkName: string;
|
|
18
|
+
forkPoint: string;
|
|
19
|
+
}
|
|
20
|
+
/** Lowercase kebab-case, exactly 3-5 words of [a-z0-9] (Task Name Generation convention). */
|
|
21
|
+
export declare const RESEARCH_SLUG_PATTERN: RegExp;
|
|
22
|
+
/**
|
|
23
|
+
* Copies the whole source task directory into a new date-prefixed directory
|
|
24
|
+
* named from the slug, then trims the copied state so the fork continues
|
|
25
|
+
* from the fork point onwards. The source task is never modified.
|
|
26
|
+
*/
|
|
27
|
+
export declare const forkTask: (input: ForkTaskInput) => ForkTaskResult;
|
package/dist/utils/fork-task.js
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
|
-
import fs from
|
|
2
|
-
import path from
|
|
3
|
-
import yaml from
|
|
4
|
-
import {
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import yaml from 'yaml';
|
|
4
|
+
import { RESEARCH_DIRECTORY_NAME, TASKS_DIRECTORY_NAME, workflowRelativePath } from './workflow-paths.js';
|
|
5
|
+
import { parseYamlFile } from './yaml-structure.js';
|
|
5
6
|
/** Lowercase kebab-case, exactly 3-5 words of [a-z0-9] (Task Name Generation convention). */
|
|
6
7
|
export const RESEARCH_SLUG_PATTERN = /^[a-z0-9]+(?:-[a-z0-9]+){2,4}$/;
|
|
7
|
-
const FORK_TERMINAL_STEP =
|
|
8
|
+
const FORK_TERMINAL_STEP = 'research-completed';
|
|
8
9
|
/**
|
|
9
10
|
* Copies the whole source task directory into a new date-prefixed directory
|
|
10
11
|
* named from the slug, then trims the copied state so the fork continues
|
|
@@ -16,30 +17,27 @@ export const forkTask = (input) => {
|
|
|
16
17
|
throw new Error("INVALID_NAME: slug must be lowercase kebab-case, 3-5 words of [a-z0-9] (e.g. 'compare-caching-strategies')");
|
|
17
18
|
}
|
|
18
19
|
const taskRoot = path.resolve(cwd, input.taskRoot);
|
|
19
|
-
const researchRoot = path.join(taskRoot,
|
|
20
|
-
const sourceDir = [
|
|
21
|
-
path.resolve(cwd, input.source),
|
|
22
|
-
path.resolve(researchRoot, input.source),
|
|
23
|
-
].find((candidate) => fs.existsSync(candidate) && fs.statSync(candidate).isDirectory());
|
|
20
|
+
const researchRoot = path.join(taskRoot, 'research');
|
|
21
|
+
const sourceDir = [path.resolve(cwd, input.source), path.resolve(researchRoot, input.source)].find((candidate) => fs.existsSync(candidate) && fs.statSync(candidate).isDirectory());
|
|
24
22
|
if (sourceDir === undefined) {
|
|
25
|
-
throw new Error(`INVALID_SOURCE: no task directory at ${input.source}. Hint: pass the task path or a directory name under
|
|
23
|
+
throw new Error(`INVALID_SOURCE: no task directory at ${input.source}. Hint: pass the task path or a directory name under ${workflowRelativePath(TASKS_DIRECTORY_NAME, RESEARCH_DIRECTORY_NAME)}/.`);
|
|
26
24
|
}
|
|
27
25
|
if (!sourceDir.startsWith(researchRoot + path.sep)) {
|
|
28
|
-
throw new Error(`INVALID_SOURCE: ${input.source} is not a research task directory under
|
|
26
|
+
throw new Error(`INVALID_SOURCE: ${input.source} is not a research task directory under ${workflowRelativePath(TASKS_DIRECTORY_NAME, RESEARCH_DIRECTORY_NAME)}/.`);
|
|
29
27
|
}
|
|
30
|
-
const parsedSourceState = parseYamlFile(path.join(sourceDir,
|
|
28
|
+
const parsedSourceState = parseYamlFile(path.join(sourceDir, 'orchestrator-state.yml'));
|
|
31
29
|
if (!parsedSourceState.ok || !parsedSourceState.data) {
|
|
32
30
|
throw new Error(`INVALID_SOURCE: ${parsedSourceState.error ?? `no orchestrator-state.yml in ${input.source}`}`);
|
|
33
31
|
}
|
|
34
32
|
const sourceState = parsedSourceState.data;
|
|
35
33
|
const completed = Array.isArray(sourceState?.orchestrator?.completed_phases)
|
|
36
|
-
? sourceState.orchestrator.completed_phases.filter((s) => typeof s ===
|
|
34
|
+
? sourceState.orchestrator.completed_phases.filter((s) => typeof s === 'string')
|
|
37
35
|
: [];
|
|
38
36
|
if (input.from === FORK_TERMINAL_STEP) {
|
|
39
37
|
throw new Error(`UNKNOWN_STEP: ${FORK_TERMINAL_STEP} is not a valid fork point — nothing diverges after completion.`);
|
|
40
38
|
}
|
|
41
39
|
if (!completed.includes(input.from)) {
|
|
42
|
-
throw new Error(`UNKNOWN_STEP: ${input.from} is not completed in the source task. Completed steps: ${completed.join(
|
|
40
|
+
throw new Error(`UNKNOWN_STEP: ${input.from} is not completed in the source task. Completed steps: ${completed.join(', ') || 'none'}`);
|
|
43
41
|
}
|
|
44
42
|
const forkName = `${new Date().toISOString().slice(0, 10)}-${input.slug}`;
|
|
45
43
|
const forkPath = path.join(researchRoot, forkName);
|
|
@@ -50,11 +48,8 @@ export const forkTask = (input) => {
|
|
|
50
48
|
const forkState = structuredClone(sourceState);
|
|
51
49
|
forkState.orchestrator.started_phase = null;
|
|
52
50
|
forkState.orchestrator.completed_phases = completed.slice(0, completed.indexOf(input.from) + 1);
|
|
53
|
-
forkState.orchestrator.entry_point =
|
|
54
|
-
forkState.orchestrator.task_path = path
|
|
55
|
-
.relative(cwd, forkPath)
|
|
56
|
-
.split(path.sep)
|
|
57
|
-
.join("/");
|
|
51
|
+
forkState.orchestrator.entry_point = 'research-fork';
|
|
52
|
+
forkState.orchestrator.task_path = path.relative(cwd, forkPath).split(path.sep).join('/');
|
|
58
53
|
forkState.orchestrator.options = {
|
|
59
54
|
...(forkState.orchestrator.options ?? {}),
|
|
60
55
|
fork_information: {
|
|
@@ -66,9 +61,9 @@ export const forkTask = (input) => {
|
|
|
66
61
|
};
|
|
67
62
|
forkState.task = {
|
|
68
63
|
...forkState.task,
|
|
69
|
-
status:
|
|
70
|
-
title: `${forkState.task?.title ??
|
|
64
|
+
status: 'in_progress',
|
|
65
|
+
title: `${forkState.task?.title ?? 'Research task'} (fork of ${path.basename(sourceDir)})`,
|
|
71
66
|
};
|
|
72
|
-
fs.writeFileSync(path.join(forkPath,
|
|
67
|
+
fs.writeFileSync(path.join(forkPath, 'orchestrator-state.yml'), yaml.stringify(forkState), 'utf8');
|
|
73
68
|
return { forkPath, forkName, forkPoint: input.from };
|
|
74
69
|
};
|
|
@@ -1,17 +1,15 @@
|
|
|
1
|
-
import path from
|
|
2
|
-
import fs from
|
|
3
|
-
import { fileURLToPath } from
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
import fs from 'node:fs';
|
|
3
|
+
import { fileURLToPath } from 'node:url';
|
|
4
4
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
5
|
-
export const PLUGIN_ROOT = path.join(__dirname,
|
|
6
|
-
const UNKNOWN_VERSION =
|
|
5
|
+
export const PLUGIN_ROOT = path.join(__dirname, '..'); // Points to dist/ where skills/commands/agents are copied
|
|
6
|
+
const UNKNOWN_VERSION = 'unknown';
|
|
7
7
|
export const readPluginVersion = (pluginRoot = PLUGIN_ROOT) => {
|
|
8
8
|
try {
|
|
9
|
-
const packageJsonPath = path.join(pluginRoot,
|
|
10
|
-
const raw = fs.readFileSync(packageJsonPath,
|
|
9
|
+
const packageJsonPath = path.join(pluginRoot, '..', 'package.json');
|
|
10
|
+
const raw = fs.readFileSync(packageJsonPath, 'utf8');
|
|
11
11
|
const version = JSON.parse(raw).version;
|
|
12
|
-
return typeof version ===
|
|
13
|
-
? version
|
|
14
|
-
: UNKNOWN_VERSION;
|
|
12
|
+
return typeof version === 'string' && version.length > 0 ? version : UNKNOWN_VERSION;
|
|
15
13
|
}
|
|
16
14
|
catch {
|
|
17
15
|
return UNKNOWN_VERSION;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Canonical owflow directory names. Host projects and every internal surface
|
|
3
|
+
* (server plugin, CLI, TUI, hooks, tools) refer to these constants instead of
|
|
4
|
+
* repeating path fragments.
|
|
5
|
+
*/
|
|
6
|
+
export declare const WORKFLOW_DIRECTORY_NAME = ".owflow";
|
|
7
|
+
export declare const HOOKS_DIRECTORY_NAME = "hooks";
|
|
8
|
+
export declare const TASKS_DIRECTORY_NAME = "tasks";
|
|
9
|
+
export declare const DOCS_DIRECTORY_NAME = "docs";
|
|
10
|
+
export declare const RESEARCH_DIRECTORY_NAME = "research";
|
|
11
|
+
export declare const workflowPath: (root: string, ...segments: readonly string[]) => string;
|
|
12
|
+
export declare const workflowRelativePath: (...segments: readonly string[]) => string;
|
|
13
|
+
export declare const hooksDirectory: (root: string) => string;
|
|
14
|
+
export declare const tasksDirectory: (root: string) => string;
|
|
15
|
+
export declare const docsDirectory: (root: string) => string;
|
|
16
|
+
export declare const hooksLocationLabel: string;
|
|
17
|
+
export declare const tasksLocationLabel: string;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
/**
|
|
3
|
+
* Canonical owflow directory names. Host projects and every internal surface
|
|
4
|
+
* (server plugin, CLI, TUI, hooks, tools) refer to these constants instead of
|
|
5
|
+
* repeating path fragments.
|
|
6
|
+
*/
|
|
7
|
+
export const WORKFLOW_DIRECTORY_NAME = '.owflow';
|
|
8
|
+
export const HOOKS_DIRECTORY_NAME = 'hooks';
|
|
9
|
+
export const TASKS_DIRECTORY_NAME = 'tasks';
|
|
10
|
+
export const DOCS_DIRECTORY_NAME = 'docs';
|
|
11
|
+
export const RESEARCH_DIRECTORY_NAME = 'research';
|
|
12
|
+
export const workflowPath = (root, ...segments) => path.join(root, WORKFLOW_DIRECTORY_NAME, ...segments);
|
|
13
|
+
export const workflowRelativePath = (...segments) => [WORKFLOW_DIRECTORY_NAME, ...segments].join('/');
|
|
14
|
+
export const hooksDirectory = (root) => workflowPath(root, HOOKS_DIRECTORY_NAME);
|
|
15
|
+
export const tasksDirectory = (root) => workflowPath(root, TASKS_DIRECTORY_NAME);
|
|
16
|
+
export const docsDirectory = (root) => workflowPath(root, DOCS_DIRECTORY_NAME);
|
|
17
|
+
export const hooksLocationLabel = workflowRelativePath(HOOKS_DIRECTORY_NAME);
|
|
18
|
+
export const tasksLocationLabel = workflowRelativePath(TASKS_DIRECTORY_NAME);
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export interface ParsedYaml {
|
|
2
|
+
ok: boolean;
|
|
3
|
+
data?: any;
|
|
4
|
+
error?: string;
|
|
5
|
+
}
|
|
6
|
+
export declare const parseYamlFile: (absolutePath: string) => ParsedYaml;
|
|
7
|
+
export declare const findStructuralErrors: (templateObj: any, targetObj: any, currentPath?: string) => string[];
|
|
@@ -1,30 +1,26 @@
|
|
|
1
|
-
import fs from
|
|
2
|
-
import yaml from
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import yaml from 'yaml';
|
|
3
3
|
export const parseYamlFile = (absolutePath) => {
|
|
4
4
|
try {
|
|
5
|
-
const fileContent = fs.readFileSync(absolutePath,
|
|
5
|
+
const fileContent = fs.readFileSync(absolutePath, 'utf8');
|
|
6
6
|
return { ok: true, data: yaml.parse(fileContent) };
|
|
7
7
|
}
|
|
8
8
|
catch (error) {
|
|
9
9
|
return { ok: false, error: error.message };
|
|
10
10
|
}
|
|
11
11
|
};
|
|
12
|
-
export const findStructuralErrors = (templateObj, targetObj, currentPath =
|
|
12
|
+
export const findStructuralErrors = (templateObj, targetObj, currentPath = '') => {
|
|
13
13
|
const errors = [];
|
|
14
|
-
if (typeof templateObj !==
|
|
14
|
+
if (typeof templateObj !== 'object' || templateObj === null) {
|
|
15
15
|
return errors;
|
|
16
16
|
}
|
|
17
17
|
for (const key of Object.keys(templateObj)) {
|
|
18
18
|
const newPath = currentPath ? `${currentPath}.${key}` : key;
|
|
19
|
-
if (typeof targetObj !==
|
|
20
|
-
targetObj === null ||
|
|
21
|
-
!(key in targetObj)) {
|
|
19
|
+
if (typeof targetObj !== 'object' || targetObj === null || !(key in targetObj)) {
|
|
22
20
|
errors.push(`Missing key: ${newPath}`);
|
|
23
21
|
continue;
|
|
24
22
|
}
|
|
25
|
-
if (typeof templateObj[key] ===
|
|
26
|
-
templateObj[key] !== null &&
|
|
27
|
-
!Array.isArray(templateObj[key])) {
|
|
23
|
+
if (typeof templateObj[key] === 'object' && templateObj[key] !== null && !Array.isArray(templateObj[key])) {
|
|
28
24
|
errors.push(...findStructuralErrors(templateObj[key], targetObj[key], newPath));
|
|
29
25
|
}
|
|
30
26
|
}
|