@duypham93/openkit 0.2.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/.opencode/README.md +47 -0
- package/.opencode/install-manifest.json +41 -0
- package/.opencode/lib/artifact-scaffolder.js +111 -0
- package/.opencode/lib/contract-consistency.js +218 -0
- package/.opencode/lib/parallel-execution-rules.js +261 -0
- package/.opencode/lib/runtime-paths.js +95 -0
- package/.opencode/lib/runtime-summary.js +82 -0
- package/.opencode/lib/state-guard.js +99 -0
- package/.opencode/lib/task-board-rules.js +375 -0
- package/.opencode/lib/work-item-store.js +280 -0
- package/.opencode/lib/workflow-state-controller.js +1739 -0
- package/.opencode/lib/workflow-state-rules.js +331 -0
- package/.opencode/opencode.json +93 -0
- package/.opencode/package.json +3 -0
- package/.opencode/tests/artifact-scaffolder.test.js +733 -0
- package/.opencode/tests/multi-work-item-runtime.test.js +369 -0
- package/.opencode/tests/parallel-execution-runtime.test.js +259 -0
- package/.opencode/tests/session-start-hook.test.js +357 -0
- package/.opencode/tests/state-guard.test.js +124 -0
- package/.opencode/tests/task-board-rules.test.js +204 -0
- package/.opencode/tests/work-item-store.test.js +380 -0
- package/.opencode/tests/workflow-behavior.test.js +149 -0
- package/.opencode/tests/workflow-contract-consistency.test.js +387 -0
- package/.opencode/tests/workflow-state-cli.test.js +1275 -0
- package/.opencode/tests/workflow-state-controller.test.js +1038 -0
- package/.opencode/work-items/feature-001/state.json +70 -0
- package/.opencode/work-items/index.json +13 -0
- package/.opencode/workflow-state.js +489 -0
- package/.opencode/workflow-state.json +70 -0
- package/AGENTS.md +265 -0
- package/README.md +401 -0
- package/agents/architect-agent.md +63 -0
- package/agents/ba-agent.md +56 -0
- package/agents/code-reviewer.md +77 -0
- package/agents/fullstack-agent.md +115 -0
- package/agents/master-orchestrator.md +60 -0
- package/agents/pm-agent.md +56 -0
- package/agents/qa-agent.md +124 -0
- package/agents/tech-lead-agent.md +60 -0
- package/assets/install-bundle/README.md +7 -0
- package/assets/install-bundle/opencode/README.md +11 -0
- package/assets/install-bundle/opencode/agents/ArchitectAgent.md +63 -0
- package/assets/install-bundle/opencode/agents/BAAgent.md +56 -0
- package/assets/install-bundle/opencode/agents/CodeReviewer.md +77 -0
- package/assets/install-bundle/opencode/agents/FullstackAgent.md +115 -0
- package/assets/install-bundle/opencode/agents/MasterOrchestrator.md +60 -0
- package/assets/install-bundle/opencode/agents/PMAgent.md +56 -0
- package/assets/install-bundle/opencode/agents/QAAgent.md +124 -0
- package/assets/install-bundle/opencode/agents/TechLeadAgent.md +60 -0
- package/assets/install-bundle/opencode/commands/brainstorm.md +44 -0
- package/assets/install-bundle/opencode/commands/delivery.md +45 -0
- package/assets/install-bundle/opencode/commands/execute-plan.md +44 -0
- package/assets/install-bundle/opencode/commands/migrate.md +61 -0
- package/assets/install-bundle/opencode/commands/quick-task.md +45 -0
- package/assets/install-bundle/opencode/commands/task.md +46 -0
- package/assets/install-bundle/opencode/commands/write-plan.md +50 -0
- package/assets/install-bundle/opencode/context/core/lane-selection.md +54 -0
- package/assets/install-bundle/opencode/skills/brainstorming/SKILL.md +51 -0
- package/assets/install-bundle/opencode/skills/code-review/SKILL.md +48 -0
- package/assets/install-bundle/opencode/skills/subagent-driven-development/SKILL.md +79 -0
- package/assets/install-bundle/opencode/skills/systematic-debugging/SKILL.md +61 -0
- package/assets/install-bundle/opencode/skills/test-driven-development/SKILL.md +48 -0
- package/assets/install-bundle/opencode/skills/using-skills/SKILL.md +39 -0
- package/assets/install-bundle/opencode/skills/verification-before-completion/SKILL.md +137 -0
- package/assets/install-bundle/opencode/skills/writing-plans/SKILL.md +68 -0
- package/assets/install-bundle/opencode/skills/writing-specs/SKILL.md +47 -0
- package/assets/opencode.json.template +11 -0
- package/assets/openkit-install.json.template +19 -0
- package/bin/openkit.js +9 -0
- package/commands/brainstorm.md +44 -0
- package/commands/delivery.md +45 -0
- package/commands/execute-plan.md +44 -0
- package/commands/migrate.md +61 -0
- package/commands/quick-task.md +45 -0
- package/commands/task.md +46 -0
- package/commands/write-plan.md +50 -0
- package/context/core/approval-gates.md +146 -0
- package/context/core/code-quality.md +42 -0
- package/context/core/issue-routing.md +85 -0
- package/context/core/lane-selection.md +54 -0
- package/context/core/project-config.md +143 -0
- package/context/core/session-resume.md +85 -0
- package/context/core/workflow-state-schema.md +224 -0
- package/context/core/workflow.md +442 -0
- package/context/navigation.md +94 -0
- package/docs/adr/README.md +6 -0
- package/docs/architecture/2026-03-20-task-intake-dashboard.md +54 -0
- package/docs/architecture/README.md +7 -0
- package/docs/briefs/2026-03-20-task-intake-dashboard.md +48 -0
- package/docs/briefs/README.md +7 -0
- package/docs/governance/README.md +25 -0
- package/docs/governance/adr-policy.md +27 -0
- package/docs/governance/definition-of-done.md +17 -0
- package/docs/governance/naming-conventions.md +21 -0
- package/docs/governance/severity-levels.md +12 -0
- package/docs/maintainer/README.md +51 -0
- package/docs/operations/README.md +79 -0
- package/docs/operations/internal-records/2026-03-24-release-checklist.md +79 -0
- package/docs/operations/internal-records/2026-03-24-simplified-install-ux.md +36 -0
- package/docs/operations/internal-records/README.md +18 -0
- package/docs/operations/runbooks/README.md +23 -0
- package/docs/operations/runbooks/openkit-daily-usage.md +288 -0
- package/docs/operations/runbooks/workflow-state-smoke-tests.md +302 -0
- package/docs/operator/README.md +50 -0
- package/docs/plans/2026-03-20-task-intake-dashboard.md +49 -0
- package/docs/plans/2026-03-21-openkit-full-delivery-multi-task-runtime.md +521 -0
- package/docs/plans/2026-03-23-openkit-global-install-runtime.md +157 -0
- package/docs/plans/README.md +7 -0
- package/docs/qa/2026-03-20-task-intake-dashboard.md +41 -0
- package/docs/qa/README.md +7 -0
- package/docs/specs/2026-03-20-task-intake-dashboard.md +50 -0
- package/docs/specs/2026-03-21-openkit-full-delivery-multi-task-runtime.md +462 -0
- package/docs/specs/README.md +7 -0
- package/docs/templates/README.md +36 -0
- package/docs/templates/adr-template.md +18 -0
- package/docs/templates/architecture-template.md +31 -0
- package/docs/templates/implementation-plan-template.md +32 -0
- package/docs/templates/migration-baseline-checklist.md +48 -0
- package/docs/templates/migration-plan-template.md +52 -0
- package/docs/templates/migration-report-template.md +74 -0
- package/docs/templates/migration-verify-checklist.md +39 -0
- package/docs/templates/product-brief-template.md +32 -0
- package/docs/templates/qa-report-template.md +37 -0
- package/docs/templates/quick-task-template.md +36 -0
- package/docs/templates/spec-template.md +31 -0
- package/hooks/hooks.json +16 -0
- package/hooks/session-start +162 -0
- package/package.json +24 -0
- package/registry.json +328 -0
- package/skills/brainstorming/SKILL.md +51 -0
- package/skills/code-review/SKILL.md +48 -0
- package/skills/subagent-driven-development/SKILL.md +79 -0
- package/skills/systematic-debugging/SKILL.md +61 -0
- package/skills/test-driven-development/SKILL.md +48 -0
- package/skills/using-skills/SKILL.md +39 -0
- package/skills/verification-before-completion/SKILL.md +137 -0
- package/skills/writing-plans/SKILL.md +68 -0
- package/skills/writing-specs/SKILL.md +47 -0
- package/src/audit/vietnamese-detection.js +259 -0
- package/src/cli/commands/detect-vietnamese.js +24 -0
- package/src/cli/commands/doctor.js +33 -0
- package/src/cli/commands/help.js +33 -0
- package/src/cli/commands/init.js +25 -0
- package/src/cli/commands/install-global.js +26 -0
- package/src/cli/commands/install.js +25 -0
- package/src/cli/commands/run.js +63 -0
- package/src/cli/commands/uninstall.js +32 -0
- package/src/cli/commands/upgrade.js +25 -0
- package/src/cli/conflict-output.js +19 -0
- package/src/cli/index.js +56 -0
- package/src/global/doctor.js +101 -0
- package/src/global/ensure-install.js +32 -0
- package/src/global/install-state.js +73 -0
- package/src/global/launcher.js +51 -0
- package/src/global/materialize.js +123 -0
- package/src/global/paths.js +85 -0
- package/src/global/uninstall.js +25 -0
- package/src/global/workspace-state.js +63 -0
- package/src/install/asset-manifest.js +284 -0
- package/src/install/conflicts.js +43 -0
- package/src/install/discovery.js +138 -0
- package/src/install/install-state.js +136 -0
- package/src/install/materialize.js +158 -0
- package/src/install/merge-policy.js +145 -0
- package/src/runtime/doctor.js +281 -0
- package/src/runtime/launcher.js +49 -0
- package/src/runtime/opencode-layering.js +135 -0
- package/src/runtime/openkit-managed-summary.js +27 -0
- package/tests/cli/openkit-cli.test.js +417 -0
- package/tests/global/doctor.test.js +130 -0
- package/tests/global/ensure-install.test.js +105 -0
- package/tests/install/discovery.test.js +124 -0
- package/tests/install/install-state.test.js +346 -0
- package/tests/install/materialize.test.js +244 -0
- package/tests/install/merge-policy.test.js +177 -0
- package/tests/runtime/doctor.test.js +430 -0
- package/tests/runtime/launcher.test.js +230 -0
- package/tests/runtime/module-boundary.test.js +16 -0
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { ensureGlobalInstall } from '../../global/ensure-install.js';
|
|
2
|
+
import { launchGlobalOpenKit } from '../../global/launcher.js';
|
|
3
|
+
|
|
4
|
+
function runHelp() {
|
|
5
|
+
return [
|
|
6
|
+
'Usage: openkit run',
|
|
7
|
+
'',
|
|
8
|
+
'Run OpenCode with the globally installed OpenKit profile for the current project.',
|
|
9
|
+
].join('\n');
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export const runCommand = {
|
|
13
|
+
name: 'run',
|
|
14
|
+
async run(args = [], io) {
|
|
15
|
+
if (args.includes('--help') || args.includes('-h')) {
|
|
16
|
+
io.stdout.write(`${runHelp()}\n`);
|
|
17
|
+
return 0;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const ensured = ensureGlobalInstall({
|
|
21
|
+
projectRoot: process.cwd(),
|
|
22
|
+
env: process.env,
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
if (ensured.action === 'installed') {
|
|
26
|
+
io.stdout.write('OpenKit is not installed yet. Performing first-time setup...\n');
|
|
27
|
+
io.stdout.write(`Installed OpenKit globally.\n`);
|
|
28
|
+
io.stdout.write(`Kit root: ${ensured.install.kitRoot}\n`);
|
|
29
|
+
io.stdout.write(`Profile root: ${ensured.install.profilesRoot}\n`);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
if (ensured.action === 'blocked' || (!ensured.doctor.canRunCleanly && ensured.doctor.status !== 'workspace-ready-with-issues')) {
|
|
33
|
+
for (const issue of ensured.doctor.issues ?? []) {
|
|
34
|
+
io.stderr.write(`${issue}\n`);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
if (ensured.doctor.nextStep) {
|
|
38
|
+
io.stderr.write(`Next: ${ensured.doctor.nextStep}\n`);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
return 1;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
if (ensured.doctor.status === 'workspace-ready-with-issues') {
|
|
45
|
+
io.stderr.write('OpenKit setup is usable, but the workspace has issues.\n');
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const result = launchGlobalOpenKit(args, {
|
|
49
|
+
projectRoot: process.cwd(),
|
|
50
|
+
env: process.env,
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
if (result.stdout) {
|
|
54
|
+
io.stdout.write(result.stdout);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
if (result.stderr) {
|
|
58
|
+
io.stderr.write(result.stderr);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
return result.exitCode;
|
|
62
|
+
},
|
|
63
|
+
};
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { uninstallGlobalOpenKit } from '../../global/uninstall.js';
|
|
2
|
+
|
|
3
|
+
function uninstallHelp() {
|
|
4
|
+
return [
|
|
5
|
+
'Usage: openkit uninstall [--remove-workspaces]',
|
|
6
|
+
'',
|
|
7
|
+
'Remove the globally installed OpenKit kit and profile from the OpenCode home directory.',
|
|
8
|
+
].join('\n');
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export const uninstallCommand = {
|
|
12
|
+
name: 'uninstall',
|
|
13
|
+
async run(args = [], io) {
|
|
14
|
+
if (args.includes('--help') || args.includes('-h')) {
|
|
15
|
+
io.stdout.write(`${uninstallHelp()}\n`);
|
|
16
|
+
return 0;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const result = uninstallGlobalOpenKit({
|
|
20
|
+
env: process.env,
|
|
21
|
+
removeWorkspaces: args.includes('--remove-workspaces'),
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
io.stdout.write('Uninstalled OpenKit global kit.\n');
|
|
25
|
+
if (result.removedWorkspaces) {
|
|
26
|
+
io.stdout.write('Workspace state was removed.\n');
|
|
27
|
+
} else {
|
|
28
|
+
io.stdout.write('Workspace state was preserved.\n');
|
|
29
|
+
}
|
|
30
|
+
return 0;
|
|
31
|
+
},
|
|
32
|
+
};
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { materializeGlobalInstall } from '../../global/materialize.js';
|
|
2
|
+
|
|
3
|
+
function upgradeHelp() {
|
|
4
|
+
return [
|
|
5
|
+
'Usage: openkit upgrade',
|
|
6
|
+
'',
|
|
7
|
+
'Refresh the globally installed OpenKit kit in the OpenCode home directory.',
|
|
8
|
+
'Use this when the global install needs to be repaired or refreshed.',
|
|
9
|
+
].join('\n');
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export const upgradeCommand = {
|
|
13
|
+
name: 'upgrade',
|
|
14
|
+
async run(args = [], io) {
|
|
15
|
+
if (args.includes('--help') || args.includes('-h')) {
|
|
16
|
+
io.stdout.write(`${upgradeHelp()}\n`);
|
|
17
|
+
return 0;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const result = materializeGlobalInstall({ env: process.env });
|
|
21
|
+
io.stdout.write('Upgraded OpenKit global install.\n');
|
|
22
|
+
io.stdout.write(`Kit root: ${result.kitRoot}\n`);
|
|
23
|
+
return 0;
|
|
24
|
+
},
|
|
25
|
+
};
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
function formatConflict(conflict) {
|
|
2
|
+
const details = [conflict.path];
|
|
3
|
+
|
|
4
|
+
if (conflict.reason) {
|
|
5
|
+
details.push(`reason=${conflict.reason}`);
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
if (conflict.resolution) {
|
|
9
|
+
details.push(`resolution=${conflict.resolution}`);
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
return `- ${details.join(' | ')}`;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function writeConflictReport(io, commandName, conflicts) {
|
|
16
|
+
io.stderr.write(
|
|
17
|
+
`${commandName} aborted due to conflicts.\n${conflicts.map((conflict) => formatConflict(conflict)).join('\n')}\n`
|
|
18
|
+
);
|
|
19
|
+
}
|
package/src/cli/index.js
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { helpCommand } from './commands/help.js';
|
|
2
|
+
import { initCommand } from './commands/init.js';
|
|
3
|
+
import { installCommand } from './commands/install.js';
|
|
4
|
+
import { installGlobalCommand } from './commands/install-global.js';
|
|
5
|
+
import { runCommand } from './commands/run.js';
|
|
6
|
+
import { upgradeCommand } from './commands/upgrade.js';
|
|
7
|
+
import { uninstallCommand } from './commands/uninstall.js';
|
|
8
|
+
import { doctorCommand } from './commands/doctor.js';
|
|
9
|
+
import { detectVietnameseCommand } from './commands/detect-vietnamese.js';
|
|
10
|
+
|
|
11
|
+
const commands = {
|
|
12
|
+
help: helpCommand,
|
|
13
|
+
init: initCommand,
|
|
14
|
+
install: installCommand,
|
|
15
|
+
'install-global': installGlobalCommand,
|
|
16
|
+
run: runCommand,
|
|
17
|
+
upgrade: upgradeCommand,
|
|
18
|
+
uninstall: uninstallCommand,
|
|
19
|
+
doctor: doctorCommand,
|
|
20
|
+
'internal-audit-vietnamese': detectVietnameseCommand,
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
export async function runCli(argv, io = defaultIo()) {
|
|
24
|
+
const args = Array.isArray(argv) ? argv : [];
|
|
25
|
+
const [commandName, ...rest] = args;
|
|
26
|
+
|
|
27
|
+
if (!commandName || commandName === '--help' || commandName === '-h') {
|
|
28
|
+
return helpCommand.run([], io, { commands });
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
if (commandName === 'help') {
|
|
32
|
+
return helpCommand.run(rest, io, { commands });
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const command = commands[commandName];
|
|
36
|
+
|
|
37
|
+
if (!command) {
|
|
38
|
+
io.stderr.write(
|
|
39
|
+
`Unknown command: ${commandName}\nRun \`openkit --help\` to see available commands.\n`
|
|
40
|
+
);
|
|
41
|
+
return 1;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
if (rest.includes('--help') || rest.includes('-h')) {
|
|
45
|
+
return command.run(['--help'], io, { commands });
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
return command.run(rest, io, { commands });
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function defaultIo() {
|
|
52
|
+
return {
|
|
53
|
+
stdout: process.stdout,
|
|
54
|
+
stderr: process.stderr,
|
|
55
|
+
};
|
|
56
|
+
}
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
|
|
4
|
+
import { readJsonIfPresent, validateGlobalInstallState } from './install-state.js';
|
|
5
|
+
import { ensureWorkspaceBootstrap, readWorkspaceMeta } from './workspace-state.js';
|
|
6
|
+
import { getGlobalPaths, getWorkspacePaths } from './paths.js';
|
|
7
|
+
|
|
8
|
+
function isOpenCodeAvailable(env = process.env) {
|
|
9
|
+
const pathValue = env.PATH ?? '';
|
|
10
|
+
return pathValue.split(path.delimiter).some((segment) => segment && fs.existsSync(path.join(segment, 'opencode')));
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function withGuidance(result, nextStep, recommendedCommand = null) {
|
|
14
|
+
return {
|
|
15
|
+
...result,
|
|
16
|
+
nextStep,
|
|
17
|
+
recommendedCommand,
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function inspectGlobalDoctor({ projectRoot = process.cwd(), env = process.env } = {}) {
|
|
22
|
+
const globalPaths = getGlobalPaths({ env });
|
|
23
|
+
const workspacePaths = getWorkspacePaths({ projectRoot, env });
|
|
24
|
+
const globalInstallState = readJsonIfPresent(globalPaths.installStatePath);
|
|
25
|
+
const profileManifest = readJsonIfPresent(globalPaths.profileManifestPath);
|
|
26
|
+
|
|
27
|
+
const issues = [];
|
|
28
|
+
|
|
29
|
+
if (!globalInstallState) {
|
|
30
|
+
return withGuidance({
|
|
31
|
+
status: 'install-missing',
|
|
32
|
+
canRunCleanly: false,
|
|
33
|
+
globalPaths,
|
|
34
|
+
workspacePaths,
|
|
35
|
+
issues: ['Global OpenKit install was not found.'],
|
|
36
|
+
}, 'Run openkit run for first-time setup.', 'openkit run');
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const installStateErrors = validateGlobalInstallState(globalInstallState);
|
|
40
|
+
if (installStateErrors.length > 0) {
|
|
41
|
+
return withGuidance({
|
|
42
|
+
status: 'install-invalid',
|
|
43
|
+
canRunCleanly: false,
|
|
44
|
+
globalPaths,
|
|
45
|
+
workspacePaths,
|
|
46
|
+
issues: installStateErrors,
|
|
47
|
+
}, 'Run openkit upgrade to refresh the global install.', 'openkit upgrade');
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
if (!profileManifest) {
|
|
51
|
+
issues.push('OpenCode profile manifest for openkit is missing.');
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
if (!fs.existsSync(path.join(globalPaths.kitRoot, '.opencode', 'workflow-state.js'))) {
|
|
55
|
+
issues.push('Global workflow-state CLI is missing from the installed kit.');
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
if (!isOpenCodeAvailable(env)) {
|
|
59
|
+
issues.push('OpenCode executable is not available on PATH.');
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const workspace = readWorkspaceMeta({ projectRoot, env });
|
|
63
|
+
ensureWorkspaceBootstrap({ projectRoot, env });
|
|
64
|
+
|
|
65
|
+
return withGuidance({
|
|
66
|
+
status: issues.length === 0 ? 'healthy' : 'workspace-ready-with-issues',
|
|
67
|
+
canRunCleanly: issues.length === 0,
|
|
68
|
+
globalPaths,
|
|
69
|
+
workspacePaths,
|
|
70
|
+
workspace,
|
|
71
|
+
issues,
|
|
72
|
+
}, issues.length === 0 ? 'Run openkit run.' : 'Review the issues above before relying on this workspace.', issues.length === 0 ? 'openkit run' : null);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export function renderGlobalDoctorSummary(result) {
|
|
76
|
+
const lines = [
|
|
77
|
+
`Status: ${result.status}`,
|
|
78
|
+
`Global kit root: ${result.globalPaths.kitRoot}`,
|
|
79
|
+
`Workspace root: ${result.workspacePaths.workspaceRoot}`,
|
|
80
|
+
`Project root: ${result.workspacePaths.projectRoot}`,
|
|
81
|
+
`Workspace id: ${result.workspacePaths.workspaceId}`,
|
|
82
|
+
`Can run cleanly: ${result.canRunCleanly ? 'yes' : 'no'}`,
|
|
83
|
+
];
|
|
84
|
+
|
|
85
|
+
if (Array.isArray(result.issues) && result.issues.length > 0) {
|
|
86
|
+
lines.push('Issues:');
|
|
87
|
+
for (const issue of result.issues) {
|
|
88
|
+
lines.push(`- ${issue}`);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
if (result.nextStep) {
|
|
93
|
+
lines.push(`Next: ${result.nextStep}`);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
if (result.recommendedCommand) {
|
|
97
|
+
lines.push(`Recommended command: ${result.recommendedCommand}`);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
return `${lines.join('\n')}\n`;
|
|
101
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { inspectGlobalDoctor } from './doctor.js';
|
|
2
|
+
import { materializeGlobalInstall } from './materialize.js';
|
|
3
|
+
|
|
4
|
+
export function ensureGlobalInstall({ projectRoot = process.cwd(), env = process.env } = {}) {
|
|
5
|
+
const initialDoctor = inspectGlobalDoctor({ projectRoot, env });
|
|
6
|
+
|
|
7
|
+
if (initialDoctor.status === 'install-invalid') {
|
|
8
|
+
return {
|
|
9
|
+
action: 'blocked',
|
|
10
|
+
installed: false,
|
|
11
|
+
doctor: initialDoctor,
|
|
12
|
+
};
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
if (initialDoctor.status !== 'install-missing') {
|
|
16
|
+
return {
|
|
17
|
+
action: 'none',
|
|
18
|
+
installed: false,
|
|
19
|
+
doctor: initialDoctor,
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const install = materializeGlobalInstall({ env });
|
|
24
|
+
const doctor = inspectGlobalDoctor({ projectRoot, env });
|
|
25
|
+
|
|
26
|
+
return {
|
|
27
|
+
action: doctor.canRunCleanly || doctor.status === 'workspace-ready-with-issues' ? 'installed' : 'blocked',
|
|
28
|
+
installed: true,
|
|
29
|
+
install,
|
|
30
|
+
doctor,
|
|
31
|
+
};
|
|
32
|
+
}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
|
|
4
|
+
const GLOBAL_INSTALL_SCHEMA = 'openkit/global-install-state@1';
|
|
5
|
+
|
|
6
|
+
export function createGlobalInstallState({ kitVersion = '0.1.0', installedAt = new Date().toISOString(), profile = 'openkit' } = {}) {
|
|
7
|
+
return {
|
|
8
|
+
schema: GLOBAL_INSTALL_SCHEMA,
|
|
9
|
+
stateVersion: 1,
|
|
10
|
+
kit: {
|
|
11
|
+
name: 'OpenKit',
|
|
12
|
+
version: kitVersion,
|
|
13
|
+
},
|
|
14
|
+
installation: {
|
|
15
|
+
profile,
|
|
16
|
+
status: 'installed',
|
|
17
|
+
installedAt,
|
|
18
|
+
},
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function readJsonIfPresent(filePath) {
|
|
23
|
+
if (!fs.existsSync(filePath)) {
|
|
24
|
+
return null;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
return JSON.parse(fs.readFileSync(filePath, 'utf8'));
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export function writeJson(filePath, value) {
|
|
31
|
+
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
32
|
+
fs.writeFileSync(filePath, `${JSON.stringify(value, null, 2)}\n`, 'utf8');
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export function validateGlobalInstallState(state) {
|
|
36
|
+
const errors = [];
|
|
37
|
+
|
|
38
|
+
if (!state || typeof state !== 'object') {
|
|
39
|
+
return ['global install state must be an object'];
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
if (state.schema !== GLOBAL_INSTALL_SCHEMA) {
|
|
43
|
+
errors.push(`schema must be '${GLOBAL_INSTALL_SCHEMA}'`);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
if (state.stateVersion !== 1) {
|
|
47
|
+
errors.push('stateVersion must be 1');
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
if (!state.kit?.name || typeof state.kit.name !== 'string') {
|
|
51
|
+
errors.push('kit.name must be a non-empty string');
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
if (!state.kit?.version || typeof state.kit.version !== 'string') {
|
|
55
|
+
errors.push('kit.version must be a non-empty string');
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
if (!state.installation?.profile || typeof state.installation.profile !== 'string') {
|
|
59
|
+
errors.push('installation.profile must be a non-empty string');
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
if (state.installation?.status !== 'installed') {
|
|
63
|
+
errors.push("installation.status must be 'installed'");
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
if (!state.installation?.installedAt || typeof state.installation.installedAt !== 'string') {
|
|
67
|
+
errors.push('installation.installedAt must be an ISO timestamp string');
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
return errors;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export { GLOBAL_INSTALL_SCHEMA };
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { spawnSync } from 'node:child_process';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
|
|
4
|
+
import { ensureWorkspaceBootstrap } from './workspace-state.js';
|
|
5
|
+
|
|
6
|
+
function formatMissingOpenCodeError() {
|
|
7
|
+
return [
|
|
8
|
+
'Could not find `opencode` on your PATH.',
|
|
9
|
+
'Install OpenCode or add `opencode` to PATH, then retry `openkit run`.',
|
|
10
|
+
].join('\n');
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function launchGlobalOpenKit(args = [], { projectRoot = process.cwd(), env = process.env, spawn = spawnSync, stdio = 'inherit' } = {}) {
|
|
14
|
+
const paths = ensureWorkspaceBootstrap({ projectRoot, env });
|
|
15
|
+
const launcherEnv = {
|
|
16
|
+
...env,
|
|
17
|
+
OPENKIT_GLOBAL_MODE: '1',
|
|
18
|
+
OPENKIT_PROJECT_ROOT: paths.projectRoot,
|
|
19
|
+
OPENKIT_WORKFLOW_STATE: paths.workflowStatePath,
|
|
20
|
+
OPENKIT_KIT_ROOT: paths.kitRoot,
|
|
21
|
+
OPENKIT_HOME: paths.openCodeHome,
|
|
22
|
+
OPENCODE_CONFIG_DIR: paths.profilesRoot,
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
const result = spawn('opencode', ['--profile', 'openkit', ...args], {
|
|
26
|
+
cwd: paths.projectRoot,
|
|
27
|
+
env: launcherEnv,
|
|
28
|
+
encoding: 'utf8',
|
|
29
|
+
stdio,
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
if (result.error?.code === 'ENOENT') {
|
|
33
|
+
return {
|
|
34
|
+
exitCode: 1,
|
|
35
|
+
stdout: '',
|
|
36
|
+
stderr: `${formatMissingOpenCodeError()}\n`,
|
|
37
|
+
paths,
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
if (result.error) {
|
|
42
|
+
throw result.error;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
return {
|
|
46
|
+
exitCode: typeof result.status === 'number' ? result.status : 1,
|
|
47
|
+
stdout: result.stdout ?? '',
|
|
48
|
+
stderr: result.stderr ?? '',
|
|
49
|
+
paths,
|
|
50
|
+
};
|
|
51
|
+
}
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { fileURLToPath } from 'node:url';
|
|
4
|
+
|
|
5
|
+
import { createGlobalInstallState, writeJson } from './install-state.js';
|
|
6
|
+
import { getGlobalPaths } from './paths.js';
|
|
7
|
+
|
|
8
|
+
const MODULE_DIR = path.dirname(fileURLToPath(import.meta.url));
|
|
9
|
+
const PACKAGE_ROOT = path.resolve(MODULE_DIR, '../..');
|
|
10
|
+
|
|
11
|
+
const GLOBAL_KIT_ASSETS = [
|
|
12
|
+
'.opencode',
|
|
13
|
+
'agents',
|
|
14
|
+
'skills',
|
|
15
|
+
'commands',
|
|
16
|
+
'context',
|
|
17
|
+
'hooks',
|
|
18
|
+
'docs',
|
|
19
|
+
'registry.json',
|
|
20
|
+
'AGENTS.md',
|
|
21
|
+
'README.md',
|
|
22
|
+
];
|
|
23
|
+
|
|
24
|
+
function removePathIfPresent(targetPath) {
|
|
25
|
+
if (!fs.existsSync(targetPath)) {
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
fs.rmSync(targetPath, { recursive: true, force: true });
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function copyAsset(sourcePath, targetPath) {
|
|
32
|
+
fs.mkdirSync(path.dirname(targetPath), { recursive: true });
|
|
33
|
+
const stats = fs.statSync(sourcePath);
|
|
34
|
+
|
|
35
|
+
if (stats.isDirectory()) {
|
|
36
|
+
fs.cpSync(sourcePath, targetPath, { recursive: true });
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
fs.copyFileSync(sourcePath, targetPath);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function listManagedFiles(kitRoot) {
|
|
44
|
+
const files = [];
|
|
45
|
+
|
|
46
|
+
function walk(currentPath) {
|
|
47
|
+
for (const entry of fs.readdirSync(currentPath, { withFileTypes: true })) {
|
|
48
|
+
const entryPath = path.join(currentPath, entry.name);
|
|
49
|
+
if (entry.isDirectory()) {
|
|
50
|
+
walk(entryPath);
|
|
51
|
+
} else {
|
|
52
|
+
files.push(path.relative(kitRoot, entryPath));
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
walk(kitRoot);
|
|
58
|
+
return files.sort();
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function createProfileManifest(kitRoot, profileHooksPath) {
|
|
62
|
+
const kitManifestPath = path.join(kitRoot, '.opencode', 'opencode.json');
|
|
63
|
+
const kitManifest = JSON.parse(fs.readFileSync(kitManifestPath, 'utf8'));
|
|
64
|
+
|
|
65
|
+
return {
|
|
66
|
+
model: kitManifest.model,
|
|
67
|
+
agents_dir: path.join(kitRoot, 'agents'),
|
|
68
|
+
skills_dir: path.join(kitRoot, 'skills'),
|
|
69
|
+
commands_dir: path.join(kitRoot, 'commands'),
|
|
70
|
+
hooks: {
|
|
71
|
+
config: profileHooksPath,
|
|
72
|
+
},
|
|
73
|
+
openkit: {
|
|
74
|
+
profile: 'openkit',
|
|
75
|
+
kit_root: kitRoot,
|
|
76
|
+
manifest: kitManifestPath,
|
|
77
|
+
},
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export function materializeGlobalInstall({ env = process.env, kitVersion = '0.1.0' } = {}) {
|
|
82
|
+
const paths = getGlobalPaths({ env });
|
|
83
|
+
|
|
84
|
+
removePathIfPresent(paths.kitRoot);
|
|
85
|
+
removePathIfPresent(paths.profilesRoot);
|
|
86
|
+
|
|
87
|
+
fs.mkdirSync(paths.kitRoot, { recursive: true });
|
|
88
|
+
fs.mkdirSync(paths.profilesRoot, { recursive: true });
|
|
89
|
+
|
|
90
|
+
for (const relativeAsset of GLOBAL_KIT_ASSETS) {
|
|
91
|
+
copyAsset(path.join(PACKAGE_ROOT, relativeAsset), path.join(paths.kitRoot, relativeAsset));
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
const installState = createGlobalInstallState({ kitVersion, profile: 'openkit' });
|
|
95
|
+
writeJson(paths.installStatePath, installState);
|
|
96
|
+
writeJson(paths.profileManifestPath, createProfileManifest(paths.kitRoot, paths.profileHooksPath));
|
|
97
|
+
writeJson(paths.profileHooksPath, {
|
|
98
|
+
hooks: {
|
|
99
|
+
SessionStart: [
|
|
100
|
+
{
|
|
101
|
+
matcher: 'startup|clear|compact',
|
|
102
|
+
hooks: [
|
|
103
|
+
{
|
|
104
|
+
type: 'command',
|
|
105
|
+
command: `${path.join(paths.kitRoot, 'hooks', 'session-start')}`,
|
|
106
|
+
async: false,
|
|
107
|
+
},
|
|
108
|
+
],
|
|
109
|
+
},
|
|
110
|
+
],
|
|
111
|
+
},
|
|
112
|
+
});
|
|
113
|
+
writeJson(paths.managedFilesPath, {
|
|
114
|
+
schema: 'openkit/managed-files@1',
|
|
115
|
+
generatedAt: new Date().toISOString(),
|
|
116
|
+
files: listManagedFiles(paths.kitRoot),
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
return {
|
|
120
|
+
...paths,
|
|
121
|
+
installState,
|
|
122
|
+
};
|
|
123
|
+
}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import crypto from 'node:crypto';
|
|
2
|
+
import fs from 'node:fs';
|
|
3
|
+
import os from 'node:os';
|
|
4
|
+
import path from 'node:path';
|
|
5
|
+
|
|
6
|
+
function normalizeWorkspaceSeed(projectRoot, platform = process.platform) {
|
|
7
|
+
const resolved = path.resolve(projectRoot);
|
|
8
|
+
return platform === 'win32' ? resolved.toLowerCase() : resolved;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function getOpenCodeHome({ env = process.env, platform = process.platform, homedir = os.homedir() } = {}) {
|
|
12
|
+
if (env.OPENCODE_HOME) {
|
|
13
|
+
return path.resolve(env.OPENCODE_HOME);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
if (platform === 'win32') {
|
|
17
|
+
const base = env.APPDATA || path.join(homedir, 'AppData', 'Roaming');
|
|
18
|
+
return path.join(base, 'opencode');
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const base = env.XDG_CONFIG_HOME ? path.resolve(env.XDG_CONFIG_HOME) : path.join(homedir, '.config');
|
|
22
|
+
return path.join(base, 'opencode');
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export function detectProjectRoot(startDir = process.cwd()) {
|
|
26
|
+
let current = path.resolve(startDir);
|
|
27
|
+
|
|
28
|
+
while (true) {
|
|
29
|
+
if (fs.existsSync(path.join(current, '.git')) || fs.existsSync(path.join(current, 'package.json'))) {
|
|
30
|
+
return current;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const parent = path.dirname(current);
|
|
34
|
+
if (parent === current) {
|
|
35
|
+
return path.resolve(startDir);
|
|
36
|
+
}
|
|
37
|
+
current = parent;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export function createWorkspaceId(projectRoot, { platform = process.platform } = {}) {
|
|
42
|
+
const seed = normalizeWorkspaceSeed(projectRoot, platform);
|
|
43
|
+
return crypto.createHash('sha256').update(seed).digest('hex').slice(0, 16);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export function getGlobalPaths(options = {}) {
|
|
47
|
+
const openCodeHome = getOpenCodeHome(options);
|
|
48
|
+
const kitRoot = path.join(openCodeHome, 'kits', 'openkit');
|
|
49
|
+
const profilesRoot = path.join(openCodeHome, 'profiles', 'openkit');
|
|
50
|
+
const workspacesRoot = path.join(openCodeHome, 'workspaces');
|
|
51
|
+
|
|
52
|
+
return {
|
|
53
|
+
openCodeHome,
|
|
54
|
+
kitRoot,
|
|
55
|
+
installStatePath: path.join(kitRoot, 'install-state.json'),
|
|
56
|
+
managedFilesPath: path.join(kitRoot, 'managed-files.json'),
|
|
57
|
+
profilesRoot,
|
|
58
|
+
profileManifestPath: path.join(profilesRoot, 'opencode.json'),
|
|
59
|
+
profileHooksPath: path.join(profilesRoot, 'hooks.json'),
|
|
60
|
+
workspacesRoot,
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export function getWorkspacePaths({ projectRoot, env = process.env, platform = process.platform, homedir = os.homedir() } = {}) {
|
|
65
|
+
const resolvedProjectRoot = detectProjectRoot(projectRoot ?? process.cwd());
|
|
66
|
+
const globalPaths = getGlobalPaths({ env, platform, homedir });
|
|
67
|
+
const workspaceId = createWorkspaceId(resolvedProjectRoot, { platform });
|
|
68
|
+
const workspaceRoot = path.join(globalPaths.workspacesRoot, workspaceId, 'openkit');
|
|
69
|
+
const opencodeDir = path.join(workspaceRoot, '.opencode');
|
|
70
|
+
|
|
71
|
+
return {
|
|
72
|
+
...globalPaths,
|
|
73
|
+
projectRoot: resolvedProjectRoot,
|
|
74
|
+
workspaceId,
|
|
75
|
+
workspaceRoot,
|
|
76
|
+
workspaceMetaPath: path.join(workspaceRoot, 'workspace.json'),
|
|
77
|
+
opencodeDir,
|
|
78
|
+
workflowStatePath: path.join(opencodeDir, 'workflow-state.json'),
|
|
79
|
+
workItemsDir: path.join(opencodeDir, 'work-items'),
|
|
80
|
+
workItemIndexPath: path.join(opencodeDir, 'work-items', 'index.json'),
|
|
81
|
+
legacyRuntimeDir: path.join(resolvedProjectRoot, '.opencode'),
|
|
82
|
+
legacyWorkflowStatePath: path.join(resolvedProjectRoot, '.opencode', 'workflow-state.json'),
|
|
83
|
+
legacyWorkItemsDir: path.join(resolvedProjectRoot, '.opencode', 'work-items'),
|
|
84
|
+
};
|
|
85
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
|
|
3
|
+
import { getGlobalPaths } from './paths.js';
|
|
4
|
+
|
|
5
|
+
function removeIfPresent(targetPath) {
|
|
6
|
+
if (fs.existsSync(targetPath)) {
|
|
7
|
+
fs.rmSync(targetPath, { recursive: true, force: true });
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function uninstallGlobalOpenKit({ env = process.env, removeWorkspaces = false } = {}) {
|
|
12
|
+
const paths = getGlobalPaths({ env });
|
|
13
|
+
|
|
14
|
+
removeIfPresent(paths.kitRoot);
|
|
15
|
+
removeIfPresent(paths.profilesRoot);
|
|
16
|
+
|
|
17
|
+
if (removeWorkspaces) {
|
|
18
|
+
removeIfPresent(paths.workspacesRoot);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
return {
|
|
22
|
+
...paths,
|
|
23
|
+
removedWorkspaces: removeWorkspaces,
|
|
24
|
+
};
|
|
25
|
+
}
|