@martinffx/atelier 1.4.0 → 3.0.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 +38 -0
- package/README.md +95 -220
- package/agents/architect.md +6 -6
- package/agents/oracle.md +1 -1
- package/agents/{recon.md → sentinel.md} +3 -3
- package/dist/atelier.js +167 -26
- package/package.json +2 -1
- package/skills/atelier-orchestrator/SKILL.md +180 -0
- package/skills/atelier-setup/SKILL.md +80 -0
- package/skills/code-review/SKILL.md +2 -2
- package/skills/code-review/references/reviewers.md +3 -3
- package/skills/code-review/references/rq.md +9 -9
- package/skills/code-subagents/SKILL.md +44 -75
- package/skills/code-subagents/references/batch-reviewer-prompt.md +44 -0
- package/skills/code-subagents/references/implementor-prompt.md +10 -11
- package/skills/spec-brainstorm/SKILL.md +21 -16
- package/skills/spec-finish/SKILL.md +14 -14
- package/skills/spec-implement/SKILL.md +42 -42
- package/skills/spec-plan/SKILL.md +72 -26
- package/skills/spec-plan/references/plan_template.md +77 -0
- package/skills/code-subagents/references/code-quality-reviewer-prompt.md +0 -48
- package/skills/code-subagents/references/spec-reviewer-prompt.md +0 -62
- package/skills/python-architecture/SKILL.md +0 -148
- package/skills/python-architecture/references/data-modeling.md +0 -558
- package/skills/python-architecture/references/ddd.md +0 -569
- package/skills/python-architecture/references/functional-core.md +0 -337
- package/skills/python-build-tools/SKILL.md +0 -153
- package/skills/python-build-tools/references/basedpyright.md +0 -321
- package/skills/python-build-tools/references/ruff.md +0 -225
- package/skills/python-build-tools/references/uv.md +0 -257
- package/skills/python-fastapi/SKILL.md +0 -184
- package/skills/python-fastapi/references/api-design.md +0 -563
- package/skills/python-fastapi/references/dependencies.md +0 -479
- package/skills/python-fastapi/references/middleware.md +0 -475
- package/skills/python-fastapi/references/validation.md +0 -497
- package/skills/python-modern-python/SKILL.md +0 -201
- package/skills/python-modern-python/references/async.md +0 -76
- package/skills/python-modern-python/references/pattern-matching.md +0 -89
- package/skills/python-modern-python/references/typing.md +0 -96
- package/skills/python-monorepo/SKILL.md +0 -146
- package/skills/python-monorepo/references/docker.md +0 -515
- package/skills/python-monorepo/references/namespace-packages.md +0 -426
- package/skills/python-monorepo/references/workspace-config.md +0 -341
- package/skills/python-sqlalchemy/SKILL.md +0 -119
- package/skills/python-sqlalchemy/references/async.md +0 -51
- package/skills/python-sqlalchemy/references/models.md +0 -67
- package/skills/python-sqlalchemy/references/queries.md +0 -57
- package/skills/python-temporal/SKILL.md +0 -128
- package/skills/python-temporal/references/patterns.md +0 -87
- package/skills/python-temporal/references/testing.md +0 -54
- package/skills/python-testing/SKILL.md +0 -161
- package/skills/python-testing/references/boundaries.md +0 -449
- package/skills/python-testing/references/mocking.md +0 -449
- package/skills/python-testing/references/pytest.md +0 -524
- package/skills/spec-orchestrator/SKILL.md +0 -145
- package/skills/typescript-api-design/SKILL.md +0 -396
- package/skills/typescript-api-design/references/error-responses.md +0 -469
- package/skills/typescript-build-tools/SKILL.md +0 -447
- package/skills/typescript-build-tools/references/biome.md +0 -436
- package/skills/typescript-build-tools/references/bun.md +0 -432
- package/skills/typescript-build-tools/references/turborepo.md +0 -426
- package/skills/typescript-build-tools/references/vitest.md +0 -518
- package/skills/typescript-drizzle-orm/SKILL.md +0 -398
- package/skills/typescript-drizzle-orm/references/cloudflare.md +0 -392
- package/skills/typescript-drizzle-orm/references/entity-pattern.md +0 -412
- package/skills/typescript-drizzle-orm/references/postgresql.md +0 -191
- package/skills/typescript-drizzle-orm/references/repository-pattern.md +0 -479
- package/skills/typescript-drizzle-orm/references/sqlite.md +0 -291
- package/skills/typescript-dynamodb-toolbox/SKILL.md +0 -370
- package/skills/typescript-dynamodb-toolbox/references/entity-layer.md +0 -574
- package/skills/typescript-dynamodb-toolbox/references/error-handling.md +0 -495
- package/skills/typescript-dynamodb-toolbox/references/modeling.md +0 -332
- package/skills/typescript-dynamodb-toolbox/references/repository-layer.md +0 -631
- package/skills/typescript-dynamodb-toolbox/references/testing.md +0 -718
- package/skills/typescript-dynamodb-toolbox/references/transactions.md +0 -423
- package/skills/typescript-effect-ts/SKILL.md +0 -341
- package/skills/typescript-effect-ts/references/core.md +0 -412
- package/skills/typescript-effect-ts/references/error-handling.md +0 -468
- package/skills/typescript-effect-ts/references/resources.md +0 -476
- package/skills/typescript-effect-ts/references/schema.md +0 -536
- package/skills/typescript-effect-ts/references/services.md +0 -461
- package/skills/typescript-fastify/SKILL.md +0 -405
- package/skills/typescript-fastify/references/plugins.md +0 -329
- package/skills/typescript-fastify/references/typeid.md +0 -315
- package/skills/typescript-functional-patterns/SKILL.md +0 -402
- package/skills/typescript-functional-patterns/references/adts.md +0 -497
- package/skills/typescript-functional-patterns/references/branded-types.md +0 -542
- package/skills/typescript-functional-patterns/references/migration-guide.md +0 -695
- package/skills/typescript-functional-patterns/references/option-result.md +0 -585
- package/skills/typescript-testing/SKILL.md +0 -353
- package/skills/typescript-testing/references/mocking.md +0 -675
- package/skills/typescript-testing/references/msw.md +0 -852
- package/skills/typescript-testing/references/snapshot-testing.md +0 -774
package/dist/atelier.js
CHANGED
|
@@ -31996,10 +31996,13 @@ var coerce = {
|
|
|
31996
31996
|
};
|
|
31997
31997
|
var NEVER = INVALID;
|
|
31998
31998
|
// src/constants.ts
|
|
31999
|
-
var
|
|
32000
|
-
var AGENT_NAMES = ["
|
|
31999
|
+
var Harness = ["claude", "opencode", "codex", "cursor"];
|
|
32000
|
+
var AGENT_NAMES = ["sentinel", "oracle", "architect"];
|
|
32001
|
+
var LEGACY_AGENT_NAME = "recon";
|
|
32001
32002
|
|
|
32002
32003
|
// src/utils/errors.ts
|
|
32004
|
+
var harnessChoices = `${Harness.slice(0, -1).join(", ")}, or ${Harness.at(-1)}`;
|
|
32005
|
+
|
|
32003
32006
|
class AtelierError extends Error {
|
|
32004
32007
|
code;
|
|
32005
32008
|
exitCode;
|
|
@@ -32013,19 +32016,19 @@ class AtelierError extends Error {
|
|
|
32013
32016
|
|
|
32014
32017
|
class ConfigNotFoundError extends AtelierError {
|
|
32015
32018
|
constructor(command) {
|
|
32016
|
-
super(`.atelier/config.json not found. Run \`atelier ${command} --harness
|
|
32019
|
+
super(`.atelier/config.json not found. Run \`atelier ${command} --harness <${Harness.join("|")}>\` first.`, "CONFIG_NOT_FOUND", 1);
|
|
32017
32020
|
}
|
|
32018
32021
|
}
|
|
32019
32022
|
|
|
32020
32023
|
class HarnessRequiredError extends AtelierError {
|
|
32021
32024
|
constructor() {
|
|
32022
|
-
super(
|
|
32025
|
+
super(`\`--yes\` requires \`--harness\` (${harnessChoices}).`, "HARNESS_REQUIRED", 1);
|
|
32023
32026
|
}
|
|
32024
32027
|
}
|
|
32025
32028
|
|
|
32026
32029
|
class InvalidHarnessError extends AtelierError {
|
|
32027
32030
|
constructor(value) {
|
|
32028
|
-
super(`Invalid harness "${value}". Must be
|
|
32031
|
+
super(`Invalid harness "${value}". Must be ${harnessChoices}.`, "INVALID_HARNESS", 1);
|
|
32029
32032
|
}
|
|
32030
32033
|
}
|
|
32031
32034
|
class FileWriteError extends AtelierError {
|
|
@@ -32041,7 +32044,7 @@ class HarnessConfigError extends AtelierError {
|
|
|
32041
32044
|
}
|
|
32042
32045
|
class InvalidConfigError extends AtelierError {
|
|
32043
32046
|
constructor(cause, { suggestReinit = false } = {}) {
|
|
32044
|
-
const suffix = suggestReinit ?
|
|
32047
|
+
const suffix = suggestReinit ? ` Run \`atelier init --harness <${Harness.join("|")}>\` to reconfigure.` : "";
|
|
32045
32048
|
super(`Invalid configuration: ${cause}.${suffix}`, "INVALID_CONFIG", 1);
|
|
32046
32049
|
}
|
|
32047
32050
|
}
|
|
@@ -32131,18 +32134,46 @@ function readConfig(path2 = CONFIG_PATH) {
|
|
|
32131
32134
|
});
|
|
32132
32135
|
}
|
|
32133
32136
|
try {
|
|
32134
|
-
return ok(validateConfig(parsed));
|
|
32137
|
+
return ok(validateConfig(migrateLegacyAgentName(parsed)));
|
|
32135
32138
|
} catch (validationErr) {
|
|
32136
32139
|
const message = validationErr instanceof Error ? validationErr.message : String(validationErr);
|
|
32137
32140
|
return err({ type: "invalid", message });
|
|
32138
32141
|
}
|
|
32139
32142
|
}
|
|
32143
|
+
function migrateLegacyAgentName(config) {
|
|
32144
|
+
if (typeof config !== "object" || config === null) {
|
|
32145
|
+
return config;
|
|
32146
|
+
}
|
|
32147
|
+
const migrated = { ...config };
|
|
32148
|
+
for (const harness of Harness) {
|
|
32149
|
+
const section = migrated[harness];
|
|
32150
|
+
if (typeof section !== "object" || section === null || !Array.isArray(section.agents)) {
|
|
32151
|
+
continue;
|
|
32152
|
+
}
|
|
32153
|
+
const agents = section.agents;
|
|
32154
|
+
migrated[harness] = {
|
|
32155
|
+
...section,
|
|
32156
|
+
agents: agents.map((agent) => {
|
|
32157
|
+
if (typeof agent !== "object" || agent === null) {
|
|
32158
|
+
return agent;
|
|
32159
|
+
}
|
|
32160
|
+
const entry = agent;
|
|
32161
|
+
return {
|
|
32162
|
+
...entry,
|
|
32163
|
+
template: entry.template === LEGACY_AGENT_NAME ? "sentinel" : entry.template,
|
|
32164
|
+
name: entry.name === LEGACY_AGENT_NAME ? "sentinel" : entry.name
|
|
32165
|
+
};
|
|
32166
|
+
})
|
|
32167
|
+
};
|
|
32168
|
+
}
|
|
32169
|
+
return migrated;
|
|
32170
|
+
}
|
|
32140
32171
|
function isOldFlatConfig(config) {
|
|
32141
32172
|
if (typeof config !== "object" || config === null) {
|
|
32142
32173
|
return false;
|
|
32143
32174
|
}
|
|
32144
32175
|
const record = config;
|
|
32145
|
-
return typeof record.harness === "string" &&
|
|
32176
|
+
return typeof record.harness === "string" && Harness.every((harness) => record[harness] == null);
|
|
32146
32177
|
}
|
|
32147
32178
|
function writeConfig(config, path2 = CONFIG_PATH) {
|
|
32148
32179
|
const dir = dirname(path2);
|
|
@@ -32153,7 +32184,7 @@ function writeConfig(config, path2 = CONFIG_PATH) {
|
|
|
32153
32184
|
`);
|
|
32154
32185
|
}
|
|
32155
32186
|
function getConfiguredHarnesses(config) {
|
|
32156
|
-
return
|
|
32187
|
+
return Harness.filter((harness) => config[harness] !== undefined);
|
|
32157
32188
|
}
|
|
32158
32189
|
function removeConfigDir(path2 = CONFIG_PATH) {
|
|
32159
32190
|
const dir = dirname(path2);
|
|
@@ -32292,7 +32323,7 @@ async function init(options2) {
|
|
|
32292
32323
|
}
|
|
32293
32324
|
let harness;
|
|
32294
32325
|
if (harnessOption) {
|
|
32295
|
-
if (!
|
|
32326
|
+
if (!Harness.includes(harnessOption)) {
|
|
32296
32327
|
throw new InvalidHarnessError(harnessOption);
|
|
32297
32328
|
}
|
|
32298
32329
|
harness = harnessOption;
|
|
@@ -32302,7 +32333,7 @@ async function init(options2) {
|
|
|
32302
32333
|
type: "list",
|
|
32303
32334
|
name: "harness",
|
|
32304
32335
|
message: "Which harness are you using?",
|
|
32305
|
-
choices:
|
|
32336
|
+
choices: Harness
|
|
32306
32337
|
}
|
|
32307
32338
|
]);
|
|
32308
32339
|
harness = answer.harness;
|
|
@@ -32348,7 +32379,7 @@ Files to write:`);
|
|
|
32348
32379
|
Atelier initialized for ${harness}.`);
|
|
32349
32380
|
console.log(`
|
|
32350
32381
|
Next step:`);
|
|
32351
|
-
if (harness === "opencode" && section.provider === "openai") {
|
|
32382
|
+
if (harness === "opencode" && "provider" in section && section.provider === "openai") {
|
|
32352
32383
|
console.log(" opencode auth login --provider openai # connect your OpenAI account");
|
|
32353
32384
|
}
|
|
32354
32385
|
console.log(" npx skills add martinffx/atelier # install skills");
|
|
@@ -32361,7 +32392,7 @@ import { homedir as homedir4 } from "os";
|
|
|
32361
32392
|
// src/commands/utils.ts
|
|
32362
32393
|
async function resolveHarness(config, harnessOption, command) {
|
|
32363
32394
|
if (harnessOption) {
|
|
32364
|
-
if (!
|
|
32395
|
+
if (!Harness.includes(harnessOption)) {
|
|
32365
32396
|
throw new InvalidHarnessError(harnessOption);
|
|
32366
32397
|
}
|
|
32367
32398
|
const configured2 = getConfiguredHarnesses(config);
|
|
@@ -32493,6 +32524,9 @@ var SimpleConfigSchema = exports_external.object({
|
|
|
32493
32524
|
default_model: exports_external.string().min(1),
|
|
32494
32525
|
agents: exports_external.array(AgentSchema).min(1)
|
|
32495
32526
|
});
|
|
32527
|
+
var CursorConfigSchema = exports_external.object({
|
|
32528
|
+
agents: exports_external.array(AgentSchema).min(1)
|
|
32529
|
+
}).strict();
|
|
32496
32530
|
var OPENCODE_PROVIDERS = ["opencode-zen", "opencode-go", "amazon-bedrock", "openai"];
|
|
32497
32531
|
var OpenCodeConfigSchema = exports_external.object({
|
|
32498
32532
|
provider: exports_external.enum(OPENCODE_PROVIDERS),
|
|
@@ -32505,7 +32539,7 @@ var OpenCodeConfigSchema = exports_external.object({
|
|
|
32505
32539
|
var ANTHROPIC_MODELS = ["haiku", "sonnet", "opus", "opusplan"];
|
|
32506
32540
|
var DEFAULT_MODELS = {
|
|
32507
32541
|
default_model: "opusplan",
|
|
32508
|
-
|
|
32542
|
+
sentinel: "haiku",
|
|
32509
32543
|
oracle: "opus",
|
|
32510
32544
|
architect: "opus"
|
|
32511
32545
|
};
|
|
@@ -32545,6 +32579,7 @@ function installAgents(section, basePath) {
|
|
|
32545
32579
|
} catch (err2) {
|
|
32546
32580
|
throw new FileWriteError(agentsDir, err2 instanceof Error ? err2.message : String(err2));
|
|
32547
32581
|
}
|
|
32582
|
+
rmSync2(join7(agentsDir, `${LEGACY_AGENT_NAME}.md`), { force: true });
|
|
32548
32583
|
for (const agent of config.agents) {
|
|
32549
32584
|
const template = readTemplate(agent.template);
|
|
32550
32585
|
const frontmatter = `---
|
|
@@ -32599,6 +32634,7 @@ function remove2(section, basePath) {
|
|
|
32599
32634
|
rmSync2(file, { force: true });
|
|
32600
32635
|
}
|
|
32601
32636
|
}
|
|
32637
|
+
rmSync2(join7(agentsDir, `${LEGACY_AGENT_NAME}.md`), { force: true });
|
|
32602
32638
|
removeDirIfEmpty(agentsDir);
|
|
32603
32639
|
removeAtelierSettings(claudeDir);
|
|
32604
32640
|
}
|
|
@@ -32717,28 +32753,28 @@ var PROVIDER_MODELS = {
|
|
|
32717
32753
|
};
|
|
32718
32754
|
var DEFAULT_MODELS2 = {
|
|
32719
32755
|
"opencode-zen": {
|
|
32720
|
-
|
|
32756
|
+
sentinel: "opencode/minimax-m2.7",
|
|
32721
32757
|
oracle: "opencode/kimi-k2.6",
|
|
32722
32758
|
architect: "opencode/deepseek-v4-pro",
|
|
32723
32759
|
build: "opencode/deepseek-v4-flash",
|
|
32724
32760
|
plan: "opencode/deepseek-v4-pro"
|
|
32725
32761
|
},
|
|
32726
32762
|
"opencode-go": {
|
|
32727
|
-
|
|
32763
|
+
sentinel: "opencode-go/minimax-m2.7",
|
|
32728
32764
|
oracle: "opencode-go/kimi-k2.6",
|
|
32729
32765
|
architect: "opencode-go/deepseek-v4-pro",
|
|
32730
32766
|
build: "opencode-go/deepseek-v4-flash",
|
|
32731
32767
|
plan: "opencode-go/deepseek-v4-pro"
|
|
32732
32768
|
},
|
|
32733
32769
|
"amazon-bedrock": {
|
|
32734
|
-
|
|
32770
|
+
sentinel: "amazon-bedrock/anthropic-claude-haiku-4-5",
|
|
32735
32771
|
oracle: "amazon-bedrock/anthropic-claude-opus-4-7",
|
|
32736
32772
|
architect: "amazon-bedrock/anthropic-claude-opus-4-7",
|
|
32737
32773
|
build: "amazon-bedrock/anthropic-claude-sonnet-4-5",
|
|
32738
32774
|
plan: "amazon-bedrock/anthropic-claude-haiku-4-5"
|
|
32739
32775
|
},
|
|
32740
32776
|
openai: {
|
|
32741
|
-
|
|
32777
|
+
sentinel: "openai/gpt-5.6-luna",
|
|
32742
32778
|
oracle: "openai/gpt-5.6-sol",
|
|
32743
32779
|
architect: "openai/gpt-5.6-sol",
|
|
32744
32780
|
build: "openai/gpt-5.6-terra",
|
|
@@ -32790,6 +32826,7 @@ function installAgents2(section, basePath) {
|
|
|
32790
32826
|
} catch (err2) {
|
|
32791
32827
|
throw new FileWriteError(agentsDir, err2 instanceof Error ? err2.message : String(err2));
|
|
32792
32828
|
}
|
|
32829
|
+
rmSync3(join8(agentsDir, `${LEGACY_AGENT_NAME}.md`), { force: true });
|
|
32793
32830
|
for (const agent of config.agents) {
|
|
32794
32831
|
const template = readTemplate(agent.template);
|
|
32795
32832
|
const frontmatter = `---
|
|
@@ -32816,13 +32853,13 @@ function mergeHarnessConfig2(section, basePath) {
|
|
|
32816
32853
|
} catch (err2) {
|
|
32817
32854
|
throw new FileWriteError(basePath, err2 instanceof Error ? err2.message : String(err2));
|
|
32818
32855
|
}
|
|
32819
|
-
const
|
|
32856
|
+
const sentinel = config.agents.find((a) => a.name === "sentinel");
|
|
32820
32857
|
const architect = config.agents.find((a) => a.name === "architect");
|
|
32821
32858
|
const atelierFields = {
|
|
32822
32859
|
agent: {
|
|
32823
32860
|
build: {
|
|
32824
32861
|
mode: "primary",
|
|
32825
|
-
model: config.build_model ||
|
|
32862
|
+
model: config.build_model || sentinel?.model || DEFAULT_MODELS2[config.provider].build
|
|
32826
32863
|
},
|
|
32827
32864
|
plan: {
|
|
32828
32865
|
mode: "primary",
|
|
@@ -32874,6 +32911,7 @@ function removeAgentFiles(opencodeRoot, config) {
|
|
|
32874
32911
|
rmSync3(file, { force: true });
|
|
32875
32912
|
}
|
|
32876
32913
|
}
|
|
32914
|
+
rmSync3(join8(agentsDir, `${LEGACY_AGENT_NAME}.md`), { force: true });
|
|
32877
32915
|
removeDirIfEmpty2(agentsDir);
|
|
32878
32916
|
}
|
|
32879
32917
|
function removeDirIfEmpty2(dir) {
|
|
@@ -34045,7 +34083,7 @@ var OPENAI_MODELS = [
|
|
|
34045
34083
|
];
|
|
34046
34084
|
var DEFAULT_MODELS3 = {
|
|
34047
34085
|
default_model: "gpt-5.6-terra",
|
|
34048
|
-
|
|
34086
|
+
sentinel: "gpt-5.6-luna",
|
|
34049
34087
|
oracle: "gpt-5.6-sol",
|
|
34050
34088
|
architect: "gpt-5.6-sol"
|
|
34051
34089
|
};
|
|
@@ -34098,6 +34136,7 @@ function installAgents3(section, basePath) {
|
|
|
34098
34136
|
} catch (err2) {
|
|
34099
34137
|
throw new FileWriteError(agentsDir, err2 instanceof Error ? err2.message : String(err2));
|
|
34100
34138
|
}
|
|
34139
|
+
rmSync4(join9(agentsDir, `${LEGACY_AGENT_NAME}.toml`), { force: true });
|
|
34101
34140
|
for (const agent of config.agents) {
|
|
34102
34141
|
const template = readTemplate(agent.template);
|
|
34103
34142
|
const content = {
|
|
@@ -34167,6 +34206,7 @@ function remove4(section, basePath) {
|
|
|
34167
34206
|
rmSync4(file, { force: true });
|
|
34168
34207
|
}
|
|
34169
34208
|
}
|
|
34209
|
+
rmSync4(join9(agentsDir, `${LEGACY_AGENT_NAME}.toml`), { force: true });
|
|
34170
34210
|
removeDirIfEmpty3(agentsDir);
|
|
34171
34211
|
cleanCodexConfig(codexDir);
|
|
34172
34212
|
removeEmptyCodexDir(codexDir);
|
|
@@ -34246,22 +34286,123 @@ function isObject(value) {
|
|
|
34246
34286
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
34247
34287
|
}
|
|
34248
34288
|
|
|
34289
|
+
// src/adapters/cursor.ts
|
|
34290
|
+
import { existsSync as existsSync6, mkdirSync as mkdirSync5, readdirSync as readdirSync4, rmSync as rmSync5, rmdirSync as rmdirSync4, writeFileSync as writeFileSync6 } from "fs";
|
|
34291
|
+
import { join as join10 } from "path";
|
|
34292
|
+
var CURSOR_MODELS = [
|
|
34293
|
+
"composer-2.5",
|
|
34294
|
+
"cursor-grok-4.5-high",
|
|
34295
|
+
"gpt-5.6-luna-medium",
|
|
34296
|
+
"gpt-5.6-terra-medium",
|
|
34297
|
+
"gpt-5.6-sol-medium",
|
|
34298
|
+
"claude-opus-4-8-high",
|
|
34299
|
+
"claude-sonnet-5-high",
|
|
34300
|
+
"kimi-k2.7-code",
|
|
34301
|
+
"glm-5.2-high"
|
|
34302
|
+
];
|
|
34303
|
+
var DEFAULT_MODELS4 = {
|
|
34304
|
+
sentinel: "composer-2.5",
|
|
34305
|
+
oracle: "claude-opus-4-8-high",
|
|
34306
|
+
architect: "gpt-5.6-sol-medium"
|
|
34307
|
+
};
|
|
34308
|
+
var cursorAdapter = {
|
|
34309
|
+
name: "cursor",
|
|
34310
|
+
configSchema: CursorConfigSchema,
|
|
34311
|
+
defaultSection: defaultSection4,
|
|
34312
|
+
modelsForProvider: modelsForProvider4,
|
|
34313
|
+
promptSection: promptSection4,
|
|
34314
|
+
installAgents: installAgents4,
|
|
34315
|
+
mergeHarnessConfig: mergeHarnessConfig4,
|
|
34316
|
+
fileList: fileList4,
|
|
34317
|
+
remove: remove5
|
|
34318
|
+
};
|
|
34319
|
+
function defaultSection4() {
|
|
34320
|
+
return {
|
|
34321
|
+
agents: AGENT_NAMES.map((name) => ({ template: name, name, model: DEFAULT_MODELS4[name] }))
|
|
34322
|
+
};
|
|
34323
|
+
}
|
|
34324
|
+
function modelsForProvider4(_provider) {
|
|
34325
|
+
return CURSOR_MODELS;
|
|
34326
|
+
}
|
|
34327
|
+
async function promptSection4(prompt2, section) {
|
|
34328
|
+
const config = section;
|
|
34329
|
+
const answers = await prompt2.prompt(AGENT_NAMES.map((name) => ({
|
|
34330
|
+
type: "list",
|
|
34331
|
+
name,
|
|
34332
|
+
message: `Select model for ${name}`,
|
|
34333
|
+
choices: CURSOR_MODELS,
|
|
34334
|
+
default: config.agents.find((agent) => agent.name === name)?.model ?? DEFAULT_MODELS4[name]
|
|
34335
|
+
})));
|
|
34336
|
+
return {
|
|
34337
|
+
agents: AGENT_NAMES.map((name) => ({
|
|
34338
|
+
template: name,
|
|
34339
|
+
name,
|
|
34340
|
+
model: answers[name] ?? DEFAULT_MODELS4[name]
|
|
34341
|
+
}))
|
|
34342
|
+
};
|
|
34343
|
+
}
|
|
34344
|
+
function installAgents4(section, basePath) {
|
|
34345
|
+
const config = section;
|
|
34346
|
+
const agentsDir = join10(basePath, ".cursor", "agents");
|
|
34347
|
+
try {
|
|
34348
|
+
mkdirSync5(agentsDir, { recursive: true });
|
|
34349
|
+
} catch (err2) {
|
|
34350
|
+
throw new FileWriteError(agentsDir, err2 instanceof Error ? err2.message : String(err2));
|
|
34351
|
+
}
|
|
34352
|
+
rmSync5(join10(agentsDir, `${LEGACY_AGENT_NAME}.md`), { force: true });
|
|
34353
|
+
for (const agent of config.agents) {
|
|
34354
|
+
const template = readTemplate(agent.template);
|
|
34355
|
+
const content = `---
|
|
34356
|
+
name: ${agent.name}
|
|
34357
|
+
description: ${template.description}
|
|
34358
|
+
model: ${agent.model}
|
|
34359
|
+
---
|
|
34360
|
+
${template.body}`;
|
|
34361
|
+
const agentPath = join10(agentsDir, `${agent.name}.md`);
|
|
34362
|
+
writeFileSync6(agentPath, content);
|
|
34363
|
+
console.log(`Created ${shortPath(agentPath)}`);
|
|
34364
|
+
}
|
|
34365
|
+
}
|
|
34366
|
+
function mergeHarnessConfig4(_section, _basePath) {}
|
|
34367
|
+
function fileList4(basePath) {
|
|
34368
|
+
return AGENT_NAMES.map((name) => {
|
|
34369
|
+
const path2 = join10(basePath, ".cursor", "agents", `${name}.md`);
|
|
34370
|
+
return { path: path2, exists: existsSync6(path2) };
|
|
34371
|
+
});
|
|
34372
|
+
}
|
|
34373
|
+
function remove5(section, basePath) {
|
|
34374
|
+
const config = section;
|
|
34375
|
+
const agentsDir = join10(basePath, ".cursor", "agents");
|
|
34376
|
+
try {
|
|
34377
|
+
for (const agent of config.agents) {
|
|
34378
|
+
rmSync5(join10(agentsDir, `${agent.name}.md`), { force: true });
|
|
34379
|
+
}
|
|
34380
|
+
rmSync5(join10(agentsDir, `${LEGACY_AGENT_NAME}.md`), { force: true });
|
|
34381
|
+
if (existsSync6(agentsDir) && readdirSync4(agentsDir).length === 0) {
|
|
34382
|
+
rmdirSync4(agentsDir);
|
|
34383
|
+
}
|
|
34384
|
+
} catch (err2) {
|
|
34385
|
+
throw new FileWriteError(agentsDir, err2 instanceof Error ? err2.message : String(err2));
|
|
34386
|
+
}
|
|
34387
|
+
}
|
|
34388
|
+
|
|
34249
34389
|
// src/adapters/index.ts
|
|
34250
34390
|
registerAdapter(claudeAdapter);
|
|
34251
34391
|
registerAdapter(opencodeAdapter);
|
|
34252
34392
|
registerAdapter(codexAdapter);
|
|
34393
|
+
registerAdapter(cursorAdapter);
|
|
34253
34394
|
|
|
34254
34395
|
// src/atelier.ts
|
|
34255
|
-
var
|
|
34396
|
+
var harnessChoices2 = Harness.join(", ");
|
|
34256
34397
|
var program2 = new Command;
|
|
34257
|
-
program2.name("atelier").description("Atelier CLI - configure Claude Code, OpenCode, and
|
|
34258
|
-
program2.command("init", { isDefault: true }).description("Initialize atelier globally").option(`--harness <type>`, `Harness type (${
|
|
34398
|
+
program2.name("atelier").description("Atelier CLI - configure Claude Code, OpenCode, Codex, and Cursor for spec-driven development").version("0.1.0");
|
|
34399
|
+
program2.command("init", { isDefault: true }).description("Initialize atelier globally").option(`--harness <type>`, `Harness type (${harnessChoices2})`).option("--yes", "Non-interactive mode with defaults").action((options2) => {
|
|
34259
34400
|
init(options2).catch(handleError);
|
|
34260
34401
|
});
|
|
34261
|
-
program2.command("update").description("Update atelier agents").option(`--harness <type>`, `Harness type (${
|
|
34402
|
+
program2.command("update").description("Update atelier agents").option(`--harness <type>`, `Harness type (${harnessChoices2})`).action((options2) => {
|
|
34262
34403
|
update(options2).catch(handleError);
|
|
34263
34404
|
});
|
|
34264
|
-
program2.command("remove").description("Remove a configured harness from atelier").option(`--harness <type>`, `Harness type (${
|
|
34405
|
+
program2.command("remove").description("Remove a configured harness from atelier").option(`--harness <type>`, `Harness type (${harnessChoices2})`).action((options2) => {
|
|
34265
34406
|
remove(options2).catch(handleError);
|
|
34266
34407
|
});
|
|
34267
34408
|
program2.parse();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@martinffx/atelier",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "3.0.0",
|
|
4
4
|
"description": "Personal development toolkit - spec-driven development, code quality, deep thinking, and ecosystem patterns",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
"claude-code",
|
|
13
13
|
"opencode",
|
|
14
14
|
"codex",
|
|
15
|
+
"cursor",
|
|
15
16
|
"ai-agents",
|
|
16
17
|
"spec-driven-development"
|
|
17
18
|
],
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: atelier-orchestrator
|
|
3
|
+
description: >
|
|
4
|
+
Skill routing and workflow orchestration. Selects Inline Plan or Spec-backed Plan, routes
|
|
5
|
+
to the correct workflow skill, and manages transitions between phases. Use when starting
|
|
6
|
+
any conversation or task to determine which planning mode and skill apply.
|
|
7
|
+
user-invocable: false
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
<EXTREMELY-IMPORTANT>
|
|
11
|
+
If you think there is even a 1% chance a skill might apply to what you are doing, you ABSOLUTELY
|
|
12
|
+
MUST invoke the skill.
|
|
13
|
+
|
|
14
|
+
IF A SKILL APPLIES TO YOUR TASK, YOU DO NOT HAVE A CHOICE. YOU MUST USE IT.
|
|
15
|
+
|
|
16
|
+
This is not negotiable. This is not optional. You cannot rationalize your way out of this.
|
|
17
|
+
</EXTREMELY-IMPORTANT>
|
|
18
|
+
|
|
19
|
+
# Planning Workflow
|
|
20
|
+
|
|
21
|
+
You have skills. You MUST use them. Not "should." Not "when convenient." MUST.
|
|
22
|
+
|
|
23
|
+
Invoke relevant skills BEFORE any response or action. Even a 1% chance a skill might apply
|
|
24
|
+
means you invoke it. If an invoked skill turns out to be wrong for the situation, you don't
|
|
25
|
+
need to follow it. But you must check.
|
|
26
|
+
|
|
27
|
+
## The One Rule
|
|
28
|
+
|
|
29
|
+
**When an implementation choice exists, never write code until the human has reviewed and
|
|
30
|
+
approved a written plan.** Exact mechanical changes may use the exception below.
|
|
31
|
+
|
|
32
|
+
The plan may be inline in the conversation or backed by persisted spec artifacts. Select
|
|
33
|
+
the mode before invoking an artifact-producing skill, state the choice with one brief
|
|
34
|
+
reason, and let the human override it.
|
|
35
|
+
|
|
36
|
+
## Planning Modes
|
|
37
|
+
|
|
38
|
+
### Inline Plan (default)
|
|
39
|
+
|
|
40
|
+
Use for bounded, well-understood work, including ordinary features, bug fixes, refactors,
|
|
41
|
+
configuration changes, and multi-file changes. Route to **spec-plan** with Inline mode
|
|
42
|
+
explicitly selected. It presents a concise plan in conversation, creates no planning artifacts
|
|
43
|
+
or tracker entries, and stops for approval. After approval, implement the plan directly; do
|
|
44
|
+
not invoke `spec-implement`, `spec-finish`, or `code-subagents`.
|
|
45
|
+
|
|
46
|
+
Direct Inline implementation must:
|
|
47
|
+
|
|
48
|
+
- Avoid main/master unless the human explicitly permits it
|
|
49
|
+
- Stay within the approved scope and files
|
|
50
|
+
- Run every validation item in the approved plan
|
|
51
|
+
- Review the final diff for unintended changes
|
|
52
|
+
- Do not commit, push, or open a PR without explicit human instruction
|
|
53
|
+
|
|
54
|
+
When in doubt, choose Inline Plan. File count and estimated duration alone do not make work
|
|
55
|
+
spec-worthy.
|
|
56
|
+
|
|
57
|
+
### Spec-backed Plan
|
|
58
|
+
|
|
59
|
+
Use when at least one concrete signal exists:
|
|
60
|
+
|
|
61
|
+
- The human explicitly requests a spec or durable planning artifacts
|
|
62
|
+
- Requirements or boundaries need discovery before planning
|
|
63
|
+
- The work materially changes architecture, public contracts, data models, or cross-system behavior
|
|
64
|
+
- Multiple dependent subsystems need decomposition and dependency tracking
|
|
65
|
+
- The plan must survive the conversation for handoff, audit, or multi-person coordination
|
|
66
|
+
|
|
67
|
+
Route through **spec-brainstorm**. This mode produces:
|
|
68
|
+
|
|
69
|
+
```
|
|
70
|
+
design.md ← spec-brainstorm (requirements + research + architecture)
|
|
71
|
+
plan.json ← spec-plan (tasks, dependencies)
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
Do not infer substantial work from size alone. If no concrete signal applies, use Inline Plan.
|
|
75
|
+
|
|
76
|
+
## Skill Routing
|
|
77
|
+
|
|
78
|
+
```
|
|
79
|
+
atelier-orchestrator → Select and announce planning mode
|
|
80
|
+
spec-brainstorm → Spec-backed discovery + design → design.md
|
|
81
|
+
spec-plan → Inline Plan or Spec-backed Plan → approval
|
|
82
|
+
spec-implement → Execute approved Spec-backed Plans; track tasks
|
|
83
|
+
spec-finish → Post-implementation validation for Spec-backed Plans
|
|
84
|
+
code-subagents → Parallel dispatch for Spec-backed Plan tasks
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
### Ordinary bounded work
|
|
88
|
+
|
|
89
|
+
```
|
|
90
|
+
spec-plan (Inline mode) → approval → implement directly
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
### Substantial work or explicit spec request
|
|
94
|
+
|
|
95
|
+
```
|
|
96
|
+
spec-brainstorm → spec-plan (Spec-backed Plan) → approval → spec-implement → spec-finish
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
### Truly mechanical change
|
|
100
|
+
|
|
101
|
+
A typo or exact single-line replacement may skip planning when there is no implementation
|
|
102
|
+
choice to review. If any choice exists, use an Inline Plan.
|
|
103
|
+
|
|
104
|
+
## Hard Transitions
|
|
105
|
+
|
|
106
|
+
| After completing... | Next step |
|
|
107
|
+
|---------------------|-----------|
|
|
108
|
+
| spec-brainstorm | spec-plan in Spec-backed mode |
|
|
109
|
+
| approved Inline Plan | implement the approved plan directly |
|
|
110
|
+
| approved Spec-backed Plan | spec-implement in Spec-backed mode |
|
|
111
|
+
| spec-implement | spec-finish |
|
|
112
|
+
|
|
113
|
+
Do not begin implementation without an approved written plan. Spec-backed work must not
|
|
114
|
+
jump from research to implementation.
|
|
115
|
+
|
|
116
|
+
## Iteration Patterns
|
|
117
|
+
|
|
118
|
+
The workflow is not purely linear. Expect backflows:
|
|
119
|
+
|
|
120
|
+
### Research → Research (discovery loop)
|
|
121
|
+
- Brainstorm reveals new requirements → loop back to discovery phase
|
|
122
|
+
- Human adds new scope mid-brainstorm → continue discovery
|
|
123
|
+
|
|
124
|
+
### Plan → Research (design flaw)
|
|
125
|
+
- Planning reveals design assumptions are wrong → back to brainstorm
|
|
126
|
+
- Tasks can't be decomposed without more context → back to brainstorm
|
|
127
|
+
|
|
128
|
+
### Implement → Plan (missing tasks)
|
|
129
|
+
- Implementation reveals missing work → revise the active plan and get approval
|
|
130
|
+
- Spec-backed work is blocked on an unplanned dependency → update plan.json
|
|
131
|
+
|
|
132
|
+
### Implement → Research (fundamental issue)
|
|
133
|
+
- Inline planning or implementation develops substantial needs → ask the human whether to
|
|
134
|
+
switch to a Spec-backed Plan
|
|
135
|
+
- Implementation reveals spec-backed design is fundamentally wrong → back to brainstorm
|
|
136
|
+
|
|
137
|
+
### Finish → Implement (bugs found)
|
|
138
|
+
- Validation finds bugs → back to implement
|
|
139
|
+
- Tests failing → back to implement
|
|
140
|
+
|
|
141
|
+
### When to escalate
|
|
142
|
+
If you loop 2+ times on the same issue, stop and ask the human:
|
|
143
|
+
|
|
144
|
+
> "We've looped on [issue] twice. Should we reconsider the approach?"
|
|
145
|
+
|
|
146
|
+
## Red Flags — You Are Rationalizing
|
|
147
|
+
|
|
148
|
+
| What you're thinking | Why it's wrong |
|
|
149
|
+
|----------------------|----------------|
|
|
150
|
+
| "This is too simple for a persisted spec" | Use an Inline Plan instead |
|
|
151
|
+
| "I already know how to do this" | Knowing how ≠ having the human's approval for how |
|
|
152
|
+
| "The human seems impatient" | Wasting time on wrong code is worse than planning |
|
|
153
|
+
| "I'll just do a quick prototype" | Prototypes become production. Plan it. |
|
|
154
|
+
| "I need to explore the code first" | Gather enough context for an Inline Plan; use brainstorm only when discovery is substantial |
|
|
155
|
+
| "Let me just fix this one thing" | One thing becomes three. Plan it. |
|
|
156
|
+
| "I can plan in my head" | Plans in your head can't be reviewed or annotated |
|
|
157
|
+
| "This is just a refactor" | Refactors still need at least an Inline Plan |
|
|
158
|
+
| "I'll write the plan after" | Post-hoc plans are fiction. Plan before. |
|
|
159
|
+
| "I need more context first" | Skills tell you HOW to gather context. Check first. |
|
|
160
|
+
| "The spec workflow is overkill" | It may be. Default to an Inline Plan unless substantial-work signals exist. |
|
|
161
|
+
| "I know what that skill says" | Skills evolve. Read current version. Invoke it. |
|
|
162
|
+
| "This feels productive" | Undisciplined action wastes time. Skills prevent this. |
|
|
163
|
+
|
|
164
|
+
## Skill Types
|
|
165
|
+
|
|
166
|
+
**Process skills** (spec-brainstorm, spec-plan, spec-implement, spec-finish): Follow exactly.
|
|
167
|
+
Don't adapt away discipline.
|
|
168
|
+
|
|
169
|
+
**Knowledge skills** (oracle-grill-me, oracle-domain-modelling): Adapt principles to
|
|
170
|
+
context. These inform decisions within the workflow.
|
|
171
|
+
|
|
172
|
+
**Discipline skills** (oracle-debug): Strict methodology that must be followed
|
|
173
|
+
exactly — no adaptation that bypasses root-cause investigation.
|
|
174
|
+
|
|
175
|
+
Process skills come first. Knowledge skills get invoked by process skills when needed.
|
|
176
|
+
|
|
177
|
+
## User Instructions
|
|
178
|
+
|
|
179
|
+
"Add X" or "Fix Y" doesn't mean skip workflows. Instructions say WHAT, not HOW.
|
|
180
|
+
The skills define HOW.
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: atelier-setup
|
|
3
|
+
description: Configure a repository's generic agent guidance for Atelier's development workflow. Use only when the user explicitly invokes atelier-setup; it inspects existing repository instructions and domain-document conventions, previews an idempotent project-guidance update, and writes only after approval. It does not install Atelier, configure a harness, or scaffold empty workflow artifacts.
|
|
4
|
+
user-invocable: true
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Atelier Setup
|
|
8
|
+
|
|
9
|
+
Set up generic repository guidance that Atelier skills need without taking ownership of the
|
|
10
|
+
developer's installation, harness configuration, or project structure. Explore first, present
|
|
11
|
+
the proposed edit exactly, and write only after the developer approves it.
|
|
12
|
+
|
|
13
|
+
## 1. Explore
|
|
14
|
+
|
|
15
|
+
Read the repository before proposing any change:
|
|
16
|
+
|
|
17
|
+
- Root `AGENTS.md` and `CLAUDE.md`, if present. Note whether either already has an `## Agent skills`
|
|
18
|
+
heading.
|
|
19
|
+
- Root `CONTEXT.md` and `CONTEXT-MAP.md`.
|
|
20
|
+
- `docs/adr/` and any context-local `docs/adr/` directories.
|
|
21
|
+
- Genuine monorepo signals: a workspace manifest, or multiple independently deployable packages
|
|
22
|
+
with their own source directories.
|
|
23
|
+
|
|
24
|
+
Do not create, install, configure, or modify anything during exploration.
|
|
25
|
+
|
|
26
|
+
## 2. Present Findings
|
|
27
|
+
|
|
28
|
+
Summarize what exists and recommend a domain-document layout:
|
|
29
|
+
|
|
30
|
+
- **Single-context** is the default: domain language belongs in root `CONTEXT.md`; architectural
|
|
31
|
+
decisions belong in root `docs/adr/`.
|
|
32
|
+
- **Multi-context** is appropriate only when the repository has genuinely separate bounded
|
|
33
|
+
contexts. `CONTEXT-MAP.md` belongs at the root and points to each context's `CONTEXT.md` and
|
|
34
|
+
`docs/adr/` directory.
|
|
35
|
+
|
|
36
|
+
Explain that these documents remain lazy: do not create `CONTEXT.md`, `CONTEXT-MAP.md`, or an ADR
|
|
37
|
+
directory until another Atelier skill has resolved terminology or made a decision worth recording.
|
|
38
|
+
|
|
39
|
+
Select the instruction file to edit:
|
|
40
|
+
|
|
41
|
+
1. If both files exist, ask which file should own the agent guidance; do not duplicate it.
|
|
42
|
+
2. Otherwise use root `AGENTS.md` when it exists.
|
|
43
|
+
3. Otherwise use root `CLAUDE.md` when it exists.
|
|
44
|
+
4. If neither exists, ask which instruction file to create; do not choose on the developer's behalf.
|
|
45
|
+
|
|
46
|
+
Show the developer the selected file, domain layout, and the exact `## Agent skills` block that will be
|
|
47
|
+
written. Stop and wait for explicit approval.
|
|
48
|
+
|
|
49
|
+
## 3. Write After Approval
|
|
50
|
+
|
|
51
|
+
Add the following block to the selected instruction file:
|
|
52
|
+
|
|
53
|
+
```markdown
|
|
54
|
+
## Agent skills
|
|
55
|
+
|
|
56
|
+
### Planning and implementation
|
|
57
|
+
|
|
58
|
+
Use `atelier-orchestrator` at the start of development work. It selects an Inline Plan for bounded
|
|
59
|
+
changes or a Spec-backed Plan when durable design and coordination artifacts are warranted.
|
|
60
|
+
|
|
61
|
+
### Domain documentation
|
|
62
|
+
|
|
63
|
+
Before working in a domain area, read root `CONTEXT-MAP.md` when present and follow it to the
|
|
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.
|
|
66
|
+
|
|
67
|
+
If these documents do not exist, proceed silently. Create or update them only when domain
|
|
68
|
+
terminology or an architectural decision has been resolved.
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
If an `## Agent skills` section already exists, update the `### Planning and implementation` and
|
|
72
|
+
`### Domain documentation` subsections in place rather than appending duplicates. Preserve other
|
|
73
|
+
subsections and all content outside the section, including user edits. Do not create any other
|
|
74
|
+
files or directories.
|
|
75
|
+
|
|
76
|
+
## 4. Report
|
|
77
|
+
|
|
78
|
+
State which instruction file changed and the chosen domain-document layout. Remind the developer
|
|
79
|
+
that the layout is a convention only: the first domain-modelling or architectural-decision task
|
|
80
|
+
creates the relevant documents when it has substantive content.
|
|
@@ -34,7 +34,7 @@ Use these concrete harness subagent types. If an exact match is unavailable, use
|
|
|
34
34
|
|
|
35
35
|
| `subagent_type` | Purpose |
|
|
36
36
|
|-----------------|---------|
|
|
37
|
-
| `
|
|
37
|
+
| `sentinel` | Triage only: changed-file analysis, context retrieval, reviewer selection |
|
|
38
38
|
| `oracle` | Parallel reviewer personas, evidence-based critique, failure-mode analysis, challenge validation |
|
|
39
39
|
| `architect` | Architecture, design-boundary, data-model, and API-contract review |
|
|
40
40
|
|
|
@@ -67,7 +67,7 @@ Follows [code-subagents](../code-subagents/SKILL.md) patterns:
|
|
|
67
67
|
|
|
68
68
|
| Agent | Used In Step |
|
|
69
69
|
|-------|--------------|
|
|
70
|
-
| `
|
|
70
|
+
| `sentinel` | Triage only (context retrieval, file analysis) |
|
|
71
71
|
| `oracle` | Reviewers and challenge validation |
|
|
72
72
|
| `architect` | Architect (architecture review) |
|
|
73
73
|
|