@martinffx/atelier 3.0.1 → 3.1.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/CHANGELOG.md +17 -0
- package/README.md +19 -1
- package/dist/atelier.js +237 -44
- package/package.json +1 -1
- package/skills/atelier-orchestrator/SKILL.md +16 -3
- package/skills/atelier-setup/SKILL.md +43 -20
- package/skills/atelier-setup/references/domain.md +22 -0
- package/skills/atelier-setup/references/issue-tracker.md +35 -0
- package/skills/code-commit/SKILL.md +4 -3
- package/skills/code-pull-request/references/common.md +4 -4
- package/skills/code-pull-request/references/create-pr.md +15 -15
- package/skills/code-pull-request/references/merge-pr.md +3 -3
- package/skills/code-pull-request/references/read-comments.md +2 -2
- package/skills/code-review/SKILL.md +27 -8
- package/skills/code-review/references/output.md +52 -0
- package/skills/code-review/references/reviewers.md +113 -13
- package/skills/code-review/references/rq.md +195 -53
- package/skills/code-review/references/rs.md +41 -5
- package/skills/code-subagents/SKILL.md +4 -2
- package/skills/code-subagents/references/batch-reviewer-prompt.md +9 -0
- package/skills/code-subagents/references/implementor-prompt.md +7 -0
- package/skills/oracle-debug/SKILL.md +17 -10
- package/skills/oracle-debug/references/techniques.md +41 -137
- package/skills/oracle-domain-modelling/SKILL.md +5 -2
- package/skills/oracle-grill-me/SKILL.md +4 -1
- package/skills/spec-brainstorm/SKILL.md +34 -9
- package/skills/spec-finish/SKILL.md +40 -36
- package/skills/spec-implement/SKILL.md +38 -47
- package/skills/spec-plan/SKILL.md +64 -32
- package/skills/spec-plan/references/plan_template.md +15 -2
- package/skills/oracle-debug/references/language-tools.md +0 -139
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,23 @@
|
|
|
4
4
|
|
|
5
5
|
- **breaking:** rename the `recon` agent to `sentinel`. Existing Atelier configuration is migrated automatically and legacy generated files are removed during the next command.
|
|
6
6
|
|
|
7
|
+
## [3.1.0](https://github.com/martinffx/atelier/compare/v3.0.2...v3.1.0) (2026-08-09)
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
### Features
|
|
11
|
+
|
|
12
|
+
* **code-review:** persist review decisions ([4b1a349](https://github.com/martinffx/atelier/commit/4b1a34962f2b52f248e4581c663ed139eed4e081))
|
|
13
|
+
* **codex:** configure project execution defaults ([74ef9cc](https://github.com/martinffx/atelier/commit/74ef9cc71c25145ba7591bd58306efc7e734a7c6))
|
|
14
|
+
* **skills:** apply writing guidance to prose ([3fce90d](https://github.com/martinffx/atelier/commit/3fce90da2881788a8d8cdcad63a61849b298d33c))
|
|
15
|
+
* **skills:** enforce simplicity throughout workflows ([fcf852b](https://github.com/martinffx/atelier/commit/fcf852bf68139e66d165761beeec8f74827513eb))
|
|
16
|
+
|
|
17
|
+
## [3.0.2](https://github.com/martinffx/atelier/compare/v3.0.1...v3.0.2) (2026-08-01)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
### Bug Fixes
|
|
21
|
+
|
|
22
|
+
* **opencode:** synchronize user-invocable skill commands ([b373120](https://github.com/martinffx/atelier/commit/b37312040ed80cfdab78139f39c8e5150696b763))
|
|
23
|
+
|
|
7
24
|
## [3.0.1](https://github.com/martinffx/atelier/compare/v3.0.0...v3.0.1) (2026-07-31)
|
|
8
25
|
|
|
9
26
|
|
package/README.md
CHANGED
|
@@ -7,10 +7,28 @@
|
|
|
7
7
|
Atelier gives coding agents a disciplined way to move from an idea to reviewed, verified code without taking control away from the developer.
|
|
8
8
|
|
|
9
9
|
```bash
|
|
10
|
-
npx skills add martinffx/atelier
|
|
11
10
|
npx @martinffx/atelier@latest init --harness <claude|opencode|codex|cursor>
|
|
12
11
|
```
|
|
13
12
|
|
|
13
|
+
Install the skills separately:
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
npx skills add martinffx/atelier
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
Update installed skills with:
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
npx skills update martinffx/atelier
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
For OpenCode, installing skills first also creates slash commands for every installed skill
|
|
26
|
+
marked `user-invocable: true`. If you initialized OpenCode before installing the skills, run:
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
npx @martinffx/atelier@latest update --harness opencode
|
|
30
|
+
```
|
|
31
|
+
|
|
14
32
|
## How Atelier works
|
|
15
33
|
|
|
16
34
|
Atelier uses as much process as each request needs. Bounded work gets a concise plan in the conversation. Substantial work gets a durable spec, an implementation plan, and tracked execution. The developer approves the plan before implementation begins.
|
package/dist/atelier.js
CHANGED
|
@@ -25433,7 +25433,7 @@ var {
|
|
|
25433
25433
|
} = import__.default;
|
|
25434
25434
|
|
|
25435
25435
|
// src/commands/init.ts
|
|
25436
|
-
import { join as
|
|
25436
|
+
import { join as join4 } from "path";
|
|
25437
25437
|
import { homedir as homedir3 } from "os";
|
|
25438
25438
|
|
|
25439
25439
|
// node_modules/@inquirer/core/dist/esm/lib/key.js
|
|
@@ -32042,6 +32042,12 @@ class HarnessConfigError extends AtelierError {
|
|
|
32042
32042
|
super(`Failed to parse ${file}: ${cause}`, "HARNESS_CONFIG_ERROR", 1);
|
|
32043
32043
|
}
|
|
32044
32044
|
}
|
|
32045
|
+
class SkillDiscoveryError extends AtelierError {
|
|
32046
|
+
constructor(path2, cause) {
|
|
32047
|
+
super(`Failed to read skills from ${path2}: ${cause}`, "SKILL_DISCOVERY_ERROR", 1);
|
|
32048
|
+
}
|
|
32049
|
+
}
|
|
32050
|
+
|
|
32045
32051
|
class InvalidConfigError extends AtelierError {
|
|
32046
32052
|
constructor(cause, { suggestReinit = false } = {}) {
|
|
32047
32053
|
const suffix = suggestReinit ? ` Run \`atelier init --harness <${Harness.join("|")}>\` to reconfigure.` : "";
|
|
@@ -32183,6 +32189,13 @@ function writeConfig(config, path2 = CONFIG_PATH) {
|
|
|
32183
32189
|
writeFileSync2(path2, JSON.stringify(config, null, 2) + `
|
|
32184
32190
|
`);
|
|
32185
32191
|
}
|
|
32192
|
+
function toSharedConfig(config) {
|
|
32193
|
+
return {
|
|
32194
|
+
version: config.version,
|
|
32195
|
+
skills_source: config.skills_source,
|
|
32196
|
+
skills_path: config.skills_path
|
|
32197
|
+
};
|
|
32198
|
+
}
|
|
32186
32199
|
function getConfiguredHarnesses(config) {
|
|
32187
32200
|
return Harness.filter((harness) => config[harness] !== undefined);
|
|
32188
32201
|
}
|
|
@@ -32205,7 +32218,7 @@ function getDefaultConfig(harness) {
|
|
|
32205
32218
|
|
|
32206
32219
|
// src/services/paths.ts
|
|
32207
32220
|
import { homedir as homedir2 } from "os";
|
|
32208
|
-
import { join as join2 } from "path";
|
|
32221
|
+
import { isAbsolute, join as join2, resolve } from "path";
|
|
32209
32222
|
function shortPath(p) {
|
|
32210
32223
|
const home = homedir2();
|
|
32211
32224
|
if (p === home || p.startsWith(home + "/")) {
|
|
@@ -32216,6 +32229,15 @@ function shortPath(p) {
|
|
|
32216
32229
|
function getGlobalOpencodeDir() {
|
|
32217
32230
|
return join2(homedir2(), ".config", "opencode");
|
|
32218
32231
|
}
|
|
32232
|
+
function resolveSkillsPath(skillsPath) {
|
|
32233
|
+
if (skillsPath === "~") {
|
|
32234
|
+
return homedir2();
|
|
32235
|
+
}
|
|
32236
|
+
if (skillsPath.startsWith("~/")) {
|
|
32237
|
+
return join2(homedir2(), skillsPath.slice(2));
|
|
32238
|
+
}
|
|
32239
|
+
return isAbsolute(skillsPath) ? skillsPath : resolve(skillsPath);
|
|
32240
|
+
}
|
|
32219
32241
|
function resolveBasePath(harness) {
|
|
32220
32242
|
return harness === "opencode" ? getGlobalOpencodeDir() : homedir2();
|
|
32221
32243
|
}
|
|
@@ -32305,7 +32327,7 @@ async function promptForOpenCodeModels(prompt2, section, models, provider) {
|
|
|
32305
32327
|
};
|
|
32306
32328
|
}
|
|
32307
32329
|
function formatFileList(files) {
|
|
32308
|
-
return files.map((f) => ` ${f.exists ? "~" : "+"} ${f.path}`).join(`
|
|
32330
|
+
return files.map((f) => ` ${f.action === "delete" ? "-" : f.exists ? "~" : "+"} ${f.path}`).join(`
|
|
32309
32331
|
`);
|
|
32310
32332
|
}
|
|
32311
32333
|
function guardProvider(provider) {
|
|
@@ -32315,6 +32337,67 @@ function guardProvider(provider) {
|
|
|
32315
32337
|
return provider;
|
|
32316
32338
|
}
|
|
32317
32339
|
|
|
32340
|
+
// src/utils/templates.ts
|
|
32341
|
+
var import_gray_matter = __toESM(require_gray_matter(), 1);
|
|
32342
|
+
import { readFileSync as readFileSync3, existsSync as existsSync2, readdirSync, lstatSync, statSync } from "fs";
|
|
32343
|
+
import { join as join3, dirname as dirname2 } from "path";
|
|
32344
|
+
import { fileURLToPath } from "url";
|
|
32345
|
+
var __dirname2 = dirname2(fileURLToPath(import.meta.url));
|
|
32346
|
+
var AGENTS_DIR = existsSync2(join3(__dirname2, "..", "agents")) ? join3(__dirname2, "..", "agents") : join3(__dirname2, "..", "..", "agents");
|
|
32347
|
+
var MAX_SKILL_FILE_SIZE = 1024 * 1024;
|
|
32348
|
+
function readTemplate(name) {
|
|
32349
|
+
const filePath = join3(AGENTS_DIR, `${name}.md`);
|
|
32350
|
+
const raw = readFileSync3(filePath, "utf-8");
|
|
32351
|
+
const { data, content } = import_gray_matter.default(raw);
|
|
32352
|
+
return {
|
|
32353
|
+
name: data.name,
|
|
32354
|
+
description: data.description,
|
|
32355
|
+
body: content.trim()
|
|
32356
|
+
};
|
|
32357
|
+
}
|
|
32358
|
+
function readUserInvocableSkills(skillsPath) {
|
|
32359
|
+
let entries;
|
|
32360
|
+
try {
|
|
32361
|
+
entries = readdirSync(skillsPath, { withFileTypes: true });
|
|
32362
|
+
} catch (err2) {
|
|
32363
|
+
if (err2.code === "ENOENT") {
|
|
32364
|
+
return { skills: [], warnings: [] };
|
|
32365
|
+
}
|
|
32366
|
+
throw new SkillDiscoveryError(skillsPath, err2 instanceof Error ? err2.message : String(err2));
|
|
32367
|
+
}
|
|
32368
|
+
const warnings = [];
|
|
32369
|
+
const skills = entries.flatMap((entry) => {
|
|
32370
|
+
const skillDir = join3(skillsPath, entry.name);
|
|
32371
|
+
try {
|
|
32372
|
+
if (!entry.isDirectory() && !(entry.isSymbolicLink() && statSync(skillDir).isDirectory())) {
|
|
32373
|
+
return [];
|
|
32374
|
+
}
|
|
32375
|
+
const skillPath = join3(skillDir, "SKILL.md");
|
|
32376
|
+
if (!existsSync2(skillPath)) {
|
|
32377
|
+
return [];
|
|
32378
|
+
}
|
|
32379
|
+
const stat = lstatSync(skillPath);
|
|
32380
|
+
if (!stat.isFile() || stat.size > MAX_SKILL_FILE_SIZE) {
|
|
32381
|
+
warnings.push(`Skipped ${skillPath}: expected a regular SKILL.md no larger than ${MAX_SKILL_FILE_SIZE} bytes.`);
|
|
32382
|
+
return [];
|
|
32383
|
+
}
|
|
32384
|
+
const { data } = import_gray_matter.default(readFileSync3(skillPath, "utf-8"));
|
|
32385
|
+
if (data["user-invocable"] !== true) {
|
|
32386
|
+
return [];
|
|
32387
|
+
}
|
|
32388
|
+
if (typeof data.name !== "string" || !/^[a-z0-9]+(?:-[a-z0-9]+)*$/.test(data.name) || typeof data.description !== "string") {
|
|
32389
|
+
warnings.push(`Skipped ${skillPath}: invalid user-invocable skill metadata.`);
|
|
32390
|
+
return [];
|
|
32391
|
+
}
|
|
32392
|
+
return [{ name: data.name, description: data.description }];
|
|
32393
|
+
} catch (err2) {
|
|
32394
|
+
warnings.push(`Skipped ${skillDir}: ${err2 instanceof Error ? err2.message : String(err2)}`);
|
|
32395
|
+
return [];
|
|
32396
|
+
}
|
|
32397
|
+
}).sort((a, b) => a.name.localeCompare(b.name));
|
|
32398
|
+
return { skills: [...new Map(skills.map((skill) => [skill.name, skill])).values()], warnings };
|
|
32399
|
+
}
|
|
32400
|
+
|
|
32318
32401
|
// src/commands/init.ts
|
|
32319
32402
|
async function init(options2) {
|
|
32320
32403
|
const harnessOption = options2.harness;
|
|
@@ -32338,7 +32421,7 @@ async function init(options2) {
|
|
|
32338
32421
|
]);
|
|
32339
32422
|
harness = answer.harness;
|
|
32340
32423
|
}
|
|
32341
|
-
const configPath =
|
|
32424
|
+
const configPath = join4(homedir3(), CONFIG_FILE);
|
|
32342
32425
|
const configResult = readConfig(configPath);
|
|
32343
32426
|
if (!configResult.ok) {
|
|
32344
32427
|
if (configResult.error.type !== "not-found") {
|
|
@@ -32357,7 +32440,7 @@ async function init(options2) {
|
|
|
32357
32440
|
validateConfig(config);
|
|
32358
32441
|
const harnessBasePath = resolveBasePath(harness);
|
|
32359
32442
|
if (!options2.yes) {
|
|
32360
|
-
const files = adapter.fileList(harnessBasePath);
|
|
32443
|
+
const files = adapter.fileList(harnessBasePath, toSharedConfig(config));
|
|
32361
32444
|
console.log(`
|
|
32362
32445
|
Files to write:`);
|
|
32363
32446
|
console.log(formatFileList(files));
|
|
@@ -32373,7 +32456,7 @@ Files to write:`);
|
|
|
32373
32456
|
}
|
|
32374
32457
|
}
|
|
32375
32458
|
adapter.mergeHarnessConfig(section, harnessBasePath);
|
|
32376
|
-
adapter.installAgents(section, harnessBasePath);
|
|
32459
|
+
adapter.installAgents(section, harnessBasePath, toSharedConfig(config));
|
|
32377
32460
|
writeConfig(config, configPath);
|
|
32378
32461
|
console.log(`
|
|
32379
32462
|
Atelier initialized for ${harness}.`);
|
|
@@ -32383,10 +32466,13 @@ Next step:`);
|
|
|
32383
32466
|
console.log(" opencode auth login --provider openai # connect your OpenAI account");
|
|
32384
32467
|
}
|
|
32385
32468
|
console.log(" npx skills add martinffx/atelier # install skills");
|
|
32469
|
+
if (harness === "opencode" && readUserInvocableSkills(resolveSkillsPath(config.skills_path)).skills.length === 0) {
|
|
32470
|
+
console.log(" npx @martinffx/atelier@latest update --harness opencode # create commands after installing skills");
|
|
32471
|
+
}
|
|
32386
32472
|
}
|
|
32387
32473
|
|
|
32388
32474
|
// src/commands/update.ts
|
|
32389
|
-
import { join as
|
|
32475
|
+
import { join as join5 } from "path";
|
|
32390
32476
|
import { homedir as homedir4 } from "os";
|
|
32391
32477
|
|
|
32392
32478
|
// src/commands/utils.ts
|
|
@@ -32418,7 +32504,7 @@ async function resolveHarness(config, harnessOption, command) {
|
|
|
32418
32504
|
|
|
32419
32505
|
// src/commands/update.ts
|
|
32420
32506
|
async function update(options2) {
|
|
32421
|
-
const configPath =
|
|
32507
|
+
const configPath = join5(homedir4(), CONFIG_FILE);
|
|
32422
32508
|
const configResult = readConfig(configPath);
|
|
32423
32509
|
if (!configResult.ok) {
|
|
32424
32510
|
if (configResult.error.type === "not-found") {
|
|
@@ -32435,7 +32521,7 @@ async function update(options2) {
|
|
|
32435
32521
|
const adapter = getAdapter(harness);
|
|
32436
32522
|
const section = await promptForSection(adapter, existingSection);
|
|
32437
32523
|
const harnessBasePath = resolveBasePath(harness);
|
|
32438
|
-
const files = adapter.fileList(harnessBasePath);
|
|
32524
|
+
const files = adapter.fileList(harnessBasePath, toSharedConfig(config));
|
|
32439
32525
|
console.log(`
|
|
32440
32526
|
Files to write:`);
|
|
32441
32527
|
console.log(formatFileList(files));
|
|
@@ -32452,17 +32538,17 @@ Files to write:`);
|
|
|
32452
32538
|
config[harness] = section;
|
|
32453
32539
|
validateConfig(config);
|
|
32454
32540
|
adapter.mergeHarnessConfig(section, harnessBasePath);
|
|
32455
|
-
adapter.installAgents(section, harnessBasePath);
|
|
32541
|
+
adapter.installAgents(section, harnessBasePath, toSharedConfig(config));
|
|
32456
32542
|
writeConfig(config, configPath);
|
|
32457
32543
|
console.log(`Atelier updated for ${harness}.`);
|
|
32458
32544
|
console.log("Skills are managed separately. Run `npx skills update martinffx/atelier` to update skills.");
|
|
32459
32545
|
}
|
|
32460
32546
|
|
|
32461
32547
|
// src/commands/remove.ts
|
|
32462
|
-
import { join as
|
|
32548
|
+
import { join as join6 } from "path";
|
|
32463
32549
|
import { homedir as homedir5 } from "os";
|
|
32464
32550
|
async function remove(options2) {
|
|
32465
|
-
const configPath =
|
|
32551
|
+
const configPath = join6(homedir5(), CONFIG_FILE);
|
|
32466
32552
|
const configResult = readConfig(configPath);
|
|
32467
32553
|
if (!configResult.ok) {
|
|
32468
32554
|
if (configResult.error.type === "not-found") {
|
|
@@ -32492,27 +32578,9 @@ async function remove(options2) {
|
|
|
32492
32578
|
}
|
|
32493
32579
|
|
|
32494
32580
|
// src/adapters/claude.ts
|
|
32495
|
-
import { writeFileSync as writeFileSync3, mkdirSync as mkdirSync2, readFileSync as readFileSync4, rmSync as rmSync2, rmdirSync, existsSync as existsSync3, readdirSync } from "fs";
|
|
32581
|
+
import { writeFileSync as writeFileSync3, mkdirSync as mkdirSync2, readFileSync as readFileSync4, rmSync as rmSync2, rmdirSync, existsSync as existsSync3, readdirSync as readdirSync2 } from "fs";
|
|
32496
32582
|
import { join as join7 } from "path";
|
|
32497
32583
|
|
|
32498
|
-
// src/utils/templates.ts
|
|
32499
|
-
var import_gray_matter = __toESM(require_gray_matter(), 1);
|
|
32500
|
-
import { readFileSync as readFileSync3, existsSync as existsSync2 } from "fs";
|
|
32501
|
-
import { join as join6, dirname as dirname2 } from "path";
|
|
32502
|
-
import { fileURLToPath } from "url";
|
|
32503
|
-
var __dirname2 = dirname2(fileURLToPath(import.meta.url));
|
|
32504
|
-
var AGENTS_DIR = existsSync2(join6(__dirname2, "..", "agents")) ? join6(__dirname2, "..", "agents") : join6(__dirname2, "..", "..", "agents");
|
|
32505
|
-
function readTemplate(name) {
|
|
32506
|
-
const filePath = join6(AGENTS_DIR, `${name}.md`);
|
|
32507
|
-
const raw = readFileSync3(filePath, "utf-8");
|
|
32508
|
-
const { data, content } = import_gray_matter.default(raw);
|
|
32509
|
-
return {
|
|
32510
|
-
name: data.name,
|
|
32511
|
-
description: data.description,
|
|
32512
|
-
body: content.trim()
|
|
32513
|
-
};
|
|
32514
|
-
}
|
|
32515
|
-
|
|
32516
32584
|
// src/utils/schemas.ts
|
|
32517
32585
|
var AgentSchema = exports_external.object({
|
|
32518
32586
|
template: exports_external.enum(AGENT_NAMES),
|
|
@@ -32643,7 +32711,7 @@ function removeDirIfEmpty(dir) {
|
|
|
32643
32711
|
return;
|
|
32644
32712
|
}
|
|
32645
32713
|
try {
|
|
32646
|
-
if (
|
|
32714
|
+
if (readdirSync2(dir).length === 0) {
|
|
32647
32715
|
rmdirSync(dir);
|
|
32648
32716
|
}
|
|
32649
32717
|
} catch (err2) {
|
|
@@ -32692,8 +32760,8 @@ function readExistingSettings(settingsPath) {
|
|
|
32692
32760
|
}
|
|
32693
32761
|
|
|
32694
32762
|
// src/adapters/opencode.ts
|
|
32695
|
-
import { writeFileSync as writeFileSync4, mkdirSync as mkdirSync3, readFileSync as readFileSync5, existsSync as existsSync4, rmSync as rmSync3, rmdirSync as rmdirSync2, readdirSync as
|
|
32696
|
-
import { join as join8 } from "path";
|
|
32763
|
+
import { writeFileSync as writeFileSync4, mkdirSync as mkdirSync3, readFileSync as readFileSync5, existsSync as existsSync4, rmSync as rmSync3, rmdirSync as rmdirSync2, readdirSync as readdirSync3, lstatSync as lstatSync2, realpathSync, statSync as statSync2 } from "fs";
|
|
32764
|
+
import { basename, join as join8 } from "path";
|
|
32697
32765
|
var OPENCODE_PROVIDERS2 = [
|
|
32698
32766
|
{ name: "OpenCode Zen", value: "opencode-zen" },
|
|
32699
32767
|
{ name: "OpenCode Go", value: "opencode-go" },
|
|
@@ -32781,6 +32849,7 @@ var DEFAULT_MODELS2 = {
|
|
|
32781
32849
|
plan: "openai/gpt-5.6-sol"
|
|
32782
32850
|
}
|
|
32783
32851
|
};
|
|
32852
|
+
var COMMAND_MARKER = "<!-- Generated by Atelier -->";
|
|
32784
32853
|
var opencodeAdapter = {
|
|
32785
32854
|
name: "opencode",
|
|
32786
32855
|
providerChoices: OPENCODE_PROVIDERS2,
|
|
@@ -32817,7 +32886,7 @@ async function promptSection2(prompt2, section) {
|
|
|
32817
32886
|
const models = modelsForProvider2(provider);
|
|
32818
32887
|
return promptForOpenCodeModels(prompt2, openCodeSection, models, provider);
|
|
32819
32888
|
}
|
|
32820
|
-
function installAgents2(section, basePath) {
|
|
32889
|
+
function installAgents2(section, basePath, sharedConfig) {
|
|
32821
32890
|
const config = section;
|
|
32822
32891
|
const opencodeRoot = getOpencodeRoot(basePath);
|
|
32823
32892
|
const agentsDir = join8(opencodeRoot, "agent");
|
|
@@ -32842,6 +32911,41 @@ temperature: 0.2
|
|
|
32842
32911
|
writeFileSync4(agentPath, content);
|
|
32843
32912
|
console.log(`Created ${shortPath(agentPath)}`);
|
|
32844
32913
|
}
|
|
32914
|
+
if (sharedConfig) {
|
|
32915
|
+
installCommands(opencodeRoot, sharedConfig);
|
|
32916
|
+
}
|
|
32917
|
+
}
|
|
32918
|
+
function installCommands(opencodeRoot, sharedConfig) {
|
|
32919
|
+
const discovery = readUserInvocableSkills(resolveSkillsPath(sharedConfig.skills_path));
|
|
32920
|
+
reportSkillWarnings(discovery.warnings);
|
|
32921
|
+
const commandsDir = getCommandsDir(opencodeRoot, discovery.skills.length > 0);
|
|
32922
|
+
if (!commandsDir) {
|
|
32923
|
+
return;
|
|
32924
|
+
}
|
|
32925
|
+
const desiredCommands = new Map(discovery.skills.map((skill) => [skill.name, skill]));
|
|
32926
|
+
for (const path2 of managedCommandPaths(commandsDir)) {
|
|
32927
|
+
if (!desiredCommands.has(basename(path2, ".md"))) {
|
|
32928
|
+
rmSync3(path2, { force: true });
|
|
32929
|
+
console.log(`Removed ${shortPath(path2)}`);
|
|
32930
|
+
}
|
|
32931
|
+
}
|
|
32932
|
+
for (const skill of discovery.skills) {
|
|
32933
|
+
const commandPath = join8(commandsDir, `${skill.name}.md`);
|
|
32934
|
+
if (commandStatus(commandPath) === "custom") {
|
|
32935
|
+
console.log(`Skipped ${shortPath(commandPath)} because it is not managed by Atelier.`);
|
|
32936
|
+
continue;
|
|
32937
|
+
}
|
|
32938
|
+
const content = `---
|
|
32939
|
+
description: ${JSON.stringify(skill.description)}
|
|
32940
|
+
---
|
|
32941
|
+
${COMMAND_MARKER}
|
|
32942
|
+
Activate the ${skill.name} skill and follow its instructions precisely.
|
|
32943
|
+
|
|
32944
|
+
User request: $ARGUMENTS
|
|
32945
|
+
`;
|
|
32946
|
+
writeFileSync4(commandPath, content);
|
|
32947
|
+
console.log(`Created ${shortPath(commandPath)}`);
|
|
32948
|
+
}
|
|
32845
32949
|
}
|
|
32846
32950
|
function mergeHarnessConfig2(section, basePath) {
|
|
32847
32951
|
const config = section;
|
|
@@ -32885,7 +32989,7 @@ function mergeHarnessConfig2(section, basePath) {
|
|
|
32885
32989
|
}
|
|
32886
32990
|
console.log(`${isNew ? "Created" : "Updated"} ${shortPath(opencodeJsonPath)}`);
|
|
32887
32991
|
}
|
|
32888
|
-
function fileList2(basePath) {
|
|
32992
|
+
function fileList2(basePath, sharedConfig) {
|
|
32889
32993
|
const files = [
|
|
32890
32994
|
{ path: join8(basePath, "opencode.json"), exists: existsSync4(join8(basePath, "opencode.json")) }
|
|
32891
32995
|
];
|
|
@@ -32894,6 +32998,27 @@ function fileList2(basePath) {
|
|
|
32894
32998
|
const path2 = join8(opencodeRoot, "agent", `${name}.md`);
|
|
32895
32999
|
files.push({ path: path2, exists: existsSync4(path2) });
|
|
32896
33000
|
}
|
|
33001
|
+
if (sharedConfig) {
|
|
33002
|
+
const discovery = readUserInvocableSkills(resolveSkillsPath(sharedConfig.skills_path));
|
|
33003
|
+
reportSkillWarnings(discovery.warnings);
|
|
33004
|
+
const commandsDir = getCommandsDir(opencodeRoot, discovery.skills.length > 0);
|
|
33005
|
+
if (!commandsDir) {
|
|
33006
|
+
return files;
|
|
33007
|
+
}
|
|
33008
|
+
const desiredNames = new Set(discovery.skills.map((skill) => skill.name));
|
|
33009
|
+
for (const path2 of managedCommandPaths(commandsDir)) {
|
|
33010
|
+
if (!desiredNames.has(basename(path2, ".md"))) {
|
|
33011
|
+
files.push({ path: path2, exists: true, action: "delete" });
|
|
33012
|
+
}
|
|
33013
|
+
}
|
|
33014
|
+
for (const skill of discovery.skills) {
|
|
33015
|
+
const path2 = join8(opencodeRoot, "command", `${skill.name}.md`);
|
|
33016
|
+
const commandPath = join8(commandsDir, `${skill.name}.md`);
|
|
33017
|
+
if (commandStatus(commandPath) !== "custom") {
|
|
33018
|
+
files.push({ path: path2, exists: existsSync4(commandPath) });
|
|
33019
|
+
}
|
|
33020
|
+
}
|
|
33021
|
+
}
|
|
32897
33022
|
return files;
|
|
32898
33023
|
}
|
|
32899
33024
|
function remove3(section, basePath) {
|
|
@@ -32919,7 +33044,7 @@ function removeDirIfEmpty2(dir) {
|
|
|
32919
33044
|
return;
|
|
32920
33045
|
}
|
|
32921
33046
|
try {
|
|
32922
|
-
if (
|
|
33047
|
+
if (readdirSync3(dir).length === 0) {
|
|
32923
33048
|
rmdirSync2(dir);
|
|
32924
33049
|
}
|
|
32925
33050
|
} catch (err2) {
|
|
@@ -32931,9 +33056,71 @@ function removeLegacyFiles(opencodeRoot) {
|
|
|
32931
33056
|
if (existsSync4(pluginPath)) {
|
|
32932
33057
|
rmSync3(pluginPath, { force: true });
|
|
32933
33058
|
}
|
|
33059
|
+
const commandsDir = getCommandsDir(opencodeRoot, false);
|
|
33060
|
+
if (!commandsDir) {
|
|
33061
|
+
return;
|
|
33062
|
+
}
|
|
33063
|
+
for (const path2 of managedCommandPaths(commandsDir)) {
|
|
33064
|
+
rmSync3(path2, { force: true });
|
|
33065
|
+
}
|
|
33066
|
+
if (!lstatSync2(join8(opencodeRoot, "command")).isSymbolicLink()) {
|
|
33067
|
+
removeDirIfEmpty2(commandsDir);
|
|
33068
|
+
}
|
|
33069
|
+
}
|
|
33070
|
+
function reportSkillWarnings(warnings) {
|
|
33071
|
+
for (const warning of warnings) {
|
|
33072
|
+
console.warn(warning);
|
|
33073
|
+
}
|
|
33074
|
+
}
|
|
33075
|
+
function getCommandsDir(opencodeRoot, create) {
|
|
32934
33076
|
const commandsDir = join8(opencodeRoot, "command");
|
|
32935
|
-
if (existsSync4(commandsDir)) {
|
|
32936
|
-
|
|
33077
|
+
if (!existsSync4(commandsDir)) {
|
|
33078
|
+
if (!create) {
|
|
33079
|
+
return;
|
|
33080
|
+
}
|
|
33081
|
+
try {
|
|
33082
|
+
mkdirSync3(commandsDir, { recursive: true });
|
|
33083
|
+
return commandsDir;
|
|
33084
|
+
} catch (err2) {
|
|
33085
|
+
throw new FileWriteError(commandsDir, err2 instanceof Error ? err2.message : String(err2));
|
|
33086
|
+
}
|
|
33087
|
+
}
|
|
33088
|
+
try {
|
|
33089
|
+
const stat = lstatSync2(commandsDir);
|
|
33090
|
+
if (stat.isSymbolicLink()) {
|
|
33091
|
+
const target = realpathSync(commandsDir);
|
|
33092
|
+
if (!statSync2(target).isDirectory()) {
|
|
33093
|
+
throw new Error("symbolic link target is not a directory");
|
|
33094
|
+
}
|
|
33095
|
+
return target;
|
|
33096
|
+
}
|
|
33097
|
+
if (!stat.isDirectory()) {
|
|
33098
|
+
throw new Error("path is not a directory");
|
|
33099
|
+
}
|
|
33100
|
+
return commandsDir;
|
|
33101
|
+
} catch (err2) {
|
|
33102
|
+
throw new FileWriteError(commandsDir, err2 instanceof Error ? err2.message : String(err2));
|
|
33103
|
+
}
|
|
33104
|
+
}
|
|
33105
|
+
function managedCommandPaths(commandsDir) {
|
|
33106
|
+
try {
|
|
33107
|
+
return readdirSync3(commandsDir, { withFileTypes: true }).filter((entry) => entry.isFile() && entry.name.endsWith(".md")).map((entry) => join8(commandsDir, entry.name)).filter((path2) => readFileSync5(path2, "utf-8").includes(COMMAND_MARKER));
|
|
33108
|
+
} catch (err2) {
|
|
33109
|
+
throw new FileWriteError(commandsDir, err2 instanceof Error ? err2.message : String(err2));
|
|
33110
|
+
}
|
|
33111
|
+
}
|
|
33112
|
+
function commandStatus(path2) {
|
|
33113
|
+
try {
|
|
33114
|
+
const stat = lstatSync2(path2);
|
|
33115
|
+
if (stat.isSymbolicLink() || !stat.isFile()) {
|
|
33116
|
+
throw new Error("command path must be a regular file");
|
|
33117
|
+
}
|
|
33118
|
+
return readFileSync5(path2, "utf-8").includes(COMMAND_MARKER) ? "managed" : "custom";
|
|
33119
|
+
} catch (err2) {
|
|
33120
|
+
if (err2.code === "ENOENT") {
|
|
33121
|
+
return "missing";
|
|
33122
|
+
}
|
|
33123
|
+
throw new FileWriteError(path2, err2 instanceof Error ? err2.message : String(err2));
|
|
32937
33124
|
}
|
|
32938
33125
|
}
|
|
32939
33126
|
function writeOrDeleteOpenCodeJson(basePath, config) {
|
|
@@ -32998,7 +33185,7 @@ function deepMerge2(target, source) {
|
|
|
32998
33185
|
}
|
|
32999
33186
|
|
|
33000
33187
|
// src/adapters/codex.ts
|
|
33001
|
-
import { writeFileSync as writeFileSync5, mkdirSync as mkdirSync4, readFileSync as readFileSync6, existsSync as existsSync5, rmSync as rmSync4, rmdirSync as rmdirSync3, readdirSync as
|
|
33188
|
+
import { writeFileSync as writeFileSync5, mkdirSync as mkdirSync4, readFileSync as readFileSync6, existsSync as existsSync5, rmSync as rmSync4, rmdirSync as rmdirSync3, readdirSync as readdirSync4 } from "fs";
|
|
33002
33189
|
import { join as join9 } from "path";
|
|
33003
33190
|
|
|
33004
33191
|
// node_modules/smol-toml/dist/date.js
|
|
@@ -34090,6 +34277,9 @@ var DEFAULT_MODELS3 = {
|
|
|
34090
34277
|
var MANAGED_CODEX_KEYS = [
|
|
34091
34278
|
{ key: "model" },
|
|
34092
34279
|
{ key: "model_reasoning_effort" },
|
|
34280
|
+
{ key: "plan_mode_reasoning_effort" },
|
|
34281
|
+
{ key: "sandbox_mode" },
|
|
34282
|
+
{ key: "approval_policy" },
|
|
34093
34283
|
{
|
|
34094
34284
|
key: "features",
|
|
34095
34285
|
nested: { features: ["multi_agent"] }
|
|
@@ -34168,6 +34358,9 @@ function mergeHarnessConfig3(section, basePath) {
|
|
|
34168
34358
|
...existing,
|
|
34169
34359
|
model: config.default_model,
|
|
34170
34360
|
model_reasoning_effort: "medium",
|
|
34361
|
+
plan_mode_reasoning_effort: "high",
|
|
34362
|
+
sandbox_mode: "danger-full-access",
|
|
34363
|
+
approval_policy: "on-request",
|
|
34171
34364
|
features: {
|
|
34172
34365
|
...isObject(existing.features) ? existing.features : {},
|
|
34173
34366
|
multi_agent: true
|
|
@@ -34216,7 +34409,7 @@ function removeDirIfEmpty3(dir) {
|
|
|
34216
34409
|
return;
|
|
34217
34410
|
}
|
|
34218
34411
|
try {
|
|
34219
|
-
if (
|
|
34412
|
+
if (readdirSync4(dir).length === 0) {
|
|
34220
34413
|
rmdirSync3(dir);
|
|
34221
34414
|
}
|
|
34222
34415
|
} catch (err2) {
|
|
@@ -34274,7 +34467,7 @@ function removeEmptyCodexDir(codexDir) {
|
|
|
34274
34467
|
return;
|
|
34275
34468
|
}
|
|
34276
34469
|
try {
|
|
34277
|
-
const entries =
|
|
34470
|
+
const entries = readdirSync4(codexDir);
|
|
34278
34471
|
if (entries.length === 0) {
|
|
34279
34472
|
rmSync4(codexDir, { recursive: true, force: true });
|
|
34280
34473
|
}
|
|
@@ -34287,7 +34480,7 @@ function isObject(value) {
|
|
|
34287
34480
|
}
|
|
34288
34481
|
|
|
34289
34482
|
// src/adapters/cursor.ts
|
|
34290
|
-
import { existsSync as existsSync6, mkdirSync as mkdirSync5, readdirSync as
|
|
34483
|
+
import { existsSync as existsSync6, mkdirSync as mkdirSync5, readdirSync as readdirSync5, rmSync as rmSync5, rmdirSync as rmdirSync4, writeFileSync as writeFileSync6 } from "fs";
|
|
34291
34484
|
import { join as join10 } from "path";
|
|
34292
34485
|
var CURSOR_MODELS = [
|
|
34293
34486
|
"composer-2.5",
|
|
@@ -34378,7 +34571,7 @@ function remove5(section, basePath) {
|
|
|
34378
34571
|
rmSync5(join10(agentsDir, `${agent.name}.md`), { force: true });
|
|
34379
34572
|
}
|
|
34380
34573
|
rmSync5(join10(agentsDir, `${LEGACY_AGENT_NAME}.md`), { force: true });
|
|
34381
|
-
if (existsSync6(agentsDir) &&
|
|
34574
|
+
if (existsSync6(agentsDir) && readdirSync5(agentsDir).length === 0) {
|
|
34382
34575
|
rmdirSync4(agentsDir);
|
|
34383
34576
|
}
|
|
34384
34577
|
} catch (err2) {
|
package/package.json
CHANGED
|
@@ -33,6 +33,18 @@ The plan may be inline in the conversation or backed by persisted spec artifacts
|
|
|
33
33
|
the mode before invoking an artifact-producing skill, state the choice with one brief
|
|
34
34
|
reason, and let the human override it.
|
|
35
35
|
|
|
36
|
+
## Governing Principles
|
|
37
|
+
|
|
38
|
+
Invoking a skill selects relevant guidance. It does not make every pattern in every loaded
|
|
39
|
+
skill part of the solution. Existing source, explicit requirements, and the smallest correct
|
|
40
|
+
change determine which patterns apply.
|
|
41
|
+
|
|
42
|
+
Begin with the current design and the minimum behavior-preserving change. Product,
|
|
43
|
+
infrastructure, and architectural expansion require explicit scope. Keep **ponytail** active
|
|
44
|
+
throughout development when it is available or required by the repository. Keep
|
|
45
|
+
**writing-clearly-and-concisely** active whenever writing or editing prose for humans. Before
|
|
46
|
+
returning or persisting that prose, apply **humanizer** in embedded mode.
|
|
47
|
+
|
|
36
48
|
## Planning Modes
|
|
37
49
|
|
|
38
50
|
### Inline Plan (default)
|
|
@@ -125,9 +137,10 @@ The workflow is not purely linear. Expect backflows:
|
|
|
125
137
|
- Planning reveals design assumptions are wrong → back to brainstorm
|
|
126
138
|
- Tasks can't be decomposed without more context → back to brainstorm
|
|
127
139
|
|
|
128
|
-
### Implement → Plan (
|
|
129
|
-
-
|
|
130
|
-
|
|
140
|
+
### Implement → Plan (material changes)
|
|
141
|
+
- A minor deviation may be recorded inline and reflected in the plan when it does not change
|
|
142
|
+
approved behavior, scope, architecture, public contracts, or major dependencies.
|
|
143
|
+
- Material missing work or an unplanned dependency → revise the active plan and get approval.
|
|
131
144
|
|
|
132
145
|
### Implement → Research (fundamental issue)
|
|
133
146
|
- Inline planning or implementation develops substantial needs → ask the human whether to
|