@mindfoldhq/trellis 0.6.4 → 0.6.6
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/README.md +1 -1
- package/dist/cli/index.js +2 -0
- package/dist/cli/index.js.map +1 -1
- package/dist/commands/channel/messages.js +1 -1
- package/dist/commands/channel/supervisor/stdout.d.ts +7 -5
- package/dist/commands/channel/supervisor/stdout.d.ts.map +1 -1
- package/dist/commands/channel/supervisor/stdout.js +65 -18
- package/dist/commands/channel/supervisor/stdout.js.map +1 -1
- package/dist/commands/channel/supervisor.d.ts +13 -0
- package/dist/commands/channel/supervisor.d.ts.map +1 -1
- package/dist/commands/channel/supervisor.js +54 -2
- package/dist/commands/channel/supervisor.js.map +1 -1
- package/dist/commands/init.d.ts +2 -0
- package/dist/commands/init.d.ts.map +1 -1
- package/dist/commands/init.js.map +1 -1
- package/dist/commands/uninstall.d.ts.map +1 -1
- package/dist/commands/uninstall.js +8 -1
- package/dist/commands/uninstall.js.map +1 -1
- package/dist/commands/update.d.ts.map +1 -1
- package/dist/commands/update.js +83 -42
- package/dist/commands/update.js.map +1 -1
- package/dist/configurators/copilot.d.ts +1 -0
- package/dist/configurators/copilot.d.ts.map +1 -1
- package/dist/configurators/copilot.js +4 -1
- package/dist/configurators/copilot.js.map +1 -1
- package/dist/configurators/index.d.ts.map +1 -1
- package/dist/configurators/index.js +27 -1
- package/dist/configurators/index.js.map +1 -1
- package/dist/configurators/omp.d.ts +11 -0
- package/dist/configurators/omp.d.ts.map +1 -0
- package/dist/configurators/omp.js +51 -0
- package/dist/configurators/omp.js.map +1 -0
- package/dist/configurators/pi.d.ts.map +1 -1
- package/dist/configurators/pi.js +14 -4
- package/dist/configurators/pi.js.map +1 -1
- package/dist/configurators/shared.d.ts +7 -0
- package/dist/configurators/shared.d.ts.map +1 -1
- package/dist/configurators/shared.js +27 -0
- package/dist/configurators/shared.js.map +1 -1
- package/dist/configurators/trae.d.ts +17 -0
- package/dist/configurators/trae.d.ts.map +1 -0
- package/dist/configurators/trae.js +42 -0
- package/dist/configurators/trae.js.map +1 -0
- package/dist/configurators/zcode.d.ts +3 -3
- package/dist/configurators/zcode.js +12 -12
- package/dist/configurators/zcode.js.map +1 -1
- package/dist/migrations/manifests/0.6.5.json +9 -0
- package/dist/migrations/manifests/0.6.6.json +17 -0
- package/dist/templates/codex/hooks/session-start.py +6 -6
- package/dist/templates/codex/skills/brainstorm/SKILL.md +19 -1
- package/dist/templates/common/bundled-skills/trellis-meta/references/customize-local/change-agents.md +1 -1
- package/dist/templates/common/bundled-skills/trellis-meta/references/customize-local/change-hooks.md +1 -1
- package/dist/templates/common/bundled-skills/trellis-meta/references/customize-local/change-skills-or-commands.md +1 -1
- package/dist/templates/common/bundled-skills/trellis-meta/references/local-architecture/bundled-skills.md +2 -1
- package/dist/templates/common/bundled-skills/trellis-meta/references/local-architecture/generated-files.md +3 -3
- package/dist/templates/common/bundled-skills/trellis-meta/references/platform-files/agents.md +1 -1
- package/dist/templates/common/bundled-skills/trellis-meta/references/platform-files/hooks-and-settings.md +1 -0
- package/dist/templates/common/bundled-skills/trellis-meta/references/platform-files/overview.md +5 -5
- package/dist/templates/common/bundled-skills/trellis-meta/references/platform-files/platform-map.md +21 -2
- package/dist/templates/common/bundled-skills/trellis-meta/references/platform-files/skills-and-commands.md +1 -1
- package/dist/templates/common/skills/brainstorm.md +19 -1
- package/dist/templates/copilot/copilot-instructions.md +11 -0
- package/dist/templates/copilot/hooks/session-start.py +6 -6
- package/dist/templates/copilot/index.d.ts +6 -1
- package/dist/templates/copilot/index.d.ts.map +1 -1
- package/dist/templates/copilot/index.js +8 -1
- package/dist/templates/copilot/index.js.map +1 -1
- package/dist/templates/copilot/prompts/brainstorm.prompt.md +19 -1
- package/dist/templates/omp/agents/trellis-check.md +41 -0
- package/dist/templates/omp/agents/trellis-implement.md +45 -0
- package/dist/templates/omp/agents/trellis-research.md +30 -0
- package/dist/templates/omp/extensions/trellis/index.ts.txt +462 -0
- package/dist/templates/omp/index.d.ts +4 -0
- package/dist/templates/omp/index.d.ts.map +1 -0
- package/dist/templates/omp/index.js +9 -0
- package/dist/templates/omp/index.js.map +1 -0
- package/dist/templates/pi/agents/trellis-check.md +1 -1
- package/dist/templates/pi/agents/trellis-implement.md +1 -1
- package/dist/templates/pi/agents/trellis-research.md +1 -1
- package/dist/templates/pi/extensions/trellis/index.ts.txt +111 -9
- package/dist/templates/shared-hooks/index.d.ts +1 -1
- package/dist/templates/shared-hooks/index.d.ts.map +1 -1
- package/dist/templates/shared-hooks/index.js +1 -0
- package/dist/templates/shared-hooks/index.js.map +1 -1
- package/dist/templates/shared-hooks/inject-workflow-state.py +41 -6
- package/dist/templates/shared-hooks/session-start.py +12 -7
- package/dist/templates/trae/agents/trellis-check.md +108 -0
- package/dist/templates/trae/agents/trellis-implement.md +103 -0
- package/dist/templates/trae/agents/trellis-research.md +137 -0
- package/dist/templates/trae/hooks.json +27 -0
- package/dist/templates/trae/index.d.ts +15 -0
- package/dist/templates/trae/index.d.ts.map +1 -0
- package/dist/templates/trae/index.js +15 -0
- package/dist/templates/trae/index.js.map +1 -0
- package/dist/templates/trellis/scripts/add_session.py +65 -17
- package/dist/templates/trellis/scripts/common/active_task.py +2 -0
- package/dist/templates/trellis/scripts/common/cli_adapter.py +72 -11
- package/dist/templates/trellis/scripts/common/config.py +29 -0
- package/dist/templates/trellis/scripts/common/session_context.py +1 -1
- package/dist/templates/trellis/scripts/common/task_store.py +79 -19
- package/dist/templates/trellis/scripts/task.py +8 -2
- package/dist/templates/trellis/workflow.md +22 -20
- package/dist/templates/zcode/agents/trellis-research.md +133 -0
- package/dist/templates/zcode/index.d.ts +6 -5
- package/dist/templates/zcode/index.d.ts.map +1 -1
- package/dist/templates/zcode/index.js +6 -5
- package/dist/templates/zcode/index.js.map +1 -1
- package/dist/types/ai-tools.d.ts +4 -4
- package/dist/types/ai-tools.d.ts.map +1 -1
- package/dist/types/ai-tools.js +45 -2
- package/dist/types/ai-tools.js.map +1 -1
- package/dist/utils/uninstall-scrubbers.d.ts +1 -0
- package/dist/utils/uninstall-scrubbers.d.ts.map +1 -1
- package/dist/utils/uninstall-scrubbers.js +19 -0
- package/dist/utils/uninstall-scrubbers.js.map +1 -1
- package/package.json +2 -2
|
@@ -31,11 +31,13 @@ interface SubagentInput {
|
|
|
31
31
|
interface AgentConfig {
|
|
32
32
|
model?: string;
|
|
33
33
|
thinking?: string;
|
|
34
|
+
tools?: string[];
|
|
34
35
|
fallbackModels: string[];
|
|
35
36
|
}
|
|
36
37
|
interface PiRunConfig {
|
|
37
38
|
model?: string;
|
|
38
39
|
thinking?: string;
|
|
40
|
+
tools?: string[];
|
|
39
41
|
}
|
|
40
42
|
|
|
41
43
|
// ── Lazy-load pi-tui (avoid failing top-level imports) ─────────────────
|
|
@@ -81,6 +83,10 @@ const MAX_PARALLEL_PROMPTS = 6;
|
|
|
81
83
|
const ABORT_KILL_GRACE_MS = 1500;
|
|
82
84
|
const SESSION_OVERVIEW_TIMEOUT_MS = 1500;
|
|
83
85
|
const THROTTLE_MS = 500;
|
|
86
|
+
const FIRST_REPLY_NOTICE = `<first-reply-notice>
|
|
87
|
+
First visible reply: say once in Chinese that Trellis SessionStart context is loaded, then answer directly.
|
|
88
|
+
This notice is one-shot: do not repeat it after the first assistant reply in the same session.
|
|
89
|
+
</first-reply-notice>`;
|
|
84
90
|
|
|
85
91
|
// ── State types ───────────────────────────────────────────────────────
|
|
86
92
|
type RunStatus = "pending" | "running" | "succeeded" | "failed" | "cancelled";
|
|
@@ -654,8 +660,8 @@ function resolveRunCfg(
|
|
|
654
660
|
agentSuffixThinking ??
|
|
655
661
|
normalize(inheritedThinking);
|
|
656
662
|
if (baseModel && thinking && thinking !== "off")
|
|
657
|
-
return { model: `${baseModel}:${thinking}`, thinking };
|
|
658
|
-
return { model: baseModel || rawModel, thinking };
|
|
663
|
+
return { model: `${baseModel}:${thinking}`, thinking, tools: agentCfg.tools };
|
|
664
|
+
return { model: baseModel || rawModel, thinking, tools: agentCfg.tools };
|
|
659
665
|
}
|
|
660
666
|
|
|
661
667
|
function buildPiArgs(cfg: PiRunConfig): string[] {
|
|
@@ -669,6 +675,8 @@ function buildPiArgs(cfg: PiRunConfig): string[] {
|
|
|
669
675
|
);
|
|
670
676
|
else if (cfg.thinking && cfg.thinking !== "off")
|
|
671
677
|
args.push("--thinking", cfg.thinking);
|
|
678
|
+
if (cfg.tools && cfg.tools.length > 0)
|
|
679
|
+
args.push("--tools", cfg.tools.join(","));
|
|
672
680
|
return args;
|
|
673
681
|
}
|
|
674
682
|
|
|
@@ -762,6 +770,16 @@ function parseAgentFM(c: string): AgentConfig {
|
|
|
762
770
|
}
|
|
763
771
|
i--;
|
|
764
772
|
}
|
|
773
|
+
} else if (k === "tools") {
|
|
774
|
+
// Pi tool names are lowercase (read, bash, edit, write, grep, find, ls).
|
|
775
|
+
// Normalize to lowercase so mixed-case frontmatter still matches.
|
|
776
|
+
if (v.trim()) {
|
|
777
|
+
cfg.tools = v
|
|
778
|
+
.trim()
|
|
779
|
+
.split(",")
|
|
780
|
+
.map((s) => s.trim().replace(/^["']|["']$/g, "").toLowerCase())
|
|
781
|
+
.filter(Boolean);
|
|
782
|
+
}
|
|
765
783
|
}
|
|
766
784
|
}
|
|
767
785
|
return cfg;
|
|
@@ -862,12 +880,12 @@ function workflowBreadcrumb(root: string, key: string | null): string {
|
|
|
862
880
|
}
|
|
863
881
|
|
|
864
882
|
// ── Session Overview ───────────────────────────────────────────────────
|
|
865
|
-
function
|
|
883
|
+
function runContextScript(root: string, key: string | null, args: string[]): string {
|
|
866
884
|
const script = join(root, ".trellis", "scripts", "get_context.py");
|
|
867
885
|
if (!exists(script)) return "";
|
|
868
886
|
try {
|
|
869
887
|
const py = process.platform === "win32" ? "python" : "python3";
|
|
870
|
-
const result = spawnSync(py, [script], {
|
|
888
|
+
const result = spawnSync(py, [script, ...args], {
|
|
871
889
|
cwd: root,
|
|
872
890
|
env: key ? { ...process.env, TRELLIS_CONTEXT_ID: key } : process.env,
|
|
873
891
|
encoding: "utf-8",
|
|
@@ -876,12 +894,44 @@ function sessionOverview(root: string, key: string | null): string {
|
|
|
876
894
|
});
|
|
877
895
|
if (result.status !== 0) return "";
|
|
878
896
|
const stdout = (result.stdout ?? "").trim();
|
|
879
|
-
return stdout
|
|
897
|
+
return stdout;
|
|
880
898
|
} catch {
|
|
881
899
|
return "";
|
|
882
900
|
}
|
|
883
901
|
}
|
|
884
902
|
|
|
903
|
+
function sessionOverview(root: string, key: string | null): string {
|
|
904
|
+
const stdout = runContextScript(root, key, []);
|
|
905
|
+
return stdout ? `<session-overview>\n${stdout}\n</session-overview>` : "";
|
|
906
|
+
}
|
|
907
|
+
|
|
908
|
+
function workflowOverview(root: string, key: string | null): string {
|
|
909
|
+
const stdout = runContextScript(root, key, [
|
|
910
|
+
"--mode",
|
|
911
|
+
"phase",
|
|
912
|
+
"--platform",
|
|
913
|
+
"pi",
|
|
914
|
+
]);
|
|
915
|
+
return stdout ? `<trellis-workflow>\n${stdout}\n</trellis-workflow>` : "";
|
|
916
|
+
}
|
|
917
|
+
|
|
918
|
+
function buildStartupContext(
|
|
919
|
+
root: string,
|
|
920
|
+
key: string | null,
|
|
921
|
+
overview: string,
|
|
922
|
+
): string {
|
|
923
|
+
const workflow = workflowOverview(root, key);
|
|
924
|
+
return [
|
|
925
|
+
"<session-context>\nTrellis compact SessionStart context. Use it to orient the session; load details on demand.\n</session-context>",
|
|
926
|
+
FIRST_REPLY_NOTICE,
|
|
927
|
+
overview,
|
|
928
|
+
workflow,
|
|
929
|
+
"<ready>\nUse the current workflow state to decide whether to create, continue, or skip a Trellis task.\n</ready>",
|
|
930
|
+
]
|
|
931
|
+
.filter(Boolean)
|
|
932
|
+
.join("\n\n");
|
|
933
|
+
}
|
|
934
|
+
|
|
885
935
|
function buildContext(root: string, agent: string, key: string | null): string {
|
|
886
936
|
const dir = readTaskDir(root, key);
|
|
887
937
|
if (!dir)
|
|
@@ -1372,6 +1422,26 @@ export default function trellisExtension(pi: {
|
|
|
1372
1422
|
};
|
|
1373
1423
|
return turnCache;
|
|
1374
1424
|
};
|
|
1425
|
+
// Provider prefix caches invalidate from byte 0 whenever the system prompt
|
|
1426
|
+
// changes, so everything injected into systemPrompt is memoized per context
|
|
1427
|
+
// key and stays byte-identical for the life of the process. Volatile state
|
|
1428
|
+
// travels through persisted custom messages instead (append-only history).
|
|
1429
|
+
const startupCtxCache = new Map<string, string>();
|
|
1430
|
+
const getStartupCtx = (
|
|
1431
|
+
k: string | null,
|
|
1432
|
+
turn: { ov: string },
|
|
1433
|
+
): string => {
|
|
1434
|
+
const key = k ?? "default";
|
|
1435
|
+
let startup = startupCtxCache.get(key);
|
|
1436
|
+
if (startup === undefined) {
|
|
1437
|
+
startup = buildStartupContext(root, k, turn.ov);
|
|
1438
|
+
startupCtxCache.set(key, startup);
|
|
1439
|
+
}
|
|
1440
|
+
return startup;
|
|
1441
|
+
};
|
|
1442
|
+
const taskCtxSnapshot = new Map<string, string>();
|
|
1443
|
+
const lastSentTaskCtx = new Map<string, string>();
|
|
1444
|
+
const lastSentRuntimeCtx = new Map<string, string>();
|
|
1375
1445
|
|
|
1376
1446
|
// Toggle only the latest subagent native card; do not use Pi global tool expansion.
|
|
1377
1447
|
const toggleDetail = (ctx: PiExtensionContext) => {
|
|
@@ -1550,7 +1620,7 @@ export default function trellisExtension(pi: {
|
|
|
1550
1620
|
pi.on?.("session_start", (event, ctx) => {
|
|
1551
1621
|
getKey(event, ctx);
|
|
1552
1622
|
ctx?.ui?.notify?.(
|
|
1553
|
-
"Trellis project context is available. Use /trellis-
|
|
1623
|
+
"Trellis project context is available. Use /trellis-start to bootstrap or /trellis-continue to resume.",
|
|
1554
1624
|
"info",
|
|
1555
1625
|
);
|
|
1556
1626
|
});
|
|
@@ -1587,11 +1657,43 @@ export default function trellisExtension(pi: {
|
|
|
1587
1657
|
});
|
|
1588
1658
|
pi.on?.("before_agent_start", (event, ctx) => {
|
|
1589
1659
|
const k = getKey(event, ctx);
|
|
1660
|
+
const key = k ?? "default";
|
|
1590
1661
|
const cur = (event as { systemPrompt?: string }).systemPrompt ?? "";
|
|
1591
|
-
const
|
|
1592
|
-
const
|
|
1662
|
+
const turn = getTurnCtx(k);
|
|
1663
|
+
const startup = getStartupCtx(k, turn);
|
|
1664
|
+
// Task context is snapshotted into systemPrompt once; later on-disk
|
|
1665
|
+
// changes are delivered as persisted messages so the prefix stays stable.
|
|
1666
|
+
const freshTaskCtx = buildContext(root, "trellis-implement", k);
|
|
1667
|
+
let taskCtx = taskCtxSnapshot.get(key);
|
|
1668
|
+
if (taskCtx === undefined) {
|
|
1669
|
+
taskCtx = freshTaskCtx;
|
|
1670
|
+
taskCtxSnapshot.set(key, taskCtx);
|
|
1671
|
+
lastSentTaskCtx.set(key, freshTaskCtx);
|
|
1672
|
+
}
|
|
1673
|
+
const updates: string[] = [];
|
|
1674
|
+
const runtimeContext = [turn.wf, turn.ov].filter(Boolean).join("\n\n");
|
|
1675
|
+
if (runtimeContext && runtimeContext !== lastSentRuntimeCtx.get(key)) {
|
|
1676
|
+
lastSentRuntimeCtx.set(key, runtimeContext);
|
|
1677
|
+
updates.push(runtimeContext);
|
|
1678
|
+
}
|
|
1679
|
+
if (freshTaskCtx !== lastSentTaskCtx.get(key)) {
|
|
1680
|
+
lastSentTaskCtx.set(key, freshTaskCtx);
|
|
1681
|
+
updates.push(
|
|
1682
|
+
"<trellis-task-context-update>\nTask context changed on disk. This supersedes the Trellis Task Context in the system prompt.\n\n" +
|
|
1683
|
+
freshTaskCtx +
|
|
1684
|
+
"\n</trellis-task-context-update>",
|
|
1685
|
+
);
|
|
1686
|
+
}
|
|
1687
|
+
const content = updates.join("\n\n");
|
|
1593
1688
|
return {
|
|
1594
|
-
|
|
1689
|
+
message: content
|
|
1690
|
+
? {
|
|
1691
|
+
customType: "trellis-runtime-context",
|
|
1692
|
+
content,
|
|
1693
|
+
display: false,
|
|
1694
|
+
}
|
|
1695
|
+
: undefined,
|
|
1696
|
+
systemPrompt: [cur, startup, taskCtx].filter(Boolean).join("\n\n"),
|
|
1595
1697
|
};
|
|
1596
1698
|
});
|
|
1597
1699
|
pi.on?.("context", (event, ctx) => {
|
|
@@ -12,7 +12,7 @@ export interface HookScript {
|
|
|
12
12
|
content: string;
|
|
13
13
|
}
|
|
14
14
|
export type SharedHookName = "session-start.py" | "inject-shell-session-context.py" | "inject-workflow-state.py" | "inject-subagent-context.py";
|
|
15
|
-
export type SharedHookPlatform = "claude" | "cursor" | "codex" | "gemini" | "qoder" | "copilot" | "codebuddy" | "droid" | "kiro";
|
|
15
|
+
export type SharedHookPlatform = "claude" | "cursor" | "codex" | "gemini" | "qoder" | "copilot" | "codebuddy" | "droid" | "kiro" | "trae";
|
|
16
16
|
/**
|
|
17
17
|
* Which shared hooks each platform actually invokes. Single source of truth
|
|
18
18
|
* for shared-hook distribution — both `writeSharedHooks` (runtime install)
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/templates/shared-hooks/index.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAaH,MAAM,WAAW,UAAU;IACzB,0CAA0C;IAC1C,IAAI,EAAE,MAAM,CAAC;IACb,gEAAgE;IAChE,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,MAAM,cAAc,GACtB,kBAAkB,GAClB,iCAAiC,GACjC,0BAA0B,GAC1B,4BAA4B,CAAC;AAEjC,MAAM,MAAM,kBAAkB,GAC1B,QAAQ,GACR,QAAQ,GACR,OAAO,GACP,QAAQ,GACR,OAAO,GACP,SAAS,GACT,WAAW,GACX,OAAO,GACP,MAAM,CAAC;AAEX;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AACH,eAAO,MAAM,wBAAwB,EAAE,MAAM,CAC3C,kBAAkB,EAClB,SAAS,cAAc,EAAE,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/templates/shared-hooks/index.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAaH,MAAM,WAAW,UAAU;IACzB,0CAA0C;IAC1C,IAAI,EAAE,MAAM,CAAC;IACb,gEAAgE;IAChE,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,MAAM,cAAc,GACtB,kBAAkB,GAClB,iCAAiC,GACjC,0BAA0B,GAC1B,4BAA4B,CAAC;AAEjC,MAAM,MAAM,kBAAkB,GAC1B,QAAQ,GACR,QAAQ,GACR,OAAO,GACP,QAAQ,GACR,OAAO,GACP,SAAS,GACT,WAAW,GACX,OAAO,GACP,MAAM,GACN,MAAM,CAAC;AAEX;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AACH,eAAO,MAAM,wBAAwB,EAAE,MAAM,CAC3C,kBAAkB,EAClB,SAAS,cAAc,EAAE,CAgC1B,CAAC;AAEF;;;GAGG;AACH,wBAAgB,oBAAoB,IAAI,UAAU,EAAE,CAWnD;AAED;;;;GAIG;AACH,wBAAgB,+BAA+B,CAC7C,QAAQ,EAAE,kBAAkB,GAC3B,UAAU,EAAE,CAGd"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/templates/shared-hooks/index.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AACpD,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAC1C,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAEzC,MAAM,UAAU,GAAG,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;AAClD,MAAM,SAAS,GAAG,OAAO,CAAC,UAAU,CAAC,CAAC;AAEtC,SAAS,YAAY,CAAC,YAAoB;IACxC,OAAO,YAAY,CAAC,IAAI,CAAC,SAAS,EAAE,YAAY,CAAC,EAAE,OAAO,CAAC,CAAC;AAC9D,CAAC;
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/templates/shared-hooks/index.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AACpD,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAC1C,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAEzC,MAAM,UAAU,GAAG,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;AAClD,MAAM,SAAS,GAAG,OAAO,CAAC,UAAU,CAAC,CAAC;AAEtC,SAAS,YAAY,CAAC,YAAoB;IACxC,OAAO,YAAY,CAAC,IAAI,CAAC,SAAS,EAAE,YAAY,CAAC,EAAE,OAAO,CAAC,CAAC;AAC9D,CAAC;AA2BD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AACH,MAAM,CAAC,MAAM,wBAAwB,GAGjC;IACF,MAAM,EAAE;QACN,kBAAkB;QAClB,0BAA0B;QAC1B,4BAA4B;KAC7B;IACD,MAAM,EAAE;QACN,kBAAkB;QAClB,iCAAiC;QACjC,4BAA4B;KAC7B;IACD,KAAK,EAAE,CAAC,0BAA0B,CAAC;IACnC,MAAM,EAAE,CAAC,kBAAkB,EAAE,0BAA0B,CAAC;IACxD,KAAK,EAAE,CAAC,kBAAkB,EAAE,0BAA0B,CAAC;IACvD,OAAO,EAAE,CAAC,0BAA0B,CAAC;IACrC,SAAS,EAAE;QACT,kBAAkB;QAClB,0BAA0B;QAC1B,4BAA4B;KAC7B;IACD,KAAK,EAAE;QACL,kBAAkB;QAClB,0BAA0B;QAC1B,4BAA4B;KAC7B;IACD,IAAI,EAAE;QACJ,kBAAkB;QAClB,0BAA0B;QAC1B,4BAA4B;KAC7B;IACD,IAAI,EAAE,CAAC,kBAAkB,EAAE,0BAA0B,CAAC;CACvD,CAAC;AAEF;;;GAGG;AACH,MAAM,UAAU,oBAAoB;IAClC,MAAM,OAAO,GAAiB,EAAE,CAAC;IACjC,MAAM,KAAK,GAAG,WAAW,CAAC,SAAS,CAAC;SACjC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC;SAChC,IAAI,EAAE,CAAC;IAEV,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACzB,OAAO,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE,OAAO,EAAE,YAAY,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IAC5D,CAAC;IAED,OAAO,OAAO,CAAC;AACjB,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,+BAA+B,CAC7C,QAA4B;IAE5B,MAAM,OAAO,GAAG,IAAI,GAAG,CAAS,wBAAwB,CAAC,QAAQ,CAAC,CAAC,CAAC;IACpE,OAAO,oBAAoB,EAAE,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC;AACnE,CAAC"}
|
|
@@ -34,6 +34,8 @@ import json
|
|
|
34
34
|
import os
|
|
35
35
|
import re
|
|
36
36
|
import sys
|
|
37
|
+
import queue
|
|
38
|
+
import threading
|
|
37
39
|
from pathlib import Path
|
|
38
40
|
|
|
39
41
|
# Force UTF-8 on stdin/stdout/stderr on Windows. Default codepage there is
|
|
@@ -51,12 +53,12 @@ if sys.platform.startswith("win"):
|
|
|
51
53
|
try:
|
|
52
54
|
_stream.reconfigure(encoding="utf-8", errors="replace") # type: ignore[union-attr]
|
|
53
55
|
except Exception:
|
|
54
|
-
pass
|
|
56
|
+
pass # Optional Windows stream setup; keep hook startup non-fatal.
|
|
55
57
|
elif hasattr(_stream, "detach"):
|
|
56
58
|
try:
|
|
57
59
|
setattr(sys, _stream_name, _io.TextIOWrapper(_stream.detach(), encoding="utf-8", errors="replace"))
|
|
58
60
|
except Exception:
|
|
59
|
-
pass
|
|
61
|
+
pass # Optional Windows stream setup; keep hook startup non-fatal.
|
|
60
62
|
from typing import Optional
|
|
61
63
|
|
|
62
64
|
|
|
@@ -102,6 +104,7 @@ def _detect_platform(input_data: dict) -> str | None:
|
|
|
102
104
|
"QODER_PROJECT_DIR": "qoder",
|
|
103
105
|
"KIRO_PROJECT_DIR": "kiro",
|
|
104
106
|
"COPILOT_PROJECT_DIR": "copilot",
|
|
107
|
+
"TRAE_PROJECT_DIR": "trae",
|
|
105
108
|
}
|
|
106
109
|
for env_name, platform in env_map.items():
|
|
107
110
|
if os.environ.get(env_name):
|
|
@@ -123,6 +126,8 @@ def _detect_platform(input_data: dict) -> str | None:
|
|
|
123
126
|
return "droid"
|
|
124
127
|
if ".kiro" in script_parts:
|
|
125
128
|
return "kiro"
|
|
129
|
+
if ".trae" in script_parts:
|
|
130
|
+
return "trae"
|
|
126
131
|
return None
|
|
127
132
|
|
|
128
133
|
|
|
@@ -302,14 +307,44 @@ def build_breadcrumb(
|
|
|
302
307
|
# Entry
|
|
303
308
|
# ---------------------------------------------------------------------------
|
|
304
309
|
|
|
310
|
+
def _load_hook_input() -> dict:
|
|
311
|
+
"""Read hook JSON without trusting host runners to close stdin.
|
|
312
|
+
|
|
313
|
+
Kiro IDE `runCommand` and similar hook runners can leave stdin open while
|
|
314
|
+
sending no payload. A plain `json.load(sys.stdin)` then blocks forever.
|
|
315
|
+
Normal hook runners write the complete JSON payload and close stdin, so the
|
|
316
|
+
short daemon read preserves that path while failing closed to `{}` for
|
|
317
|
+
non-piping hosts.
|
|
318
|
+
"""
|
|
319
|
+
result_queue: "queue.Queue[str | Exception]" = queue.Queue(maxsize=1)
|
|
320
|
+
|
|
321
|
+
def _read() -> None:
|
|
322
|
+
try:
|
|
323
|
+
result_queue.put(sys.stdin.read())
|
|
324
|
+
except Exception as exc:
|
|
325
|
+
result_queue.put(exc)
|
|
326
|
+
|
|
327
|
+
reader = threading.Thread(target=_read, daemon=True)
|
|
328
|
+
reader.start()
|
|
329
|
+
try:
|
|
330
|
+
raw = result_queue.get(timeout=0.2)
|
|
331
|
+
except queue.Empty:
|
|
332
|
+
return {}
|
|
333
|
+
|
|
334
|
+
if isinstance(raw, Exception):
|
|
335
|
+
return {}
|
|
336
|
+
try:
|
|
337
|
+
data = json.loads(raw) if raw.strip() else {}
|
|
338
|
+
except (json.JSONDecodeError, ValueError):
|
|
339
|
+
return {}
|
|
340
|
+
return data if isinstance(data, dict) else {}
|
|
341
|
+
|
|
342
|
+
|
|
305
343
|
def main() -> int:
|
|
306
344
|
if os.environ.get("TRELLIS_HOOKS") == "0" or os.environ.get("TRELLIS_DISABLE_HOOKS") == "1":
|
|
307
345
|
return 0
|
|
308
346
|
|
|
309
|
-
|
|
310
|
-
data = json.load(sys.stdin)
|
|
311
|
-
except (json.JSONDecodeError, ValueError):
|
|
312
|
-
data = {}
|
|
347
|
+
data = _load_hook_input()
|
|
313
348
|
|
|
314
349
|
cwd_str = data.get("cwd") or os.getcwd()
|
|
315
350
|
cwd = Path(cwd_str)
|
|
@@ -87,12 +87,12 @@ if sys.platform.startswith("win"):
|
|
|
87
87
|
try:
|
|
88
88
|
_stream.reconfigure(encoding="utf-8", errors="replace") # type: ignore[union-attr]
|
|
89
89
|
except Exception:
|
|
90
|
-
pass
|
|
90
|
+
pass # Optional Windows stream setup; keep hook startup non-fatal.
|
|
91
91
|
elif hasattr(_stream, "detach"):
|
|
92
92
|
try:
|
|
93
93
|
setattr(sys, _stream_name, _io.TextIOWrapper(_stream.detach(), encoding="utf-8", errors="replace"))
|
|
94
94
|
except Exception:
|
|
95
|
-
pass
|
|
95
|
+
pass # Optional Windows stream setup; keep hook startup non-fatal.
|
|
96
96
|
|
|
97
97
|
|
|
98
98
|
|
|
@@ -137,6 +137,7 @@ def should_skip_injection() -> bool:
|
|
|
137
137
|
"GEMINI_NON_INTERACTIVE",
|
|
138
138
|
"KIRO_NON_INTERACTIVE",
|
|
139
139
|
"COPILOT_NON_INTERACTIVE",
|
|
140
|
+
"TRAE_NON_INTERACTIVE",
|
|
140
141
|
]
|
|
141
142
|
return any(os.environ.get(var) == "1" for var in non_interactive_vars)
|
|
142
143
|
|
|
@@ -195,6 +196,7 @@ def _detect_platform(input_data: dict) -> str | None:
|
|
|
195
196
|
"QODER_PROJECT_DIR": "qoder",
|
|
196
197
|
"KIRO_PROJECT_DIR": "kiro",
|
|
197
198
|
"COPILOT_PROJECT_DIR": "copilot",
|
|
199
|
+
"TRAE_PROJECT_DIR": "trae",
|
|
198
200
|
}
|
|
199
201
|
for env_name, platform in env_map.items():
|
|
200
202
|
if os.environ.get(env_name):
|
|
@@ -216,6 +218,8 @@ def _detect_platform(input_data: dict) -> str | None:
|
|
|
216
218
|
return "droid"
|
|
217
219
|
if ".kiro" in script_parts:
|
|
218
220
|
return "kiro"
|
|
221
|
+
if ".trae" in script_parts:
|
|
222
|
+
return "trae"
|
|
219
223
|
return None
|
|
220
224
|
|
|
221
225
|
|
|
@@ -245,7 +249,7 @@ def _persist_context_key_for_bash(context_key: str | None) -> None:
|
|
|
245
249
|
with open(env_file, "a", encoding="utf-8") as handle:
|
|
246
250
|
handle.write(f"export TRELLIS_CONTEXT_ID={shlex.quote(context_key)}\n")
|
|
247
251
|
except OSError:
|
|
248
|
-
pass
|
|
252
|
+
pass # Optional shell bridge; keep session-start non-fatal.
|
|
249
253
|
|
|
250
254
|
|
|
251
255
|
def _resolve_active_task(trellis_dir: Path, input_data: dict):
|
|
@@ -347,7 +351,7 @@ def _get_task_status(trellis_dir: Path, input_data: dict) -> str:
|
|
|
347
351
|
try:
|
|
348
352
|
task_data = json.loads(task_json_path.read_text(encoding="utf-8"))
|
|
349
353
|
except (json.JSONDecodeError, PermissionError):
|
|
350
|
-
pass
|
|
354
|
+
pass # Optional task metadata; fall back to generic status.
|
|
351
355
|
|
|
352
356
|
task_title = task_data.get("title", task_ref)
|
|
353
357
|
task_status = task_data.get("status", "unknown")
|
|
@@ -449,7 +453,7 @@ def _load_trellis_config(trellis_dir: Path, input_data: dict) -> tuple:
|
|
|
449
453
|
if isinstance(tp, str) and tp:
|
|
450
454
|
task_pkg = tp
|
|
451
455
|
except (json.JSONDecodeError, OSError):
|
|
452
|
-
pass
|
|
456
|
+
pass # Optional package metadata; fall back to default scope.
|
|
453
457
|
|
|
454
458
|
default_pkg = get_default_package(repo_root)
|
|
455
459
|
return is_mono, packages, scope, task_pkg, default_pkg
|
|
@@ -626,7 +630,7 @@ def _build_compact_current_state(
|
|
|
626
630
|
if isinstance(data, dict):
|
|
627
631
|
status = str(data.get("status") or "unknown")
|
|
628
632
|
except (json.JSONDecodeError, OSError):
|
|
629
|
-
pass
|
|
633
|
+
pass # Optional task metadata; fall back to generic status.
|
|
630
634
|
lines.append(f"Current task: {_repo_relative(repo_root, task_dir)}; status={status}.")
|
|
631
635
|
else:
|
|
632
636
|
lines.append("Current task: none.")
|
|
@@ -638,7 +642,7 @@ def _build_compact_current_state(
|
|
|
638
642
|
f"Active tasks: {task_count} total. Use `python3 ./.trellis/scripts/task.py list --mine` only if needed."
|
|
639
643
|
)
|
|
640
644
|
except Exception:
|
|
641
|
-
pass
|
|
645
|
+
pass # Optional task summary; keep compact state available.
|
|
642
646
|
|
|
643
647
|
if get_active_journal_file and count_lines:
|
|
644
648
|
journal = get_active_journal_file(repo_root)
|
|
@@ -739,6 +743,7 @@ def main():
|
|
|
739
743
|
"GEMINI_PROJECT_DIR",
|
|
740
744
|
"KIRO_PROJECT_DIR",
|
|
741
745
|
"COPILOT_PROJECT_DIR",
|
|
746
|
+
"TRAE_PROJECT_DIR",
|
|
742
747
|
]
|
|
743
748
|
project_dir = None
|
|
744
749
|
for var in project_dir_env_vars:
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: trellis-check
|
|
3
|
+
description: |
|
|
4
|
+
Code quality check expert. Reviews code changes against specs and self-fixes issues.
|
|
5
|
+
tools: Read, Write, Edit, Bash, Glob, Grep
|
|
6
|
+
---
|
|
7
|
+
# Check Agent
|
|
8
|
+
|
|
9
|
+
You are the Check Agent in the Trellis workflow.
|
|
10
|
+
|
|
11
|
+
## Recursion Guard
|
|
12
|
+
|
|
13
|
+
You are already the `trellis-check` sub-agent that the main session dispatched. Do the review and fixes directly.
|
|
14
|
+
|
|
15
|
+
- Do NOT spawn another `trellis-check` or `trellis-implement` sub-agent.
|
|
16
|
+
- If SessionStart context, workflow-state breadcrumbs, or workflow.md say to dispatch `trellis-implement` / `trellis-check`, treat that as a main-session instruction that is already satisfied by your current role.
|
|
17
|
+
- Only the main session may dispatch Trellis implement/check agents. If more implementation work is needed, report that recommendation instead of spawning.
|
|
18
|
+
|
|
19
|
+
## Context
|
|
20
|
+
|
|
21
|
+
Before checking, read:
|
|
22
|
+
- `.trellis/spec/` - Development guidelines
|
|
23
|
+
- Task `prd.md` - Requirements document
|
|
24
|
+
- Task `design.md` - Technical design (if exists)
|
|
25
|
+
- Task `implement.md` - Execution plan (if exists)
|
|
26
|
+
- Pre-commit checklist for quality standards
|
|
27
|
+
|
|
28
|
+
## Core Responsibilities
|
|
29
|
+
|
|
30
|
+
1. **Get code changes** - Use git diff to get uncommitted code
|
|
31
|
+
2. **Review task artifacts** - Check changes against prd.md, design.md if present, and implement.md if present
|
|
32
|
+
3. **Check against specs** - Verify code follows guidelines
|
|
33
|
+
4. **Self-fix** - Fix issues yourself, not just report them
|
|
34
|
+
5. **Run verification** - typecheck and lint
|
|
35
|
+
|
|
36
|
+
## Important
|
|
37
|
+
|
|
38
|
+
**Fix issues yourself**, don't just report them.
|
|
39
|
+
|
|
40
|
+
You have write and edit tools, you can modify code directly.
|
|
41
|
+
|
|
42
|
+
---
|
|
43
|
+
|
|
44
|
+
## Workflow
|
|
45
|
+
|
|
46
|
+
### Step 1: Get Changes
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
git diff --name-only # List changed files
|
|
50
|
+
git diff # View specific changes
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
### Step 2: Check Against Specs and Task Artifacts
|
|
54
|
+
|
|
55
|
+
Read the task's prd.md, design.md if present, and implement.md if present, then read relevant specs in `.trellis/spec/` to check code:
|
|
56
|
+
|
|
57
|
+
- Does it satisfy the task requirements
|
|
58
|
+
- Does it follow the technical design and implementation plan when present
|
|
59
|
+
- Does it follow directory structure conventions
|
|
60
|
+
- Does it follow naming conventions
|
|
61
|
+
- Does it follow code patterns
|
|
62
|
+
- Are there missing types
|
|
63
|
+
- Are there potential bugs
|
|
64
|
+
|
|
65
|
+
### Step 3: Self-Fix
|
|
66
|
+
|
|
67
|
+
After finding issues:
|
|
68
|
+
|
|
69
|
+
1. Fix the issue directly (use edit tool)
|
|
70
|
+
2. Record what was fixed
|
|
71
|
+
3. Continue checking other issues
|
|
72
|
+
|
|
73
|
+
### Step 4: Run Verification
|
|
74
|
+
|
|
75
|
+
Run project's lint and typecheck commands to verify changes.
|
|
76
|
+
|
|
77
|
+
If failed, fix issues and re-run.
|
|
78
|
+
|
|
79
|
+
---
|
|
80
|
+
|
|
81
|
+
## Report Format
|
|
82
|
+
|
|
83
|
+
```markdown
|
|
84
|
+
## Self-Check Complete
|
|
85
|
+
|
|
86
|
+
### Files Checked
|
|
87
|
+
|
|
88
|
+
- src/components/Feature.tsx
|
|
89
|
+
- src/hooks/useFeature.ts
|
|
90
|
+
|
|
91
|
+
### Issues Found and Fixed
|
|
92
|
+
|
|
93
|
+
1. `<file>:<line>` - <what was fixed>
|
|
94
|
+
2. `<file>:<line>` - <what was fixed>
|
|
95
|
+
|
|
96
|
+
### Issues Not Fixed
|
|
97
|
+
|
|
98
|
+
(If there are issues that cannot be self-fixed, list them here with reasons)
|
|
99
|
+
|
|
100
|
+
### Verification Results
|
|
101
|
+
|
|
102
|
+
- TypeCheck: Passed
|
|
103
|
+
- Lint: Passed
|
|
104
|
+
|
|
105
|
+
### Summary
|
|
106
|
+
|
|
107
|
+
Checked X files, found Y issues, all fixed.
|
|
108
|
+
```
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: trellis-implement
|
|
3
|
+
description: |
|
|
4
|
+
Code implementation expert. Understands specs and requirements, then implements features. No git commit allowed.
|
|
5
|
+
tools: Read, Write, Edit, Bash, Glob, Grep
|
|
6
|
+
---
|
|
7
|
+
# Implement Agent
|
|
8
|
+
|
|
9
|
+
You are the Implement Agent in the Trellis workflow.
|
|
10
|
+
|
|
11
|
+
## Recursion Guard
|
|
12
|
+
|
|
13
|
+
You are already the `trellis-implement` sub-agent that the main session dispatched. Do the implementation work directly.
|
|
14
|
+
|
|
15
|
+
- Do NOT spawn another `trellis-implement` or `trellis-check` sub-agent.
|
|
16
|
+
- If SessionStart context, workflow-state breadcrumbs, or workflow.md say to dispatch `trellis-implement` / `trellis-check`, treat that as a main-session instruction that is already satisfied by your current role.
|
|
17
|
+
- Only the main session may dispatch Trellis implement/check agents. If more parallel work is needed, report that recommendation instead of spawning.
|
|
18
|
+
|
|
19
|
+
## Context
|
|
20
|
+
|
|
21
|
+
Before implementing, read:
|
|
22
|
+
- `.trellis/workflow.md` - Project workflow
|
|
23
|
+
- `.trellis/spec/` - Development guidelines
|
|
24
|
+
- Task `prd.md` - Requirements document
|
|
25
|
+
- Task `design.md` - Technical design (if exists)
|
|
26
|
+
- Task `implement.md` - Execution plan (if exists)
|
|
27
|
+
|
|
28
|
+
## Core Responsibilities
|
|
29
|
+
|
|
30
|
+
1. **Understand specs** - Read relevant spec files in `.trellis/spec/`
|
|
31
|
+
2. **Understand task artifacts** - Read prd.md, design.md if present, and implement.md if present
|
|
32
|
+
3. **Implement features** - Write code following specs and task artifacts
|
|
33
|
+
4. **Self-check** - Ensure code quality
|
|
34
|
+
5. **Report results** - Report completion status
|
|
35
|
+
|
|
36
|
+
## Forbidden Operations
|
|
37
|
+
|
|
38
|
+
**Do NOT execute these git commands:**
|
|
39
|
+
|
|
40
|
+
- `git commit`
|
|
41
|
+
- `git push`
|
|
42
|
+
- `git merge`
|
|
43
|
+
|
|
44
|
+
---
|
|
45
|
+
|
|
46
|
+
## Workflow
|
|
47
|
+
|
|
48
|
+
### 1. Understand Specs
|
|
49
|
+
|
|
50
|
+
Read relevant specs based on task type:
|
|
51
|
+
|
|
52
|
+
- Spec layers: `.trellis/spec/<package>/<layer>/`
|
|
53
|
+
- Shared guides: `.trellis/spec/guides/`
|
|
54
|
+
|
|
55
|
+
### 2. Understand Requirements
|
|
56
|
+
|
|
57
|
+
Read the task's prd.md, design.md if present, and implement.md if present:
|
|
58
|
+
|
|
59
|
+
- What are the core requirements
|
|
60
|
+
- Key points of technical design
|
|
61
|
+
- Implementation order, validation commands, and rollback points
|
|
62
|
+
|
|
63
|
+
### 3. Implement Features
|
|
64
|
+
|
|
65
|
+
- Write code following specs and task artifacts
|
|
66
|
+
- Follow existing code patterns
|
|
67
|
+
- Only do what's required, no over-engineering
|
|
68
|
+
|
|
69
|
+
### 4. Verify
|
|
70
|
+
|
|
71
|
+
Run project's lint and typecheck commands to verify changes.
|
|
72
|
+
|
|
73
|
+
---
|
|
74
|
+
|
|
75
|
+
## Report Format
|
|
76
|
+
|
|
77
|
+
```markdown
|
|
78
|
+
## Implementation Complete
|
|
79
|
+
|
|
80
|
+
### Files Modified
|
|
81
|
+
|
|
82
|
+
- `src/components/Feature.tsx` - New component
|
|
83
|
+
- `src/hooks/useFeature.ts` - New hook
|
|
84
|
+
|
|
85
|
+
### Implementation Summary
|
|
86
|
+
|
|
87
|
+
1. Created Feature component...
|
|
88
|
+
2. Added useFeature hook...
|
|
89
|
+
|
|
90
|
+
### Verification Results
|
|
91
|
+
|
|
92
|
+
- Lint: Passed
|
|
93
|
+
- TypeCheck: Passed
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
---
|
|
97
|
+
|
|
98
|
+
## Code Standards
|
|
99
|
+
|
|
100
|
+
- Follow existing code patterns
|
|
101
|
+
- Don't add unnecessary abstractions
|
|
102
|
+
- Only do what's required, no over-engineering
|
|
103
|
+
- Keep code readable
|