@zcy2nn/agent-forge 1.0.6 → 1.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/dist/cli/custom-skills.d.ts +1 -12
- package/dist/cli/index.js +25 -96
- package/dist/cli/types.d.ts +0 -1
- package/dist/hooks/filter-available-skills/index.d.ts +13 -4
- package/dist/hooks/index.d.ts +1 -0
- package/dist/hooks/session-bootstrap/index.d.ts +32 -0
- package/dist/index.js +174 -69
- package/package.json +1 -1
- package/src/skills/brainstorming/SKILL.md +12 -3
- package/src/skills/systematic-debugging/SKILL.md +14 -4
- package/src/skills/test-driven-development/SKILL.md +14 -5
- package/src/skills/verification-before-completion/SKILL.md +10 -4
- package/dist/cli/migration.d.ts +0 -46
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* A custom skill bundled in this repository.
|
|
3
|
-
*
|
|
3
|
+
* Built-in skills are read directly from the package at runtime (no file copy needed).
|
|
4
4
|
*/
|
|
5
5
|
export interface CustomSkill {
|
|
6
6
|
/** Skill name (folder name) */
|
|
@@ -16,14 +16,3 @@ export interface CustomSkill {
|
|
|
16
16
|
* Registry of custom skills bundled in this repository.
|
|
17
17
|
*/
|
|
18
18
|
export declare const CUSTOM_SKILLS: CustomSkill[];
|
|
19
|
-
/**
|
|
20
|
-
* Get the target directory for custom skills installation.
|
|
21
|
-
*/
|
|
22
|
-
export declare function getCustomSkillsDir(): string;
|
|
23
|
-
/**
|
|
24
|
-
* Install a custom skill by copying from src/skills/ to the OpenCode skills directory
|
|
25
|
-
* @param skill - The custom skill to install
|
|
26
|
-
* @param projectRoot - Root directory of agent-forge project
|
|
27
|
-
* @returns True if installation succeeded, false otherwise
|
|
28
|
-
*/
|
|
29
|
-
export declare function installCustomSkill(skill: CustomSkill): boolean;
|
package/dist/cli/index.js
CHANGED
|
@@ -13,14 +13,14 @@ import * as path from "node:path";
|
|
|
13
13
|
|
|
14
14
|
// src/cli/config-io.ts
|
|
15
15
|
import {
|
|
16
|
-
copyFileSync
|
|
17
|
-
existsSync as
|
|
16
|
+
copyFileSync,
|
|
17
|
+
existsSync as existsSync2,
|
|
18
18
|
readFileSync,
|
|
19
19
|
renameSync,
|
|
20
|
-
statSync
|
|
20
|
+
statSync,
|
|
21
21
|
writeFileSync
|
|
22
22
|
} from "node:fs";
|
|
23
|
-
import { dirname as
|
|
23
|
+
import { dirname as dirname2, join as join2 } from "node:path";
|
|
24
24
|
|
|
25
25
|
// src/cli/paths.ts
|
|
26
26
|
import { existsSync, mkdirSync } from "node:fs";
|
|
@@ -390,15 +390,6 @@ var DEFAULT_AGENT_MCPS = {
|
|
|
390
390
|
};
|
|
391
391
|
|
|
392
392
|
// src/cli/custom-skills.ts
|
|
393
|
-
import {
|
|
394
|
-
copyFileSync,
|
|
395
|
-
existsSync as existsSync2,
|
|
396
|
-
mkdirSync as mkdirSync2,
|
|
397
|
-
readdirSync,
|
|
398
|
-
statSync
|
|
399
|
-
} from "node:fs";
|
|
400
|
-
import { dirname as dirname2, join as join2 } from "node:path";
|
|
401
|
-
import { fileURLToPath } from "node:url";
|
|
402
393
|
var CUSTOM_SKILLS = [
|
|
403
394
|
{
|
|
404
395
|
name: "simplify",
|
|
@@ -491,45 +482,6 @@ var CUSTOM_SKILLS = [
|
|
|
491
482
|
sourcePath: "src/skills/writing-skills"
|
|
492
483
|
}
|
|
493
484
|
];
|
|
494
|
-
function getCustomSkillsDir() {
|
|
495
|
-
return join2(getConfigDir(), "skills");
|
|
496
|
-
}
|
|
497
|
-
function copyDirRecursive(src, dest) {
|
|
498
|
-
if (!existsSync2(dest)) {
|
|
499
|
-
mkdirSync2(dest, { recursive: true });
|
|
500
|
-
}
|
|
501
|
-
const entries = readdirSync(src);
|
|
502
|
-
for (const entry of entries) {
|
|
503
|
-
const srcPath = join2(src, entry);
|
|
504
|
-
const destPath = join2(dest, entry);
|
|
505
|
-
const stat = statSync(srcPath);
|
|
506
|
-
if (stat.isDirectory()) {
|
|
507
|
-
copyDirRecursive(srcPath, destPath);
|
|
508
|
-
} else {
|
|
509
|
-
const destDir = dirname2(destPath);
|
|
510
|
-
if (!existsSync2(destDir)) {
|
|
511
|
-
mkdirSync2(destDir, { recursive: true });
|
|
512
|
-
}
|
|
513
|
-
copyFileSync(srcPath, destPath);
|
|
514
|
-
}
|
|
515
|
-
}
|
|
516
|
-
}
|
|
517
|
-
function installCustomSkill(skill) {
|
|
518
|
-
try {
|
|
519
|
-
const packageRoot = fileURLToPath(new URL("../..", import.meta.url));
|
|
520
|
-
const sourcePath = join2(packageRoot, skill.sourcePath);
|
|
521
|
-
const targetPath = join2(getCustomSkillsDir(), skill.name);
|
|
522
|
-
if (!existsSync2(sourcePath)) {
|
|
523
|
-
console.error(`Custom skill source not found: ${sourcePath}`);
|
|
524
|
-
return false;
|
|
525
|
-
}
|
|
526
|
-
copyDirRecursive(sourcePath, targetPath);
|
|
527
|
-
return true;
|
|
528
|
-
} catch (error) {
|
|
529
|
-
console.error(`Failed to install custom skill: ${skill.name}`, error);
|
|
530
|
-
return false;
|
|
531
|
-
}
|
|
532
|
-
}
|
|
533
485
|
|
|
534
486
|
// src/cli/skills.ts
|
|
535
487
|
import { spawnSync } from "node:child_process";
|
|
@@ -642,7 +594,7 @@ function generateLiteConfig(installConfig) {
|
|
|
642
594
|
...RECOMMENDED_SKILLS.filter((s) => s.allowedAgents.includes("*") || s.allowedAgents.includes(agentName)).map((s) => s.skillName),
|
|
643
595
|
...CUSTOM_SKILLS.filter((s) => s.allowedAgents.includes("*") || s.allowedAgents.includes(agentName)).map((s) => s.name)
|
|
644
596
|
];
|
|
645
|
-
if (agentName === "implementer" && !skills.includes("agent-browser")) {
|
|
597
|
+
if (installConfig.installSkills && agentName === "implementer" && !skills.includes("agent-browser")) {
|
|
646
598
|
skills.push("agent-browser");
|
|
647
599
|
}
|
|
648
600
|
return {
|
|
@@ -696,10 +648,10 @@ function normalizePathForMatch(path) {
|
|
|
696
648
|
return path.replaceAll("\\", "/");
|
|
697
649
|
}
|
|
698
650
|
function findPackageRoot(startPath) {
|
|
699
|
-
let currentPath =
|
|
651
|
+
let currentPath = dirname2(startPath);
|
|
700
652
|
while (true) {
|
|
701
|
-
const packageJsonPath =
|
|
702
|
-
if (
|
|
653
|
+
const packageJsonPath = join2(currentPath, "package.json");
|
|
654
|
+
if (existsSync2(packageJsonPath)) {
|
|
703
655
|
try {
|
|
704
656
|
const packageJson = JSON.parse(readFileSync(packageJsonPath, "utf-8"));
|
|
705
657
|
if (packageJson.name === PACKAGE_NAME) {
|
|
@@ -707,7 +659,7 @@ function findPackageRoot(startPath) {
|
|
|
707
659
|
}
|
|
708
660
|
} catch {}
|
|
709
661
|
}
|
|
710
|
-
const parentPath =
|
|
662
|
+
const parentPath = dirname2(currentPath);
|
|
711
663
|
if (parentPath === currentPath) {
|
|
712
664
|
return null;
|
|
713
665
|
}
|
|
@@ -722,8 +674,8 @@ function isLocalPackageRootEntry(entry) {
|
|
|
722
674
|
if (!entry || entry.startsWith("file://")) {
|
|
723
675
|
return false;
|
|
724
676
|
}
|
|
725
|
-
const packageJsonPath =
|
|
726
|
-
if (!
|
|
677
|
+
const packageJsonPath = join2(entry, "package.json");
|
|
678
|
+
if (!existsSync2(packageJsonPath)) {
|
|
727
679
|
return false;
|
|
728
680
|
}
|
|
729
681
|
try {
|
|
@@ -762,9 +714,9 @@ function stripJsonComments(json) {
|
|
|
762
714
|
}
|
|
763
715
|
function parseConfigFile(path) {
|
|
764
716
|
try {
|
|
765
|
-
if (!
|
|
717
|
+
if (!existsSync2(path))
|
|
766
718
|
return { config: null };
|
|
767
|
-
const stat =
|
|
719
|
+
const stat = statSync(path);
|
|
768
720
|
if (stat.size === 0)
|
|
769
721
|
return { config: null };
|
|
770
722
|
const content = readFileSync(path, "utf-8");
|
|
@@ -793,8 +745,8 @@ function writeConfig(configPath, config) {
|
|
|
793
745
|
const bakPath = `${configPath}.bak`;
|
|
794
746
|
const content = `${JSON.stringify(config, null, 2)}
|
|
795
747
|
`;
|
|
796
|
-
if (
|
|
797
|
-
|
|
748
|
+
if (existsSync2(configPath)) {
|
|
749
|
+
copyFileSync(configPath, bakPath);
|
|
798
750
|
}
|
|
799
751
|
writeFileSync(tmpPath, content);
|
|
800
752
|
renameSync(tmpPath, configPath);
|
|
@@ -880,8 +832,8 @@ function writeLiteConfig(installConfig, targetPath) {
|
|
|
880
832
|
const bakPath = `${configPath}.bak`;
|
|
881
833
|
const content = `${JSON.stringify(config, null, 2)}
|
|
882
834
|
`;
|
|
883
|
-
if (
|
|
884
|
-
|
|
835
|
+
if (existsSync2(configPath)) {
|
|
836
|
+
copyFileSync(configPath, bakPath);
|
|
885
837
|
}
|
|
886
838
|
writeFileSync(tmpPath, content);
|
|
887
839
|
renameSync(tmpPath, configPath);
|
|
@@ -1256,11 +1208,11 @@ Options:
|
|
|
1256
1208
|
}
|
|
1257
1209
|
|
|
1258
1210
|
// src/cli/install.ts
|
|
1259
|
-
import { existsSync as
|
|
1211
|
+
import { existsSync as existsSync4 } from "node:fs";
|
|
1260
1212
|
import { createInterface } from "node:readline/promises";
|
|
1261
1213
|
// src/cli/system.ts
|
|
1262
1214
|
import { spawnSync as spawnSync2 } from "node:child_process";
|
|
1263
|
-
import { statSync as
|
|
1215
|
+
import { statSync as statSync3 } from "node:fs";
|
|
1264
1216
|
|
|
1265
1217
|
// src/utils/compat.ts
|
|
1266
1218
|
import { spawn as nodeSpawn } from "node:child_process";
|
|
@@ -1381,7 +1333,7 @@ function resolveOpenCodePath() {
|
|
|
1381
1333
|
if (opencodePath === "opencode")
|
|
1382
1334
|
continue;
|
|
1383
1335
|
try {
|
|
1384
|
-
const stat =
|
|
1336
|
+
const stat = statSync3(opencodePath);
|
|
1385
1337
|
if (stat.isFile()) {
|
|
1386
1338
|
cachedOpenCodePath = opencodePath;
|
|
1387
1339
|
return opencodePath;
|
|
@@ -1532,8 +1484,6 @@ async function runInstall(config) {
|
|
|
1532
1484
|
let totalSteps = 6;
|
|
1533
1485
|
if (config.installSkills)
|
|
1534
1486
|
totalSteps += 1;
|
|
1535
|
-
if (config.installCustomSkills)
|
|
1536
|
-
totalSteps += 1;
|
|
1537
1487
|
let step = 1;
|
|
1538
1488
|
printStep(step++, totalSteps, "Checking OpenCode installation...");
|
|
1539
1489
|
if (config.dryRun) {
|
|
@@ -1587,7 +1537,7 @@ ${JSON.stringify(liteConfig, null, 2)}
|
|
|
1587
1537
|
`);
|
|
1588
1538
|
} else {
|
|
1589
1539
|
const configPath2 = getExistingLiteConfigPath();
|
|
1590
|
-
const configExists =
|
|
1540
|
+
const configExists = existsSync4(configPath2);
|
|
1591
1541
|
if (configExists && !config.reset) {
|
|
1592
1542
|
printInfo(`Configuration already exists at ${configPath2}. Use --reset to overwrite.`);
|
|
1593
1543
|
} else {
|
|
@@ -1616,28 +1566,8 @@ ${JSON.stringify(liteConfig, null, 2)}
|
|
|
1616
1566
|
}
|
|
1617
1567
|
printSuccess(`${skillsInstalled}/${RECOMMENDED_SKILLS.length} skills processed`);
|
|
1618
1568
|
}
|
|
1619
|
-
}
|
|
1620
|
-
|
|
1621
|
-
printStep(step++, totalSteps, "Installing custom skills...");
|
|
1622
|
-
if (config.dryRun) {
|
|
1623
|
-
printInfo("Dry run mode - would install custom skills:");
|
|
1624
|
-
for (const skill of CUSTOM_SKILLS) {
|
|
1625
|
-
printInfo(` - ${skill.name}`);
|
|
1626
|
-
}
|
|
1627
|
-
} else {
|
|
1628
|
-
let customSkillsInstalled = 0;
|
|
1629
|
-
for (const skill of CUSTOM_SKILLS) {
|
|
1630
|
-
printInfo(`Installing ${skill.name}...`);
|
|
1631
|
-
if (installCustomSkill(skill)) {
|
|
1632
|
-
printSuccess(`Installed: ${skill.name}`);
|
|
1633
|
-
customSkillsInstalled++;
|
|
1634
|
-
} else {
|
|
1635
|
-
printInfo(`Skipped: ${skill.name} (already installed)`);
|
|
1636
|
-
}
|
|
1637
|
-
}
|
|
1638
|
-
const totalCustom = CUSTOM_SKILLS.length;
|
|
1639
|
-
printSuccess(`${customSkillsInstalled}/${totalCustom} custom skills processed`);
|
|
1640
|
-
}
|
|
1569
|
+
} else {
|
|
1570
|
+
printInfo("Recommended skills (e.g. agent-browser) not installed. Use --skills=yes to install them.");
|
|
1641
1571
|
}
|
|
1642
1572
|
const statusMsg = isUpdate ? "Configuration updated!" : "Installation complete!";
|
|
1643
1573
|
console.log(`${SYMBOLS.star} ${BOLD}${GREEN}${statusMsg}${RESET}`);
|
|
@@ -1674,7 +1604,6 @@ async function install(args) {
|
|
|
1674
1604
|
const config = {
|
|
1675
1605
|
hasTmux: false,
|
|
1676
1606
|
installSkills: args.skills === "yes",
|
|
1677
|
-
installCustomSkills: args.skills === "yes",
|
|
1678
1607
|
preset: args.preset,
|
|
1679
1608
|
promptForStar: args.tui,
|
|
1680
1609
|
dryRun: args.dryRun,
|
|
@@ -1696,7 +1625,7 @@ function getGeneratedPresetNames2() {
|
|
|
1696
1625
|
function parseArgs(args) {
|
|
1697
1626
|
const result = {
|
|
1698
1627
|
tui: true,
|
|
1699
|
-
skills: "
|
|
1628
|
+
skills: "no"
|
|
1700
1629
|
};
|
|
1701
1630
|
for (const arg of args) {
|
|
1702
1631
|
if (arg === "--no-tui") {
|
|
@@ -1730,7 +1659,7 @@ Usage:
|
|
|
1730
1659
|
bunx @zcy2nn/agent-forge doctor [OPTIONS]
|
|
1731
1660
|
|
|
1732
1661
|
Options:
|
|
1733
|
-
--skills=yes|no Install recommended
|
|
1662
|
+
--skills=yes|no Install recommended skills like agent-browser (default: no)
|
|
1734
1663
|
--preset=<name> Active generated config preset (default: openai)
|
|
1735
1664
|
--no-tui Non-interactive mode
|
|
1736
1665
|
--dry-run Simulate install without writing files
|
package/dist/cli/types.d.ts
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Filter available_skills block based on the current agent's permission.skill rules.
|
|
3
|
-
* OpenCode core injects `<available_skills>`
|
|
4
|
-
* block before the prompt is sent.
|
|
2
|
+
* Filter and inject available_skills block based on the current agent's permission.skill rules.
|
|
3
|
+
* OpenCode core injects `<available_skills>` from installed skills, so this hook rewrites that
|
|
4
|
+
* block before the prompt is sent. Built-in skills from the package are also injected so they
|
|
5
|
+
* are always visible even without being copied to the OpenCode skills directory.
|
|
5
6
|
*/
|
|
6
7
|
import type { PluginInput } from '@opencode-ai/plugin';
|
|
7
8
|
import { type PluginConfig } from '../../config';
|
|
@@ -19,6 +20,14 @@ interface MessageWithParts {
|
|
|
19
20
|
parts: MessagePart[];
|
|
20
21
|
}
|
|
21
22
|
type SkillRule = 'allow' | 'ask' | 'deny';
|
|
23
|
+
interface SkillEntry {
|
|
24
|
+
name: string;
|
|
25
|
+
block: string;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Merge installed skill entries with built-in entries (dedup by name, installed takes precedence).
|
|
29
|
+
*/
|
|
30
|
+
declare function mergeWithBuiltinSkills(installedEntries: SkillEntry[]): SkillEntry[];
|
|
22
31
|
declare function filterAvailableSkillsText(text: string, permissionRules: Record<string, SkillRule>): string;
|
|
23
32
|
/**
|
|
24
33
|
* Creates the experimental.chat.messages.transform hook for filtering available skills.
|
|
@@ -29,4 +38,4 @@ export declare function createFilterAvailableSkillsHook(_ctx: PluginInput, confi
|
|
|
29
38
|
messages: MessageWithParts[];
|
|
30
39
|
}) => Promise<void>;
|
|
31
40
|
};
|
|
32
|
-
export { filterAvailableSkillsText };
|
|
41
|
+
export { filterAvailableSkillsText, mergeWithBuiltinSkills };
|
package/dist/hooks/index.d.ts
CHANGED
|
@@ -9,5 +9,6 @@ export { processImageAttachments } from './image-hook';
|
|
|
9
9
|
export { createJsonErrorRecoveryHook } from './json-error-recovery';
|
|
10
10
|
export { createPhaseReminderHook } from './phase-reminder';
|
|
11
11
|
export { createPostFileToolNudgeHook } from './post-file-tool-nudge';
|
|
12
|
+
export { createSessionBootstrapHook } from './session-bootstrap';
|
|
12
13
|
export { createTaskSessionManagerHook } from './task-session-manager';
|
|
13
14
|
export { createTodoContinuationHook } from './todo-continuation';
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Session bootstrap hook — injects lightweight assessment framework
|
|
3
|
+
* into the first user message of each session.
|
|
4
|
+
*
|
|
5
|
+
* This gives the orchestrator a "work manual front page" so it assesses
|
|
6
|
+
* task complexity before jumping into implementation.
|
|
7
|
+
* Only injected for the orchestrator agent, only once per session.
|
|
8
|
+
*/
|
|
9
|
+
interface MessageInfo {
|
|
10
|
+
role: string;
|
|
11
|
+
agent?: string;
|
|
12
|
+
sessionID?: string;
|
|
13
|
+
}
|
|
14
|
+
interface MessagePart {
|
|
15
|
+
type: string;
|
|
16
|
+
text?: string;
|
|
17
|
+
[key: string]: unknown;
|
|
18
|
+
}
|
|
19
|
+
interface MessageWithParts {
|
|
20
|
+
info: MessageInfo;
|
|
21
|
+
parts: MessagePart[];
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Creates the experimental.chat.messages.transform hook for session bootstrap.
|
|
25
|
+
* Injects the assessment framework into the first user message, orchestrator only.
|
|
26
|
+
*/
|
|
27
|
+
export declare function createSessionBootstrapHook(): {
|
|
28
|
+
'experimental.chat.messages.transform': (_input: Record<string, never>, output: {
|
|
29
|
+
messages: MessageWithParts[];
|
|
30
|
+
}) => Promise<void>;
|
|
31
|
+
};
|
|
32
|
+
export {};
|
package/dist/index.js
CHANGED
|
@@ -18150,14 +18150,14 @@ var require_turndown_cjs = __commonJS((exports, module) => {
|
|
|
18150
18150
|
} else if (node.nodeType === 1) {
|
|
18151
18151
|
replacement = replacementForNode.call(self, node);
|
|
18152
18152
|
}
|
|
18153
|
-
return
|
|
18153
|
+
return join14(output, replacement);
|
|
18154
18154
|
}, "");
|
|
18155
18155
|
}
|
|
18156
18156
|
function postProcess(output) {
|
|
18157
18157
|
var self = this;
|
|
18158
18158
|
this.rules.forEach(function(rule) {
|
|
18159
18159
|
if (typeof rule.append === "function") {
|
|
18160
|
-
output =
|
|
18160
|
+
output = join14(output, rule.append(self.options));
|
|
18161
18161
|
}
|
|
18162
18162
|
});
|
|
18163
18163
|
return output.replace(/^[\t\r\n]+/, "").replace(/[\t\r\n\s]+$/, "");
|
|
@@ -18170,7 +18170,7 @@ var require_turndown_cjs = __commonJS((exports, module) => {
|
|
|
18170
18170
|
content = content.trim();
|
|
18171
18171
|
return whitespace.leading + rule.replacement(content, node, this.options) + whitespace.trailing;
|
|
18172
18172
|
}
|
|
18173
|
-
function
|
|
18173
|
+
function join14(output, replacement) {
|
|
18174
18174
|
var s1 = trimTrailingNewlines(output);
|
|
18175
18175
|
var s2 = trimLeadingNewlines(replacement);
|
|
18176
18176
|
var nls = Math.max(output.length - s1.length, replacement.length - s2.length);
|
|
@@ -18185,38 +18185,6 @@ var require_turndown_cjs = __commonJS((exports, module) => {
|
|
|
18185
18185
|
module.exports = TurndownService;
|
|
18186
18186
|
});
|
|
18187
18187
|
|
|
18188
|
-
// src/cli/custom-skills.ts
|
|
18189
|
-
import { dirname as dirname2, join as join2 } from "node:path";
|
|
18190
|
-
|
|
18191
|
-
// src/cli/paths.ts
|
|
18192
|
-
import { homedir } from "node:os";
|
|
18193
|
-
import { dirname, join } from "node:path";
|
|
18194
|
-
function getDefaultOpenCodeConfigDir() {
|
|
18195
|
-
const userConfigDir = process.env.XDG_CONFIG_HOME ? process.env.XDG_CONFIG_HOME : join(homedir(), ".config");
|
|
18196
|
-
return join(userConfigDir, "opencode");
|
|
18197
|
-
}
|
|
18198
|
-
function getCustomOpenCodeConfigDir() {
|
|
18199
|
-
const configDir = process.env.OPENCODE_CONFIG_DIR?.trim();
|
|
18200
|
-
return configDir || undefined;
|
|
18201
|
-
}
|
|
18202
|
-
function getConfigDir() {
|
|
18203
|
-
const customConfigDir = getCustomOpenCodeConfigDir();
|
|
18204
|
-
if (customConfigDir) {
|
|
18205
|
-
return customConfigDir;
|
|
18206
|
-
}
|
|
18207
|
-
return getDefaultOpenCodeConfigDir();
|
|
18208
|
-
}
|
|
18209
|
-
function getConfigSearchDirs() {
|
|
18210
|
-
const dirs = [getCustomOpenCodeConfigDir(), getDefaultOpenCodeConfigDir()];
|
|
18211
|
-
return dirs.filter((dir, index) => {
|
|
18212
|
-
return Boolean(dir) && dirs.indexOf(dir) === index;
|
|
18213
|
-
});
|
|
18214
|
-
}
|
|
18215
|
-
function getOpenCodeConfigPaths() {
|
|
18216
|
-
const configDir = getDefaultOpenCodeConfigDir();
|
|
18217
|
-
return [join(configDir, "opencode.json"), join(configDir, "opencode.jsonc")];
|
|
18218
|
-
}
|
|
18219
|
-
|
|
18220
18188
|
// src/cli/custom-skills.ts
|
|
18221
18189
|
var CUSTOM_SKILLS = [
|
|
18222
18190
|
{
|
|
@@ -18310,9 +18278,6 @@ var CUSTOM_SKILLS = [
|
|
|
18310
18278
|
sourcePath: "src/skills/writing-skills"
|
|
18311
18279
|
}
|
|
18312
18280
|
];
|
|
18313
|
-
function getCustomSkillsDir() {
|
|
18314
|
-
return join2(getConfigDir(), "skills");
|
|
18315
|
-
}
|
|
18316
18281
|
|
|
18317
18282
|
// src/cli/skills.ts
|
|
18318
18283
|
var RECOMMENDED_SKILLS = [
|
|
@@ -18474,6 +18439,28 @@ var CouncilConfigSchema = z.object({
|
|
|
18474
18439
|
import * as fs from "node:fs";
|
|
18475
18440
|
import * as path from "node:path";
|
|
18476
18441
|
|
|
18442
|
+
// src/cli/paths.ts
|
|
18443
|
+
import { homedir } from "node:os";
|
|
18444
|
+
import { dirname, join } from "node:path";
|
|
18445
|
+
function getDefaultOpenCodeConfigDir() {
|
|
18446
|
+
const userConfigDir = process.env.XDG_CONFIG_HOME ? process.env.XDG_CONFIG_HOME : join(homedir(), ".config");
|
|
18447
|
+
return join(userConfigDir, "opencode");
|
|
18448
|
+
}
|
|
18449
|
+
function getCustomOpenCodeConfigDir() {
|
|
18450
|
+
const configDir = process.env.OPENCODE_CONFIG_DIR?.trim();
|
|
18451
|
+
return configDir || undefined;
|
|
18452
|
+
}
|
|
18453
|
+
function getConfigSearchDirs() {
|
|
18454
|
+
const dirs = [getCustomOpenCodeConfigDir(), getDefaultOpenCodeConfigDir()];
|
|
18455
|
+
return dirs.filter((dir, index) => {
|
|
18456
|
+
return Boolean(dir) && dirs.indexOf(dir) === index;
|
|
18457
|
+
});
|
|
18458
|
+
}
|
|
18459
|
+
function getOpenCodeConfigPaths() {
|
|
18460
|
+
const configDir = getDefaultOpenCodeConfigDir();
|
|
18461
|
+
return [join(configDir, "opencode.json"), join(configDir, "opencode.jsonc")];
|
|
18462
|
+
}
|
|
18463
|
+
|
|
18477
18464
|
// src/config/agent-mcps.ts
|
|
18478
18465
|
var DEFAULT_AGENT_MCPS = {
|
|
18479
18466
|
orchestrator: ["*", "!context7"],
|
|
@@ -22810,6 +22797,19 @@ ${buildRetryGuidance(detected)}`;
|
|
|
22810
22797
|
// src/hooks/filter-available-skills/index.ts
|
|
22811
22798
|
var AVAILABLE_SKILLS_BLOCK_REGEX = /<available_skills>\s*([\s\S]*?)\s*<\/available_skills>/g;
|
|
22812
22799
|
var SKILL_NAME_REGEX = /<name>([^<]+)<\/name>/;
|
|
22800
|
+
function builtinSkillBlock(name, description) {
|
|
22801
|
+
return `<skill>
|
|
22802
|
+
<name>${name}</name>
|
|
22803
|
+
<description>${description}</description>
|
|
22804
|
+
<location>builtin</location>
|
|
22805
|
+
</skill>`;
|
|
22806
|
+
}
|
|
22807
|
+
function getBuiltinSkillEntries() {
|
|
22808
|
+
return CUSTOM_SKILLS.map((skill) => ({
|
|
22809
|
+
name: skill.name,
|
|
22810
|
+
block: builtinSkillBlock(skill.name, skill.description)
|
|
22811
|
+
}));
|
|
22812
|
+
}
|
|
22813
22813
|
function getCurrentAgent(messages) {
|
|
22814
22814
|
for (let index = messages.length - 1;index >= 0; index -= 1) {
|
|
22815
22815
|
const message = messages[index];
|
|
@@ -22842,9 +22842,21 @@ function isSkillAllowed(skillName, permissionRules) {
|
|
|
22842
22842
|
}
|
|
22843
22843
|
return permissionRules["*"] === "allow";
|
|
22844
22844
|
}
|
|
22845
|
+
function mergeWithBuiltinSkills(installedEntries) {
|
|
22846
|
+
const builtinEntries = getBuiltinSkillEntries();
|
|
22847
|
+
const installedNames = new Set(installedEntries.map((e) => e.name));
|
|
22848
|
+
for (const entry of builtinEntries) {
|
|
22849
|
+
if (!installedNames.has(entry.name)) {
|
|
22850
|
+
installedEntries.push(entry);
|
|
22851
|
+
}
|
|
22852
|
+
}
|
|
22853
|
+
return installedEntries;
|
|
22854
|
+
}
|
|
22845
22855
|
function filterAvailableSkillsText(text, permissionRules) {
|
|
22846
22856
|
return text.replace(AVAILABLE_SKILLS_BLOCK_REGEX, (_fullMatch, blockContent) => {
|
|
22847
|
-
|
|
22857
|
+
let allEntries = extractSkillEntries(blockContent);
|
|
22858
|
+
allEntries = mergeWithBuiltinSkills(allEntries);
|
|
22859
|
+
const allowedEntries = allEntries.filter((entry) => isSkillAllowed(entry.name, permissionRules));
|
|
22848
22860
|
if (allowedEntries.length === 0) {
|
|
22849
22861
|
return `<available_skills>
|
|
22850
22862
|
No skills available.
|
|
@@ -22856,6 +22868,22 @@ ${allowedEntries.map((entry) => entry.block).join(`
|
|
|
22856
22868
|
</available_skills>`;
|
|
22857
22869
|
});
|
|
22858
22870
|
}
|
|
22871
|
+
function injectBuiltinSkillsIfMissing(text, permissionRules) {
|
|
22872
|
+
if (text.includes("<available_skills>")) {
|
|
22873
|
+
return text;
|
|
22874
|
+
}
|
|
22875
|
+
const builtinEntries = getBuiltinSkillEntries();
|
|
22876
|
+
const allowedEntries = builtinEntries.filter((entry) => isSkillAllowed(entry.name, permissionRules));
|
|
22877
|
+
if (allowedEntries.length === 0) {
|
|
22878
|
+
return text;
|
|
22879
|
+
}
|
|
22880
|
+
const block = `<available_skills>
|
|
22881
|
+
${allowedEntries.map((entry) => entry.block).join(`
|
|
22882
|
+
`)}
|
|
22883
|
+
</available_skills>`;
|
|
22884
|
+
return `${text}
|
|
22885
|
+
${block}`;
|
|
22886
|
+
}
|
|
22859
22887
|
function createFilterAvailableSkillsHook(_ctx, config) {
|
|
22860
22888
|
const permissionRulesByAgent = new Map;
|
|
22861
22889
|
const getPermissionRules = (agentName) => {
|
|
@@ -22878,10 +22906,14 @@ function createFilterAvailableSkillsHook(_ctx, config) {
|
|
|
22878
22906
|
const permissionRules = getPermissionRules(agentName);
|
|
22879
22907
|
for (const message of messages) {
|
|
22880
22908
|
for (const part of message.parts) {
|
|
22881
|
-
if (part.type !== "text" || !part.text
|
|
22909
|
+
if (part.type !== "text" || !part.text) {
|
|
22882
22910
|
continue;
|
|
22883
22911
|
}
|
|
22884
|
-
|
|
22912
|
+
if (part.text.includes("<available_skills>")) {
|
|
22913
|
+
part.text = filterAvailableSkillsText(part.text, permissionRules);
|
|
22914
|
+
} else {
|
|
22915
|
+
part.text = injectBuiltinSkillsIfMissing(part.text, permissionRules);
|
|
22916
|
+
}
|
|
22885
22917
|
}
|
|
22886
22918
|
}
|
|
22887
22919
|
}
|
|
@@ -23123,7 +23155,7 @@ import {
|
|
|
23123
23155
|
unlinkSync as unlinkSync2,
|
|
23124
23156
|
writeFileSync as writeFileSync3
|
|
23125
23157
|
} from "node:fs";
|
|
23126
|
-
import { basename as basename2, extname, join as
|
|
23158
|
+
import { basename as basename2, extname, join as join7 } from "node:path";
|
|
23127
23159
|
var lastCleanupByDir = new Map;
|
|
23128
23160
|
var CLEANUP_INTERVAL = 10 * 60 * 1000;
|
|
23129
23161
|
function isImagePart(p) {
|
|
@@ -23171,7 +23203,7 @@ function cleanupAllSessions(saveDir) {
|
|
|
23171
23203
|
const dirsToScan = [];
|
|
23172
23204
|
try {
|
|
23173
23205
|
for (const entry of readdirSync2(saveDir, { withFileTypes: true })) {
|
|
23174
|
-
const fp =
|
|
23206
|
+
const fp = join7(saveDir, entry.name);
|
|
23175
23207
|
if (entry.isDirectory()) {
|
|
23176
23208
|
dirsToScan.push(fp);
|
|
23177
23209
|
} else {
|
|
@@ -23188,7 +23220,7 @@ function cleanupAllSessions(saveDir) {
|
|
|
23188
23220
|
let allRemoved = true;
|
|
23189
23221
|
for (const f of readdirSync2(dir)) {
|
|
23190
23222
|
isEmpty = false;
|
|
23191
|
-
const fp =
|
|
23223
|
+
const fp = join7(dir, f);
|
|
23192
23224
|
try {
|
|
23193
23225
|
if (now - statSync3(fp).mtimeMs > maxAge) {
|
|
23194
23226
|
unlinkSync2(fp);
|
|
@@ -23210,7 +23242,7 @@ function cleanupAllSessions(saveDir) {
|
|
|
23210
23242
|
function writeUniqueFile(dir, name, data, log2) {
|
|
23211
23243
|
const ext = extname(name);
|
|
23212
23244
|
const base = basename2(name, ext) || name;
|
|
23213
|
-
let candidate =
|
|
23245
|
+
let candidate = join7(dir, name);
|
|
23214
23246
|
if (existsSync5(candidate)) {
|
|
23215
23247
|
return candidate;
|
|
23216
23248
|
}
|
|
@@ -23223,7 +23255,7 @@ function writeUniqueFile(dir, name, data, log2) {
|
|
|
23223
23255
|
} catch (e) {
|
|
23224
23256
|
if (e instanceof Error && e.code === "EEXIST") {
|
|
23225
23257
|
counter += 1;
|
|
23226
|
-
candidate =
|
|
23258
|
+
candidate = join7(dir, `${base}-${counter}${ext}`);
|
|
23227
23259
|
continue;
|
|
23228
23260
|
}
|
|
23229
23261
|
log2(`[image-hook] failed to save image: ${e}`);
|
|
@@ -23247,13 +23279,13 @@ function processImageAttachments(args) {
|
|
|
23247
23279
|
messagesWithImages.push({ msg, imageParts });
|
|
23248
23280
|
}
|
|
23249
23281
|
}
|
|
23250
|
-
const saveDir =
|
|
23282
|
+
const saveDir = join7(workDir, ".opencode", "images");
|
|
23251
23283
|
if (messagesWithImages.length === 0) {
|
|
23252
23284
|
if (existsSync5(saveDir))
|
|
23253
23285
|
cleanupAllSessions(saveDir);
|
|
23254
23286
|
return;
|
|
23255
23287
|
}
|
|
23256
|
-
const gitignorePath =
|
|
23288
|
+
const gitignorePath = join7(workDir, ".opencode", ".gitignore");
|
|
23257
23289
|
try {
|
|
23258
23290
|
mkdirSync3(saveDir, { recursive: true });
|
|
23259
23291
|
if (!existsSync5(gitignorePath))
|
|
@@ -23265,7 +23297,7 @@ function processImageAttachments(args) {
|
|
|
23265
23297
|
cleanupAllSessions(saveDir);
|
|
23266
23298
|
for (const { msg, imageParts } of messagesWithImages) {
|
|
23267
23299
|
const sessionSubdir = msg.info.sessionID ? sanitizeFilename(msg.info.sessionID) : undefined;
|
|
23268
|
-
const targetDir = sessionSubdir ?
|
|
23300
|
+
const targetDir = sessionSubdir ? join7(saveDir, sessionSubdir) : saveDir;
|
|
23269
23301
|
try {
|
|
23270
23302
|
mkdirSync3(targetDir, { recursive: true });
|
|
23271
23303
|
} catch (e) {
|
|
@@ -23425,6 +23457,80 @@ function createPostFileToolNudgeHook(options = {}) {
|
|
|
23425
23457
|
}
|
|
23426
23458
|
};
|
|
23427
23459
|
}
|
|
23460
|
+
// src/hooks/session-bootstrap/index.ts
|
|
23461
|
+
var BOOTSTRAP_MARKER = "<session_bootstrap>";
|
|
23462
|
+
var BOOTSTRAP_CONTENT = `${BOOTSTRAP_MARKER}
|
|
23463
|
+
## Instruction Priority
|
|
23464
|
+
1. User's explicit instructions (CLAUDE.md, AGENTS.md, direct requests) — highest
|
|
23465
|
+
2. Superpowers skills — override default system behavior where they conflict
|
|
23466
|
+
3. Default system prompt — lowest
|
|
23467
|
+
|
|
23468
|
+
## Subagents
|
|
23469
|
+
Subagents dispatched to execute a specific task skip this flow.
|
|
23470
|
+
|
|
23471
|
+
## Task Complexity Assessment
|
|
23472
|
+
Assess task complexity before responding:
|
|
23473
|
+
**Simple (direct answer):** Single step, knowledge question, small edit, config change, debug single error
|
|
23474
|
+
**Medium or Complex (proposal flow):** Multi-file changes, design decisions, new features, cross-module, architecture changes
|
|
23475
|
+
|
|
23476
|
+
## Proposal Flow
|
|
23477
|
+
Present three tiers:
|
|
23478
|
+
Suggested approach:
|
|
23479
|
+
── Lightweight ── [Phase]
|
|
23480
|
+
── Recommended ── [Phase] → [Phase] → [Phase] → [Phase]
|
|
23481
|
+
── Full ── [Phase (strategy)] → [Phase] → [Phase (strategy)] → [Phase + Review]
|
|
23482
|
+
Pick one, or tell me custom steps.
|
|
23483
|
+
|
|
23484
|
+
## Phase & Skill Mapping
|
|
23485
|
+
[Design] = brainstorming
|
|
23486
|
+
[Plan] = writing-plans
|
|
23487
|
+
[Implement] = test-driven-development / subagent-driven-development / dispatching-parallel-agents
|
|
23488
|
+
[Verify] = verification-before-completion
|
|
23489
|
+
[Review] = requesting-code-review
|
|
23490
|
+
Encounter bug → systematic-debugging (available at any phase)
|
|
23491
|
+
|
|
23492
|
+
## Proposal Guidance
|
|
23493
|
+
- Don't over-simplify: if the task involves multi-file changes or design decisions, don't suggest "implement directly"
|
|
23494
|
+
- Don't over-complicate: simple questions don't need the proposal flow
|
|
23495
|
+
- Lightweight must include [Verify]: even a single-phase approach must run verification commands. Lightweight means fewer phases, not skipping discipline.
|
|
23496
|
+
- Be specific: describe what will be done clearly, avoid vague terms
|
|
23497
|
+
|
|
23498
|
+
## Skill Types
|
|
23499
|
+
**Rigid** (TDD, debugging): Follow the spirit at all tiers. Simple tasks may use abbreviated forms (test-after, quick fix with verification), but the discipline of verify-before-claim always applies.
|
|
23500
|
+
**Flexible** (brainstorming, writing-plans): Adapt principles to context. Simple tasks may skip these entirely.
|
|
23501
|
+
|
|
23502
|
+
## Skill Access
|
|
23503
|
+
Use the skill tool to list and load skills on demand.
|
|
23504
|
+
${BOOTSTRAP_MARKER}`;
|
|
23505
|
+
function createSessionBootstrapHook() {
|
|
23506
|
+
return {
|
|
23507
|
+
"experimental.chat.messages.transform": async (_input, output) => {
|
|
23508
|
+
const { messages } = output;
|
|
23509
|
+
if (messages.length === 0) {
|
|
23510
|
+
return;
|
|
23511
|
+
}
|
|
23512
|
+
const firstUserMessage = messages.find((m) => m.info.role === "user");
|
|
23513
|
+
if (!firstUserMessage) {
|
|
23514
|
+
return;
|
|
23515
|
+
}
|
|
23516
|
+
const agent = firstUserMessage.info.agent;
|
|
23517
|
+
if (agent && agent !== "orchestrator") {
|
|
23518
|
+
return;
|
|
23519
|
+
}
|
|
23520
|
+
const textPartIndex = firstUserMessage.parts.findIndex((p) => p.type === "text" && typeof p.text === "string");
|
|
23521
|
+
if (textPartIndex === -1) {
|
|
23522
|
+
return;
|
|
23523
|
+
}
|
|
23524
|
+
const originalText = firstUserMessage.parts[textPartIndex].text ?? "";
|
|
23525
|
+
if (originalText.includes(BOOTSTRAP_MARKER)) {
|
|
23526
|
+
return;
|
|
23527
|
+
}
|
|
23528
|
+
firstUserMessage.parts[textPartIndex].text = `${BOOTSTRAP_CONTENT}
|
|
23529
|
+
|
|
23530
|
+
${originalText}`;
|
|
23531
|
+
}
|
|
23532
|
+
};
|
|
23533
|
+
}
|
|
23428
23534
|
// src/hooks/task-session-manager/index.ts
|
|
23429
23535
|
import path9 from "node:path";
|
|
23430
23536
|
var AGENT_NAME_SET = new Set([
|
|
@@ -29162,13 +29268,13 @@ import { existsSync as existsSync9 } from "node:fs";
|
|
|
29162
29268
|
// src/tools/ast-grep/constants.ts
|
|
29163
29269
|
import { existsSync as existsSync8, statSync as statSync4 } from "node:fs";
|
|
29164
29270
|
import { createRequire as createRequire3 } from "node:module";
|
|
29165
|
-
import { dirname as
|
|
29271
|
+
import { dirname as dirname6, join as join11 } from "node:path";
|
|
29166
29272
|
|
|
29167
29273
|
// src/tools/ast-grep/downloader.ts
|
|
29168
29274
|
import { chmodSync, existsSync as existsSync7, mkdirSync as mkdirSync5, unlinkSync as unlinkSync4 } from "node:fs";
|
|
29169
29275
|
import { createRequire as createRequire2 } from "node:module";
|
|
29170
29276
|
import { homedir as homedir5 } from "node:os";
|
|
29171
|
-
import { join as
|
|
29277
|
+
import { join as join10 } from "node:path";
|
|
29172
29278
|
var REPO = "ast-grep/ast-grep";
|
|
29173
29279
|
var DEFAULT_VERSION = "0.40.0";
|
|
29174
29280
|
function getAstGrepVersion() {
|
|
@@ -29192,18 +29298,18 @@ var PLATFORM_MAP = {
|
|
|
29192
29298
|
function getCacheDir2() {
|
|
29193
29299
|
if (process.platform === "win32") {
|
|
29194
29300
|
const localAppData = process.env.LOCALAPPDATA || process.env.APPDATA;
|
|
29195
|
-
const base2 = localAppData ||
|
|
29196
|
-
return
|
|
29301
|
+
const base2 = localAppData || join10(homedir5(), "AppData", "Local");
|
|
29302
|
+
return join10(base2, "agent-forge", "bin");
|
|
29197
29303
|
}
|
|
29198
29304
|
const xdgCache = process.env.XDG_CACHE_HOME;
|
|
29199
|
-
const base = xdgCache ||
|
|
29200
|
-
return
|
|
29305
|
+
const base = xdgCache || join10(homedir5(), ".cache");
|
|
29306
|
+
return join10(base, "agent-forge", "bin");
|
|
29201
29307
|
}
|
|
29202
29308
|
function getBinaryName() {
|
|
29203
29309
|
return process.platform === "win32" ? "sg.exe" : "sg";
|
|
29204
29310
|
}
|
|
29205
29311
|
function getCachedBinaryPath() {
|
|
29206
|
-
const binaryPath =
|
|
29312
|
+
const binaryPath = join10(getCacheDir2(), getBinaryName());
|
|
29207
29313
|
return existsSync7(binaryPath) ? binaryPath : null;
|
|
29208
29314
|
}
|
|
29209
29315
|
async function downloadAstGrep(version = DEFAULT_VERSION) {
|
|
@@ -29215,7 +29321,7 @@ async function downloadAstGrep(version = DEFAULT_VERSION) {
|
|
|
29215
29321
|
}
|
|
29216
29322
|
const cacheDir = getCacheDir2();
|
|
29217
29323
|
const binaryName = getBinaryName();
|
|
29218
|
-
const binaryPath =
|
|
29324
|
+
const binaryPath = join10(cacheDir, binaryName);
|
|
29219
29325
|
if (existsSync7(binaryPath)) {
|
|
29220
29326
|
return binaryPath;
|
|
29221
29327
|
}
|
|
@@ -29231,7 +29337,7 @@ async function downloadAstGrep(version = DEFAULT_VERSION) {
|
|
|
29231
29337
|
if (!response.ok) {
|
|
29232
29338
|
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
|
29233
29339
|
}
|
|
29234
|
-
const archivePath =
|
|
29340
|
+
const archivePath = join10(cacheDir, assetName);
|
|
29235
29341
|
const arrayBuffer = await response.arrayBuffer();
|
|
29236
29342
|
await crossWrite(archivePath, arrayBuffer);
|
|
29237
29343
|
await extractZip(archivePath, cacheDir);
|
|
@@ -29319,8 +29425,8 @@ function findSgCliPathSync() {
|
|
|
29319
29425
|
try {
|
|
29320
29426
|
const require2 = createRequire3(import.meta.url);
|
|
29321
29427
|
const cliPkgPath = require2.resolve("@ast-grep/cli/package.json");
|
|
29322
|
-
const cliDir =
|
|
29323
|
-
const sgPath =
|
|
29428
|
+
const cliDir = dirname6(cliPkgPath);
|
|
29429
|
+
const sgPath = join11(cliDir, binaryName);
|
|
29324
29430
|
if (existsSync8(sgPath) && isValidBinary(sgPath)) {
|
|
29325
29431
|
return sgPath;
|
|
29326
29432
|
}
|
|
@@ -29330,9 +29436,9 @@ function findSgCliPathSync() {
|
|
|
29330
29436
|
try {
|
|
29331
29437
|
const require2 = createRequire3(import.meta.url);
|
|
29332
29438
|
const pkgPath = require2.resolve(`${platformPkg}/package.json`);
|
|
29333
|
-
const pkgDir =
|
|
29439
|
+
const pkgDir = dirname6(pkgPath);
|
|
29334
29440
|
const astGrepName = process.platform === "win32" ? "ast-grep.exe" : "ast-grep";
|
|
29335
|
-
const binaryPath =
|
|
29441
|
+
const binaryPath = join11(pkgDir, astGrepName);
|
|
29336
29442
|
if (existsSync8(binaryPath) && isValidBinary(binaryPath)) {
|
|
29337
29443
|
return binaryPath;
|
|
29338
29444
|
}
|
|
@@ -29999,16 +30105,12 @@ Usage: /preset <name> to switch.`);
|
|
|
29999
30105
|
}
|
|
30000
30106
|
// src/tools/skill.ts
|
|
30001
30107
|
import { existsSync as existsSync10, readFileSync as readFileSync5 } from "node:fs";
|
|
30002
|
-
import { join as
|
|
30108
|
+
import { join as join13 } from "node:path";
|
|
30003
30109
|
import { tool as tool4 } from "@opencode-ai/plugin/tool";
|
|
30004
30110
|
var z5 = tool4.schema;
|
|
30005
30111
|
function resolveSkillPath(skillName) {
|
|
30006
|
-
const
|
|
30007
|
-
|
|
30008
|
-
return installedPath;
|
|
30009
|
-
}
|
|
30010
|
-
const packageRoot = join14(import.meta.dirname, "..", "..");
|
|
30011
|
-
const bundledPath = join14(packageRoot, "src", "skills", skillName, "SKILL.md");
|
|
30112
|
+
const packageRoot = join13(import.meta.dirname, "..", "..");
|
|
30113
|
+
const bundledPath = join13(packageRoot, "src", "skills", skillName, "SKILL.md");
|
|
30012
30114
|
if (existsSync10(bundledPath)) {
|
|
30013
30115
|
return bundledPath;
|
|
30014
30116
|
}
|
|
@@ -32455,6 +32557,7 @@ var OhMyOpenCodeLite = async (ctx) => {
|
|
|
32455
32557
|
let multiplexerSessionManager;
|
|
32456
32558
|
let autoUpdateChecker;
|
|
32457
32559
|
let phaseReminderHook;
|
|
32560
|
+
let sessionBootstrapHook;
|
|
32458
32561
|
let filterAvailableSkillsHook;
|
|
32459
32562
|
let sessionAgentMap;
|
|
32460
32563
|
let postFileToolNudgeHook;
|
|
@@ -32540,6 +32643,7 @@ var OhMyOpenCodeLite = async (ctx) => {
|
|
|
32540
32643
|
autoUpdate: config.autoUpdate ?? true
|
|
32541
32644
|
});
|
|
32542
32645
|
phaseReminderHook = createPhaseReminderHook();
|
|
32646
|
+
sessionBootstrapHook = createSessionBootstrapHook();
|
|
32543
32647
|
filterAvailableSkillsHook = createFilterAvailableSkillsHook(ctx, config);
|
|
32544
32648
|
sessionAgentMap = new Map;
|
|
32545
32649
|
postFileToolNudgeHook = createPostFileToolNudgeHook({
|
|
@@ -32922,6 +33026,7 @@ ${output.system[0]}` : "");
|
|
|
32922
33026
|
},
|
|
32923
33027
|
"experimental.chat.messages.transform": async (input, output) => {
|
|
32924
33028
|
const typedOutput = output;
|
|
33029
|
+
await sessionBootstrapHook["experimental.chat.messages.transform"](input, typedOutput);
|
|
32925
33030
|
for (const message of typedOutput.messages) {
|
|
32926
33031
|
if (message.info.role !== "user") {
|
|
32927
33032
|
continue;
|
package/package.json
CHANGED
|
@@ -10,12 +10,20 @@ Help turn ideas into fully formed designs and specs through natural collaborativ
|
|
|
10
10
|
Start by understanding the current project context, then ask questions one at a time to refine the idea. Once you understand what you're building, present the design and get user approval.
|
|
11
11
|
|
|
12
12
|
<HARD-GATE>
|
|
13
|
-
Do NOT invoke any implementation skill, write any code, scaffold any project, or take any implementation action until you have presented a design and the user has approved it.
|
|
13
|
+
For Medium/Complex tasks: Do NOT invoke any implementation skill, write any code, scaffold any project, or take any implementation action until you have presented a design and the user has approved it.
|
|
14
|
+
|
|
15
|
+
For Simple tasks: You may proceed directly if the task is a knowledge question, small config change, or single-file edit with no design decisions. If uncertain whether a design is needed, err on the side of presenting a Lightweight design (2-3 sentences) and getting oral confirmation.
|
|
14
16
|
</HARD-GATE>
|
|
15
17
|
|
|
16
|
-
##
|
|
18
|
+
## Complexity Adaptation
|
|
19
|
+
|
|
20
|
+
This skill adapts to the complexity tier determined by session-bootstrap:
|
|
21
|
+
|
|
22
|
+
- **Simple tasks** (direct answer tier): Skip this skill entirely unless the task involves design decisions. If a Simple task does need design, use Lightweight mode.
|
|
23
|
+
- **Medium tasks**: Use Lightweight or Standard mode.
|
|
24
|
+
- **Complex tasks**: Use Standard or Deep mode. The HARD-GATE above applies in full.
|
|
17
25
|
|
|
18
|
-
|
|
26
|
+
Don't skip design when it matters — "simple" tasks with unexamined assumptions cause the most wasted work. But don't force ceremony when it genuinely doesn't add value.
|
|
19
27
|
|
|
20
28
|
## Checklist
|
|
21
29
|
|
|
@@ -150,6 +158,7 @@ Wait for the user's response. If they request changes, make them and re-run the
|
|
|
150
158
|
- Skip context exploration, skip one-by-one questions
|
|
151
159
|
- Give 2-3 options directly for user to pick
|
|
152
160
|
- No spec document — oral confirmation is enough
|
|
161
|
+
- Applies when session-bootstrap classifies the task as Simple and design is still warranted
|
|
153
162
|
|
|
154
163
|
**Standard usage:**
|
|
155
164
|
- Full flow: explore context → one-by-one questions → compare approaches → present design → write spec
|
|
@@ -16,10 +16,13 @@ Random fixes waste time and create new bugs. Quick patches mask underlying issue
|
|
|
16
16
|
## The Iron Law
|
|
17
17
|
|
|
18
18
|
```
|
|
19
|
-
NO FIXES WITHOUT ROOT CAUSE INVESTIGATION FIRST
|
|
19
|
+
For Medium/Complex bugs: NO FIXES WITHOUT ROOT CAUSE INVESTIGATION FIRST
|
|
20
|
+
For Simple bugs: Quick fix is acceptable if the cause is obvious, but verify the fix works
|
|
20
21
|
```
|
|
21
22
|
|
|
22
|
-
If you haven't completed Phase 1, you cannot propose fixes.
|
|
23
|
+
If you haven't completed Phase 1 for a Medium/Complex bug, you cannot propose fixes.
|
|
24
|
+
|
|
25
|
+
For Simple bugs (single error, obvious symptom, config typo, known pattern): you may fix directly, but must verify the fix resolves the issue. If the quick fix doesn't work, escalate to full investigation immediately.
|
|
23
26
|
|
|
24
27
|
## When to Use
|
|
25
28
|
|
|
@@ -39,9 +42,14 @@ Use for ANY technical issue:
|
|
|
39
42
|
- You don't fully understand the issue
|
|
40
43
|
|
|
41
44
|
**Don't skip when:**
|
|
42
|
-
- Issue seems simple (simple bugs have root causes too)
|
|
43
45
|
- You're in a hurry (rushing guarantees rework)
|
|
44
46
|
- Manager wants it fixed NOW (systematic is faster than thrashing)
|
|
47
|
+
- Previous fix didn't work (symptom fixing masks root cause)
|
|
48
|
+
|
|
49
|
+
**When abbreviated investigation is acceptable:**
|
|
50
|
+
- session-bootstrap classifies the task as Simple
|
|
51
|
+
- The cause is obvious from the error message (e.g., config typo, missing import)
|
|
52
|
+
- If the quick fix doesn't work, immediately escalate to full investigation
|
|
45
53
|
|
|
46
54
|
## The Four Phases
|
|
47
55
|
|
|
@@ -290,8 +298,10 @@ These techniques are part of systematic debugging and available in this director
|
|
|
290
298
|
## Complexity Assessment
|
|
291
299
|
|
|
292
300
|
**Lightweight usage:**
|
|
293
|
-
- Reproduce → read error → fix directly
|
|
301
|
+
- Reproduce → read error → fix directly (only when cause is obvious)
|
|
294
302
|
- Skip hypothesis verification and root cause analysis
|
|
303
|
+
- Applies when session-bootstrap classifies the task as Simple
|
|
304
|
+
- If quick fix doesn't work, escalate to Standard immediately
|
|
295
305
|
|
|
296
306
|
**Standard usage:**
|
|
297
307
|
- Full flow: reproduce → gather evidence → hypothesize → verify → fix → regression test
|
|
@@ -15,34 +15,43 @@ Write the test first. Watch it fail. Write minimal code to pass.
|
|
|
15
15
|
|
|
16
16
|
## When to Use
|
|
17
17
|
|
|
18
|
-
**Always:**
|
|
18
|
+
**Always (for Medium/Complex tasks):**
|
|
19
19
|
- New features
|
|
20
20
|
- Bug fixes
|
|
21
21
|
- Refactoring
|
|
22
22
|
- Behavior changes
|
|
23
23
|
|
|
24
|
+
**Simple tasks (test-after acceptable):**
|
|
25
|
+
- Config changes
|
|
26
|
+
- Typo fixes
|
|
27
|
+
- Single-line corrections
|
|
28
|
+
- Obvious corrections with no logic change
|
|
29
|
+
|
|
24
30
|
**Exceptions (ask your human partner):**
|
|
25
31
|
- Throwaway prototypes
|
|
26
32
|
- Generated code
|
|
27
33
|
- Configuration files
|
|
34
|
+
- Simple edits classified by session-bootstrap (config changes, typos, single-line fixes)
|
|
28
35
|
|
|
29
36
|
Thinking "skip TDD just this once"? Stop. That's rationalization.
|
|
30
37
|
|
|
31
38
|
## The Iron Law
|
|
32
39
|
|
|
33
40
|
```
|
|
34
|
-
NO PRODUCTION CODE WITHOUT A FAILING TEST FIRST
|
|
41
|
+
For Medium/Complex tasks: NO PRODUCTION CODE WITHOUT A FAILING TEST FIRST
|
|
42
|
+
For Simple tasks: Test-after is acceptable, but verification is still required
|
|
35
43
|
```
|
|
36
44
|
|
|
37
|
-
Write code before the test? Delete it. Start over.
|
|
45
|
+
**Medium/Complex tasks:** Write code before the test? Delete it. Start over.
|
|
38
46
|
|
|
39
|
-
**No exceptions:**
|
|
40
47
|
- Don't keep it as "reference"
|
|
41
48
|
- Don't "adapt" it while writing tests
|
|
42
49
|
- Don't look at it
|
|
43
50
|
- Delete means delete
|
|
44
51
|
|
|
45
|
-
|
|
52
|
+
**Simple tasks** (as classified by session-bootstrap): If the change is a config change, typo fix, single-line correction, or similar trivial edit — implement first, then write a test or run verification. If the change affects behavior or logic, TDD still applies.
|
|
53
|
+
|
|
54
|
+
Implement fresh from tests. Period (for Medium/Complex).
|
|
46
55
|
|
|
47
56
|
## Red-Green-Refactor
|
|
48
57
|
|
|
@@ -21,6 +21,10 @@ NO COMPLETION CLAIMS WITHOUT FRESH VERIFICATION EVIDENCE
|
|
|
21
21
|
|
|
22
22
|
If you haven't run the verification command in this message, you cannot claim it passes.
|
|
23
23
|
|
|
24
|
+
**This applies at ALL complexity tiers.** What varies is the depth of verification:
|
|
25
|
+
- **Simple tasks:** Run the relevant verification command (tests, linter, build) and confirm it passes
|
|
26
|
+
- **Medium/Complex tasks:** Run full verification suite, check coverage, verify edge cases, review against requirements
|
|
27
|
+
|
|
24
28
|
## The Gate Function
|
|
25
29
|
|
|
26
30
|
```
|
|
@@ -134,14 +138,16 @@ From 24 failure memories:
|
|
|
134
138
|
|
|
135
139
|
**Lightweight usage:**
|
|
136
140
|
- Run core tests, confirm pass
|
|
141
|
+
- Applies when session-bootstrap classifies the task as Simple
|
|
137
142
|
|
|
138
143
|
**Standard usage:**
|
|
139
144
|
- Run all tests + check coverage + verify edge cases
|
|
140
145
|
|
|
141
|
-
|
|
146
|
+
**Deep usage:**
|
|
147
|
+
- Standard + requirements checklist + line-by-line verification + cross-reference with design spec
|
|
142
148
|
|
|
143
|
-
|
|
149
|
+
## The Bottom Line
|
|
144
150
|
|
|
145
|
-
|
|
151
|
+
**No shortcuts for verification.** The principle is non-negotiable; the depth adapts to complexity.
|
|
146
152
|
|
|
147
|
-
|
|
153
|
+
Run the command. Read the output. THEN claim the result. Whether you run one test or the full suite depends on the task's complexity tier, but you must ALWAYS run something before claiming completion.
|
package/dist/cli/migration.d.ts
DELETED
|
@@ -1,46 +0,0 @@
|
|
|
1
|
-
/** Result of a migration attempt. */
|
|
2
|
-
export interface MigrationResult {
|
|
3
|
-
/** Whether a legacy config was detected. */
|
|
4
|
-
detected: boolean;
|
|
5
|
-
/** Whether the migration was performed. */
|
|
6
|
-
migrated: boolean;
|
|
7
|
-
/** Path to the new config file, if migrated. */
|
|
8
|
-
newPath?: string;
|
|
9
|
-
/** Path to the legacy config file that was found. */
|
|
10
|
-
legacyPath?: string;
|
|
11
|
-
/** Error message if migration failed. */
|
|
12
|
-
error?: string;
|
|
13
|
-
}
|
|
14
|
-
/**
|
|
15
|
-
* Get all possible legacy config file paths (json and jsonc).
|
|
16
|
-
*/
|
|
17
|
-
export declare function getLegacyConfigPaths(): string[];
|
|
18
|
-
/**
|
|
19
|
-
* Detect whether a legacy `oh-my-opencode-slim.json` config exists.
|
|
20
|
-
* Returns the path if found, or undefined.
|
|
21
|
-
*/
|
|
22
|
-
export declare function detectLegacyConfig(): string | undefined;
|
|
23
|
-
/**
|
|
24
|
-
* Check if a config object contains any legacy agent names
|
|
25
|
-
* in its presets or agents sections.
|
|
26
|
-
*/
|
|
27
|
-
export declare function hasLegacyAgentNames(config: Record<string, unknown>): boolean;
|
|
28
|
-
/**
|
|
29
|
-
* Migrate agent names in a config object.
|
|
30
|
-
*
|
|
31
|
-
* - Renames primary mapping keys (explorer→researcher, oracle→reviewer,
|
|
32
|
-
* designer→implementer).
|
|
33
|
-
* - For secondary mappings (librarian→researcher, fixer→implementer),
|
|
34
|
-
* only applies if the primary mapping target doesn't already exist.
|
|
35
|
-
* - Removes observer entries entirely.
|
|
36
|
-
* - Preserves all non-agent config keys unchanged.
|
|
37
|
-
*/
|
|
38
|
-
export declare function migrateAgentNames(config: Record<string, unknown>): Record<string, unknown>;
|
|
39
|
-
/**
|
|
40
|
-
* Perform the full migration:
|
|
41
|
-
* 1. Read legacy config
|
|
42
|
-
* 2. Migrate agent names
|
|
43
|
-
* 3. Write to `agent-forge.json`
|
|
44
|
-
* 4. Rename legacy file to `.bak`
|
|
45
|
-
*/
|
|
46
|
-
export declare function performMigration(legacyPath: string): MigrationResult;
|