@martinffx/atelier 3.0.1 → 3.0.2
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 +7 -0
- package/README.md +4 -0
- package/dist/atelier.js +231 -44
- package/package.json +1 -1
- package/skills/atelier-orchestrator/SKILL.md +4 -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/references/reviewers.md +20 -1
- package/skills/code-review/references/rq.md +40 -18
- 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 +8 -8
- package/skills/spec-finish/SKILL.md +21 -33
- package/skills/spec-implement/SKILL.md +26 -44
- package/skills/spec-plan/SKILL.md +15 -19
- package/skills/oracle-debug/references/language-tools.md +0 -139
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,13 @@
|
|
|
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.0.2](https://github.com/martinffx/atelier/compare/v3.0.1...v3.0.2) (2026-08-01)
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
### Bug Fixes
|
|
11
|
+
|
|
12
|
+
* **opencode:** synchronize user-invocable skill commands ([b373120](https://github.com/martinffx/atelier/commit/b37312040ed80cfdab78139f39c8e5150696b763))
|
|
13
|
+
|
|
7
14
|
## [3.0.1](https://github.com/martinffx/atelier/compare/v3.0.0...v3.0.1) (2026-07-31)
|
|
8
15
|
|
|
9
16
|
|
package/README.md
CHANGED
|
@@ -11,6 +11,10 @@ npx skills add martinffx/atelier
|
|
|
11
11
|
npx @martinffx/atelier@latest init --harness <claude|opencode|codex|cursor>
|
|
12
12
|
```
|
|
13
13
|
|
|
14
|
+
For OpenCode, installing skills first also creates slash commands for every installed skill
|
|
15
|
+
marked `user-invocable: true`. If you initialize first, run `npx @martinffx/atelier@latest update --harness opencode`
|
|
16
|
+
after installing skills.
|
|
17
|
+
|
|
14
18
|
## How Atelier works
|
|
15
19
|
|
|
16
20
|
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
|
|
@@ -34216,7 +34403,7 @@ function removeDirIfEmpty3(dir) {
|
|
|
34216
34403
|
return;
|
|
34217
34404
|
}
|
|
34218
34405
|
try {
|
|
34219
|
-
if (
|
|
34406
|
+
if (readdirSync4(dir).length === 0) {
|
|
34220
34407
|
rmdirSync3(dir);
|
|
34221
34408
|
}
|
|
34222
34409
|
} catch (err2) {
|
|
@@ -34274,7 +34461,7 @@ function removeEmptyCodexDir(codexDir) {
|
|
|
34274
34461
|
return;
|
|
34275
34462
|
}
|
|
34276
34463
|
try {
|
|
34277
|
-
const entries =
|
|
34464
|
+
const entries = readdirSync4(codexDir);
|
|
34278
34465
|
if (entries.length === 0) {
|
|
34279
34466
|
rmSync4(codexDir, { recursive: true, force: true });
|
|
34280
34467
|
}
|
|
@@ -34287,7 +34474,7 @@ function isObject(value) {
|
|
|
34287
34474
|
}
|
|
34288
34475
|
|
|
34289
34476
|
// src/adapters/cursor.ts
|
|
34290
|
-
import { existsSync as existsSync6, mkdirSync as mkdirSync5, readdirSync as
|
|
34477
|
+
import { existsSync as existsSync6, mkdirSync as mkdirSync5, readdirSync as readdirSync5, rmSync as rmSync5, rmdirSync as rmdirSync4, writeFileSync as writeFileSync6 } from "fs";
|
|
34291
34478
|
import { join as join10 } from "path";
|
|
34292
34479
|
var CURSOR_MODELS = [
|
|
34293
34480
|
"composer-2.5",
|
|
@@ -34378,7 +34565,7 @@ function remove5(section, basePath) {
|
|
|
34378
34565
|
rmSync5(join10(agentsDir, `${agent.name}.md`), { force: true });
|
|
34379
34566
|
}
|
|
34380
34567
|
rmSync5(join10(agentsDir, `${LEGACY_AGENT_NAME}.md`), { force: true });
|
|
34381
|
-
if (existsSync6(agentsDir) &&
|
|
34568
|
+
if (existsSync6(agentsDir) && readdirSync5(agentsDir).length === 0) {
|
|
34382
34569
|
rmdirSync4(agentsDir);
|
|
34383
34570
|
}
|
|
34384
34571
|
} catch (err2) {
|
package/package.json
CHANGED
|
@@ -125,9 +125,10 @@ The workflow is not purely linear. Expect backflows:
|
|
|
125
125
|
- Planning reveals design assumptions are wrong → back to brainstorm
|
|
126
126
|
- Tasks can't be decomposed without more context → back to brainstorm
|
|
127
127
|
|
|
128
|
-
### Implement → Plan (
|
|
129
|
-
-
|
|
130
|
-
|
|
128
|
+
### Implement → Plan (material changes)
|
|
129
|
+
- A minor deviation may be recorded inline and reflected in the plan when it does not change
|
|
130
|
+
approved behavior, scope, architecture, public contracts, or major dependencies.
|
|
131
|
+
- Material missing work or an unplanned dependency → revise the active plan and get approval.
|
|
131
132
|
|
|
132
133
|
### Implement → Research (fundamental issue)
|
|
133
134
|
- Inline planning or implementation develops substantial needs → ask the human whether to
|
|
@@ -1,14 +1,15 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: atelier-setup
|
|
3
|
-
description: Configure a repository
|
|
3
|
+
description: Configure a repository for Atelier's development workflow. Use only when explicitly invoked; inspect existing guidance, issue-tracker and domain-document conventions, preview the proposed configuration, and write only after approval. It does not install Atelier or initialize external tooling.
|
|
4
4
|
user-invocable: true
|
|
5
|
+
disable-model-invocation: true
|
|
5
6
|
---
|
|
6
7
|
|
|
7
8
|
# Atelier Setup
|
|
8
9
|
|
|
9
|
-
Set up
|
|
10
|
-
|
|
11
|
-
|
|
10
|
+
Set up repository guidance that Atelier skills need without taking ownership of the developer's
|
|
11
|
+
installation, harness configuration, or project structure. Explore first, present every proposed
|
|
12
|
+
edit exactly, and write only after the developer approves it.
|
|
12
13
|
|
|
13
14
|
## 1. Explore
|
|
14
15
|
|
|
@@ -18,6 +19,9 @@ Read the repository before proposing any change:
|
|
|
18
19
|
heading.
|
|
19
20
|
- Root `CONTEXT.md` and `CONTEXT-MAP.md`.
|
|
20
21
|
- `docs/adr/` and any context-local `docs/adr/` directories.
|
|
22
|
+
- `docs/agents/`, including existing `issue-tracker.md` and `domain.md`.
|
|
23
|
+
- Existing issue-tracker conventions: remotes, issue links in documentation, local issue
|
|
24
|
+
directories, and project guidance.
|
|
21
25
|
- Genuine monorepo signals: a workspace manifest, or multiple independently deployable packages
|
|
22
26
|
with their own source directories.
|
|
23
27
|
|
|
@@ -33,9 +37,19 @@ Summarize what exists and recommend a domain-document layout:
|
|
|
33
37
|
contexts. `CONTEXT-MAP.md` belongs at the root and points to each context's `CONTEXT.md` and
|
|
34
38
|
`docs/adr/` directory.
|
|
35
39
|
|
|
36
|
-
Explain that
|
|
40
|
+
Explain that domain documents remain lazy: do not create `CONTEXT.md`, `CONTEXT-MAP.md`, or an ADR
|
|
37
41
|
directory until another Atelier skill has resolved terminology or made a decision worth recording.
|
|
38
42
|
|
|
43
|
+
Select or confirm the issue tracker:
|
|
44
|
+
|
|
45
|
+
1. If `docs/agents/issue-tracker.md` already defines one, preserve it unless the developer asks to
|
|
46
|
+
change it.
|
|
47
|
+
2. Otherwise recommend the tracker indicated by existing repository practice, then ask the
|
|
48
|
+
developer to confirm or describe their tracker.
|
|
49
|
+
3. Accept GitHub, GitLab, local Markdown, another tracker described by the developer, or `none`.
|
|
50
|
+
4. Record the chosen workflow in `docs/agents/issue-tracker.md`; do not install, initialize, or
|
|
51
|
+
create external tracker resources.
|
|
52
|
+
|
|
39
53
|
Select the instruction file to edit:
|
|
40
54
|
|
|
41
55
|
1. If both files exist, ask which file should own the agent guidance; do not duplicate it.
|
|
@@ -43,8 +57,13 @@ Select the instruction file to edit:
|
|
|
43
57
|
3. Otherwise use root `CLAUDE.md` when it exists.
|
|
44
58
|
4. If neither exists, ask which instruction file to create; do not choose on the developer's behalf.
|
|
45
59
|
|
|
46
|
-
Show the developer the selected file, domain layout, and
|
|
47
|
-
|
|
60
|
+
Show the developer the selected file, domain layout, and exact contents for:
|
|
61
|
+
|
|
62
|
+
- the `## Agent skills` block;
|
|
63
|
+
- `docs/agents/issue-tracker.md` using `references/issue-tracker.md`;
|
|
64
|
+
- `docs/agents/domain.md` using `references/domain.md`.
|
|
65
|
+
|
|
66
|
+
Stop and wait for explicit approval.
|
|
48
67
|
|
|
49
68
|
## 3. Write After Approval
|
|
50
69
|
|
|
@@ -58,23 +77,27 @@ Add the following block to the selected instruction file:
|
|
|
58
77
|
Use `atelier-orchestrator` at the start of development work. It selects an Inline Plan for bounded
|
|
59
78
|
changes or a Spec-backed Plan when durable design and coordination artifacts are warranted.
|
|
60
79
|
|
|
61
|
-
###
|
|
80
|
+
### Issue tracking
|
|
81
|
+
|
|
82
|
+
Read `docs/agents/issue-tracker.md` when issue tracking is relevant. It defines this repository's
|
|
83
|
+
tracker workflow; `plan.json` remains authoritative for Spec-backed task details and dependencies.
|
|
62
84
|
|
|
63
|
-
|
|
64
|
-
relevant context documentation. Otherwise, read root `CONTEXT.md` when present. Read relevant
|
|
65
|
-
architectural decisions under `docs/adr/` and any context-specific ADR directories.
|
|
85
|
+
### Domain documentation
|
|
66
86
|
|
|
67
|
-
|
|
68
|
-
|
|
87
|
+
Read `docs/agents/domain.md` before working in a domain area. It defines how to locate and use this
|
|
88
|
+
repository's context documents and ADRs.
|
|
69
89
|
```
|
|
70
90
|
|
|
71
|
-
If an `## Agent skills` section already exists, update the `### Planning and implementation
|
|
72
|
-
`### Domain documentation` subsections in place rather than appending
|
|
73
|
-
subsections and all content outside the section, including user edits.
|
|
74
|
-
|
|
91
|
+
If an `## Agent skills` section already exists, update the `### Planning and implementation`,
|
|
92
|
+
`### Issue tracking`, and `### Domain documentation` subsections in place rather than appending
|
|
93
|
+
duplicates. Preserve other subsections and all content outside the section, including user edits.
|
|
94
|
+
|
|
95
|
+
Create `docs/agents/` when needed, then write the approved tracker and domain documents. Do not
|
|
96
|
+
create any other files or directories.
|
|
75
97
|
|
|
76
98
|
## 4. Report
|
|
77
99
|
|
|
78
|
-
State which instruction file changed
|
|
79
|
-
that the layout is a convention only: the first
|
|
80
|
-
creates the relevant documents when it has
|
|
100
|
+
State which instruction file and agent documents changed, the selected tracker, and the chosen
|
|
101
|
+
domain-document layout. Remind the developer that the layout is a convention only: the first
|
|
102
|
+
domain-modelling or architectural-decision task creates the relevant documents when it has
|
|
103
|
+
substantive content.
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# Domain Documentation
|
|
2
|
+
|
|
3
|
+
## Layout
|
|
4
|
+
|
|
5
|
+
- **Mode:** [single-context or multi-context]
|
|
6
|
+
- **Context map:** [path or not used]
|
|
7
|
+
- **System ADRs:** [path]
|
|
8
|
+
- **Context ADRs:** [path pattern or not used]
|
|
9
|
+
|
|
10
|
+
## Before Domain Work
|
|
11
|
+
|
|
12
|
+
1. Read the context map when configured, then select the relevant context.
|
|
13
|
+
2. Read that context's `CONTEXT.md` when it exists.
|
|
14
|
+
3. Read ADRs relevant to the work.
|
|
15
|
+
4. Proceed silently when these documents do not yet exist.
|
|
16
|
+
|
|
17
|
+
## Ownership
|
|
18
|
+
|
|
19
|
+
- `CONTEXT.md` records domain language, distinctions, and business invariants.
|
|
20
|
+
- `CONTEXT-MAP.md` maps bounded contexts to their context documents and ADRs.
|
|
21
|
+
- ADRs record architectural decisions and trade-offs.
|
|
22
|
+
- This file only tells agents how to locate and consume those documents.
|