@mindfoldhq/trellis 0.6.0-beta.4 → 0.6.0-beta.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/dist/migrations/manifests/0.5.10.json +9 -0
- package/dist/migrations/manifests/0.5.11.json +16 -0
- package/dist/migrations/manifests/0.6.0-beta.5.json +9 -0
- package/dist/migrations/manifests/0.6.0-beta.6.json +16 -0
- package/dist/templates/common/commands/start.md +2 -0
- package/dist/templates/markdown/spec/guides/cross-layer-thinking-guide.md.txt +39 -0
- package/dist/templates/markdown/spec/guides/cross-platform-thinking-guide.md.txt +289 -43
- package/dist/templates/pi/extensions/trellis/index.ts.txt +179 -4
- package/dist/templates/pi/settings.json +9 -0
- package/dist/templates/trellis/config.yaml +18 -0
- package/dist/templates/trellis/index.d.ts +1 -0
- package/dist/templates/trellis/index.d.ts.map +1 -1
- package/dist/templates/trellis/index.js +2 -0
- package/dist/templates/trellis/index.js.map +1 -1
- package/dist/templates/trellis/scripts/add_session.py +50 -24
- package/dist/templates/trellis/scripts/common/config.py +57 -1
- package/dist/templates/trellis/scripts/common/safe_commit.py +255 -0
- package/dist/templates/trellis/scripts/common/session_context.py +170 -0
- package/dist/templates/trellis/scripts/common/task_store.py +41 -5
- 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 +21 -0
- package/dist/utils/uninstall-scrubbers.js.map +1 -1
- package/package.json +1 -1
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { existsSync, readFileSync, readdirSync, statSync } from "node:fs";
|
|
2
2
|
import { createHash, randomBytes } from "node:crypto";
|
|
3
3
|
import { delimiter, dirname, join, resolve } from "node:path";
|
|
4
|
-
import { spawn } from "node:child_process";
|
|
4
|
+
import { spawn, spawnSync } from "node:child_process";
|
|
5
5
|
|
|
6
6
|
type JsonObject = Record<string, unknown>;
|
|
7
7
|
type TextContent = { type: "text"; text: string };
|
|
@@ -632,6 +632,166 @@ function buildTrellisContext(
|
|
|
632
632
|
].join("\n");
|
|
633
633
|
}
|
|
634
634
|
|
|
635
|
+
// ---------------------------------------------------------------------------
|
|
636
|
+
// Workflow-state breadcrumb (TypeScript port of the shared workflow-state
|
|
637
|
+
// hook used by class-1 platforms).
|
|
638
|
+
//
|
|
639
|
+
// Pi is extension-backed and MUST NOT receive Python hook scripts under .pi/.
|
|
640
|
+
// We therefore parse `.trellis/workflow.md` `[workflow-state:STATUS]...
|
|
641
|
+
// [/workflow-state:STATUS]` blocks directly in TypeScript and emit the
|
|
642
|
+
// per-turn `<workflow-state>` breadcrumb in `before_agent_start` and `input`.
|
|
643
|
+
// Tag regex mirrors the shared parser so the breadcrumb body stays
|
|
644
|
+
// byte-identical with hook-driven platforms.
|
|
645
|
+
// ---------------------------------------------------------------------------
|
|
646
|
+
|
|
647
|
+
const WORKFLOW_STATE_TAG_RE =
|
|
648
|
+
/\[workflow-state:([A-Za-z0-9_-]+)\]\s*\n([\s\S]*?)\n\s*\[\/workflow-state:\1\]/g;
|
|
649
|
+
|
|
650
|
+
function loadWorkflowBreadcrumbs(projectRoot: string): Record<string, string> {
|
|
651
|
+
const workflow = readText(join(projectRoot, ".trellis", "workflow.md"));
|
|
652
|
+
if (!workflow) return {};
|
|
653
|
+
const result: Record<string, string> = {};
|
|
654
|
+
for (const match of workflow.matchAll(WORKFLOW_STATE_TAG_RE)) {
|
|
655
|
+
const status = match[1] ?? "";
|
|
656
|
+
const body = (match[2] ?? "").trim();
|
|
657
|
+
if (status && body) result[status] = body;
|
|
658
|
+
}
|
|
659
|
+
return result;
|
|
660
|
+
}
|
|
661
|
+
|
|
662
|
+
function readActiveTaskStatus(
|
|
663
|
+
projectRoot: string,
|
|
664
|
+
taskDir: string,
|
|
665
|
+
): { taskId: string; status: string } | null {
|
|
666
|
+
try {
|
|
667
|
+
const data = JSON.parse(
|
|
668
|
+
readText(join(taskDir, "task.json")),
|
|
669
|
+
) as JsonObject;
|
|
670
|
+
const status = stringValue(data.status);
|
|
671
|
+
if (!status) return null;
|
|
672
|
+
const id = stringValue(data.id) ?? taskDir.split(/[\\/]/).pop() ?? "";
|
|
673
|
+
return { taskId: id, status };
|
|
674
|
+
} catch {
|
|
675
|
+
return null;
|
|
676
|
+
}
|
|
677
|
+
}
|
|
678
|
+
|
|
679
|
+
function buildWorkflowStateBreadcrumb(
|
|
680
|
+
projectRoot: string,
|
|
681
|
+
contextKey: string | null,
|
|
682
|
+
): string {
|
|
683
|
+
const templates = loadWorkflowBreadcrumbs(projectRoot);
|
|
684
|
+
const taskDir = readCurrentTask(
|
|
685
|
+
projectRoot,
|
|
686
|
+
undefined,
|
|
687
|
+
undefined,
|
|
688
|
+
contextKey,
|
|
689
|
+
);
|
|
690
|
+
let header: string;
|
|
691
|
+
let lookupKey: string;
|
|
692
|
+
if (!taskDir) {
|
|
693
|
+
header = "Status: no_task\nSource: session";
|
|
694
|
+
lookupKey = "no_task";
|
|
695
|
+
} else {
|
|
696
|
+
const info = readActiveTaskStatus(projectRoot, taskDir);
|
|
697
|
+
if (!info) {
|
|
698
|
+
header = "Status: no_task\nSource: session";
|
|
699
|
+
lookupKey = "no_task";
|
|
700
|
+
} else {
|
|
701
|
+
header = `Task: ${info.taskId} (${info.status})\nSource: session`;
|
|
702
|
+
lookupKey = info.status;
|
|
703
|
+
}
|
|
704
|
+
}
|
|
705
|
+
const body = templates[lookupKey] ?? "Refer to workflow.md for current step.";
|
|
706
|
+
return `<workflow-state>\n${header}\n${body}\n</workflow-state>`;
|
|
707
|
+
}
|
|
708
|
+
|
|
709
|
+
// ---------------------------------------------------------------------------
|
|
710
|
+
// Session overview (developer / git branch / active tasks)
|
|
711
|
+
//
|
|
712
|
+
// Spawns `python3 .trellis/scripts/get_context.py` (the same script other
|
|
713
|
+
// platform session-start hooks invoke) to keep developer/git/active-task
|
|
714
|
+
// summary byte-identical with class-1 platforms. Failure is non-fatal — we
|
|
715
|
+
// emit an empty overview rather than block the conversation.
|
|
716
|
+
// ---------------------------------------------------------------------------
|
|
717
|
+
|
|
718
|
+
const SESSION_OVERVIEW_TIMEOUT_MS = 5000;
|
|
719
|
+
|
|
720
|
+
function pythonExecutable(): string {
|
|
721
|
+
const override = stringValue(process.env.TRELLIS_PYTHON);
|
|
722
|
+
if (override) return override;
|
|
723
|
+
return process.platform === "win32" ? "python" : "python3";
|
|
724
|
+
}
|
|
725
|
+
|
|
726
|
+
function buildSessionOverview(
|
|
727
|
+
projectRoot: string,
|
|
728
|
+
contextKey: string | null,
|
|
729
|
+
): string {
|
|
730
|
+
const script = join(projectRoot, ".trellis", "scripts", "get_context.py");
|
|
731
|
+
if (!isExistingFile(script)) return "";
|
|
732
|
+
try {
|
|
733
|
+
const result = spawnSync(pythonExecutable(), [script], {
|
|
734
|
+
cwd: projectRoot,
|
|
735
|
+
env: contextKey
|
|
736
|
+
? { ...process.env, TRELLIS_CONTEXT_ID: contextKey }
|
|
737
|
+
: process.env,
|
|
738
|
+
encoding: "utf-8",
|
|
739
|
+
timeout: SESSION_OVERVIEW_TIMEOUT_MS,
|
|
740
|
+
windowsHide: true,
|
|
741
|
+
});
|
|
742
|
+
if (result.status !== 0) return "";
|
|
743
|
+
const stdout = (result.stdout ?? "").trim();
|
|
744
|
+
if (!stdout) return "";
|
|
745
|
+
return `<session-overview>\n${stdout}\n</session-overview>`;
|
|
746
|
+
} catch {
|
|
747
|
+
return "";
|
|
748
|
+
}
|
|
749
|
+
}
|
|
750
|
+
|
|
751
|
+
// Per-turn cache so input + before_agent_start in the same turn don't double-spawn.
|
|
752
|
+
class TurnContextCache {
|
|
753
|
+
private key: string | null = null;
|
|
754
|
+
private timestamp = 0;
|
|
755
|
+
private workflowState = "";
|
|
756
|
+
private sessionOverview = "";
|
|
757
|
+
// Refresh window: per-turn injections that fire close together share a
|
|
758
|
+
// single python3 spawn; anything older than this re-runs the resolver.
|
|
759
|
+
private static readonly TTL_MS = 1500;
|
|
760
|
+
|
|
761
|
+
get(
|
|
762
|
+
projectRoot: string,
|
|
763
|
+
contextKey: string | null,
|
|
764
|
+
): { workflowState: string; sessionOverview: string } {
|
|
765
|
+
const now = Date.now();
|
|
766
|
+
if (this.key === contextKey && now - this.timestamp < TurnContextCache.TTL_MS) {
|
|
767
|
+
return {
|
|
768
|
+
workflowState: this.workflowState,
|
|
769
|
+
sessionOverview: this.sessionOverview,
|
|
770
|
+
};
|
|
771
|
+
}
|
|
772
|
+
this.workflowState = buildWorkflowStateBreadcrumb(projectRoot, contextKey);
|
|
773
|
+
this.sessionOverview = buildSessionOverview(projectRoot, contextKey);
|
|
774
|
+
this.key = contextKey;
|
|
775
|
+
this.timestamp = now;
|
|
776
|
+
return {
|
|
777
|
+
workflowState: this.workflowState,
|
|
778
|
+
sessionOverview: this.sessionOverview,
|
|
779
|
+
};
|
|
780
|
+
}
|
|
781
|
+
}
|
|
782
|
+
|
|
783
|
+
// ---------------------------------------------------------------------------
|
|
784
|
+
// Sub-agent dispatch protocol snippet (registered with the `subagent` tool).
|
|
785
|
+
// Mirrors the [workflow-state:in_progress] dispatch protocol text in
|
|
786
|
+
// trellis/workflow.md so the AI sees the same `Active task: <path>` rule
|
|
787
|
+
// whether it reads workflow.md, the per-turn breadcrumb, or the tool prompt.
|
|
788
|
+
// ---------------------------------------------------------------------------
|
|
789
|
+
|
|
790
|
+
const SUBAGENT_DISPATCH_PROTOCOL = `Sub-agent dispatch protocol (Trellis): your dispatch prompt MUST start with one line "Active task: <task path from \`task.py current\`>" before any other instructions. No exceptions. On class-2 platforms (codex / copilot / gemini / qoder) the sub-agent depends on this line because there is no hook to inject task context. On class-1 platforms (claude / cursor / opencode / kiro / codebuddy / droid) and on Pi, the line is the canonical fallback when hook/extension injection misses. trellis-research uses the line to know which {task_dir}/research/ to write into.
|
|
791
|
+
|
|
792
|
+
Wrong: prompt: "implement the new feature"
|
|
793
|
+
Correct: prompt: "Active task: .trellis/tasks/05-09-pi-workflow-state-injection\\n\\nImplement the new feature ..."`;
|
|
794
|
+
|
|
635
795
|
function normalizeAgentName(agent: string): string {
|
|
636
796
|
return agent.startsWith("trellis-") ? agent : `trellis-${agent}`;
|
|
637
797
|
}
|
|
@@ -886,6 +1046,15 @@ export default function trellisExtension(pi: {
|
|
|
886
1046
|
const projectRoot = findProjectRoot(pi.cwd ?? process.cwd());
|
|
887
1047
|
const processContextKey = createProcessContextKey(projectRoot);
|
|
888
1048
|
let currentContextKey: string | null = null;
|
|
1049
|
+
const turnContextCache = new TurnContextCache();
|
|
1050
|
+
|
|
1051
|
+
const buildPerTurnInjection = (contextKey: string | null): string => {
|
|
1052
|
+
const { workflowState, sessionOverview } = turnContextCache.get(
|
|
1053
|
+
projectRoot,
|
|
1054
|
+
contextKey,
|
|
1055
|
+
);
|
|
1056
|
+
return [workflowState, sessionOverview].filter(Boolean).join("\n\n");
|
|
1057
|
+
};
|
|
889
1058
|
|
|
890
1059
|
const getContextKey = (input?: unknown, ctx?: PiExtensionContext): string => {
|
|
891
1060
|
const resolvedContextKey = resolveContextKey(
|
|
@@ -904,6 +1073,8 @@ export default function trellisExtension(pi: {
|
|
|
904
1073
|
name: "subagent",
|
|
905
1074
|
label: "Subagent",
|
|
906
1075
|
description: "Run a Trellis project sub-agent with active task context.",
|
|
1076
|
+
promptSnippet: SUBAGENT_DISPATCH_PROTOCOL,
|
|
1077
|
+
promptGuidelines: SUBAGENT_DISPATCH_PROTOCOL,
|
|
907
1078
|
parameters: {
|
|
908
1079
|
type: "object",
|
|
909
1080
|
properties: {
|
|
@@ -976,8 +1147,9 @@ export default function trellisExtension(pi: {
|
|
|
976
1147
|
ctx,
|
|
977
1148
|
contextKey,
|
|
978
1149
|
);
|
|
1150
|
+
const perTurn = buildPerTurnInjection(contextKey);
|
|
979
1151
|
return {
|
|
980
|
-
systemPrompt: [current, context].filter(Boolean).join("\n\n"),
|
|
1152
|
+
systemPrompt: [current, context, perTurn].filter(Boolean).join("\n\n"),
|
|
981
1153
|
};
|
|
982
1154
|
});
|
|
983
1155
|
pi.on?.("context", (event, ctx) => {
|
|
@@ -986,8 +1158,11 @@ export default function trellisExtension(pi: {
|
|
|
986
1158
|
return Array.isArray(messages) ? { messages } : undefined;
|
|
987
1159
|
});
|
|
988
1160
|
pi.on?.("input", (event, ctx) => {
|
|
989
|
-
getContextKey(event, ctx);
|
|
990
|
-
|
|
1161
|
+
const contextKey = getContextKey(event, ctx);
|
|
1162
|
+
const additionalContext = buildPerTurnInjection(contextKey);
|
|
1163
|
+
return additionalContext
|
|
1164
|
+
? { action: "continue", additionalContext, systemPrompt: additionalContext }
|
|
1165
|
+
: { action: "continue" };
|
|
991
1166
|
});
|
|
992
1167
|
pi.on?.("tool_call", (event, ctx) => {
|
|
993
1168
|
const contextKey = getContextKey(event, ctx);
|
|
@@ -14,6 +14,24 @@ session_commit_message: "chore: record journal"
|
|
|
14
14
|
# Maximum lines per journal file before rotating to a new one
|
|
15
15
|
max_journal_lines: 2000
|
|
16
16
|
|
|
17
|
+
#-------------------------------------------------------------------------------
|
|
18
|
+
# Session Auto-Commit
|
|
19
|
+
#-------------------------------------------------------------------------------
|
|
20
|
+
|
|
21
|
+
# Auto-commit behavior for session journal + task archive operations.
|
|
22
|
+
# - true (default): scripts auto-stage and auto-commit journal / task changes
|
|
23
|
+
# after add_session.py / task.py archive runs.
|
|
24
|
+
# - false: scripts do not touch git. Files (journal-*.md, task archive moves)
|
|
25
|
+
# are still written to disk; you decide whether to git add / commit.
|
|
26
|
+
#
|
|
27
|
+
# Use `false` if your project's .gitignore intentionally excludes `.trellis/`
|
|
28
|
+
# and you want session data kept local-only, or if you prefer to review
|
|
29
|
+
# staged changes manually before each commit.
|
|
30
|
+
#
|
|
31
|
+
# Accepts: true / false / yes / no / 1 / 0 / on / off (case-insensitive).
|
|
32
|
+
#
|
|
33
|
+
# session_auto_commit: true
|
|
34
|
+
|
|
17
35
|
#-------------------------------------------------------------------------------
|
|
18
36
|
# Task Lifecycle Hooks
|
|
19
37
|
#-------------------------------------------------------------------------------
|
|
@@ -36,6 +36,7 @@ export declare const commonSessionContext: string;
|
|
|
36
36
|
export declare const commonPackagesContext: string;
|
|
37
37
|
export declare const commonWorkflowPhase: string;
|
|
38
38
|
export declare const commonTrellisConfig: string;
|
|
39
|
+
export declare const commonSafeCommit: string;
|
|
39
40
|
export declare const getDeveloperScript: string;
|
|
40
41
|
export declare const initDeveloperScript: string;
|
|
41
42
|
export declare const taskScript: string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/templates/trellis/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAcH,eAAO,MAAM,WAAW,QAAsC,CAAC;AAG/D,eAAO,MAAM,UAAU,QAA6C,CAAC;AACrE,eAAO,MAAM,WAAW,QAA0C,CAAC;AACnE,eAAO,MAAM,eAAe,QAA8C,CAAC;AAC3E,eAAO,MAAM,gBAAgB,QAAgD,CAAC;AAC9E,eAAO,MAAM,eAAe,QAA+C,CAAC;AAC5E,eAAO,MAAM,eAAe,QAA+C,CAAC;AAC5E,eAAO,MAAM,gBAAgB,QAAgD,CAAC;AAC9E,eAAO,MAAM,gBAAgB,QAAgD,CAAC;AAC9E,eAAO,MAAM,YAAY,QAA2C,CAAC;AACrE,eAAO,MAAM,QAAQ,QAAuC,CAAC;AAC7D,eAAO,MAAM,SAAS,QAAwC,CAAC;AAC/D,eAAO,MAAM,SAAS,QAAwC,CAAC;AAC/D,eAAO,MAAM,WAAW,QAA0C,CAAC;AACnE,eAAO,MAAM,WAAW,QAA0C,CAAC;AACnE,eAAO,MAAM,iBAAiB,QAAiD,CAAC;AAChF,eAAO,MAAM,eAAe,QAA+C,CAAC;AAC5E,eAAO,MAAM,oBAAoB,QAEhC,CAAC;AACF,eAAO,MAAM,qBAAqB,QAEjC,CAAC;AACF,eAAO,MAAM,mBAAmB,QAE/B,CAAC;AACF,eAAO,MAAM,mBAAmB,QAE/B,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/templates/trellis/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAcH,eAAO,MAAM,WAAW,QAAsC,CAAC;AAG/D,eAAO,MAAM,UAAU,QAA6C,CAAC;AACrE,eAAO,MAAM,WAAW,QAA0C,CAAC;AACnE,eAAO,MAAM,eAAe,QAA8C,CAAC;AAC3E,eAAO,MAAM,gBAAgB,QAAgD,CAAC;AAC9E,eAAO,MAAM,eAAe,QAA+C,CAAC;AAC5E,eAAO,MAAM,eAAe,QAA+C,CAAC;AAC5E,eAAO,MAAM,gBAAgB,QAAgD,CAAC;AAC9E,eAAO,MAAM,gBAAgB,QAAgD,CAAC;AAC9E,eAAO,MAAM,YAAY,QAA2C,CAAC;AACrE,eAAO,MAAM,QAAQ,QAAuC,CAAC;AAC7D,eAAO,MAAM,SAAS,QAAwC,CAAC;AAC/D,eAAO,MAAM,SAAS,QAAwC,CAAC;AAC/D,eAAO,MAAM,WAAW,QAA0C,CAAC;AACnE,eAAO,MAAM,WAAW,QAA0C,CAAC;AACnE,eAAO,MAAM,iBAAiB,QAAiD,CAAC;AAChF,eAAO,MAAM,eAAe,QAA+C,CAAC;AAC5E,eAAO,MAAM,oBAAoB,QAEhC,CAAC;AACF,eAAO,MAAM,qBAAqB,QAEjC,CAAC;AACF,eAAO,MAAM,mBAAmB,QAE/B,CAAC;AACF,eAAO,MAAM,mBAAmB,QAE/B,CAAC;AACF,eAAO,MAAM,gBAAgB,QAAgD,CAAC;AAG9E,eAAO,MAAM,kBAAkB,QAA2C,CAAC;AAC3E,eAAO,MAAM,mBAAmB,QAA4C,CAAC;AAC7E,eAAO,MAAM,UAAU,QAAkC,CAAC;AAC1D,eAAO,MAAM,gBAAgB,QAAyC,CAAC;AACvE,eAAO,MAAM,gBAAgB,QAAyC,CAAC;AAGvE,eAAO,MAAM,kBAAkB,QAA8B,CAAC;AAC9D,eAAO,MAAM,kBAAkB,QAA8B,CAAC;AAC9D,eAAO,MAAM,iBAAiB,QAAgC,CAAC;AAE/D;;GAEG;AACH,wBAAgB,aAAa,IAAI,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAqCnD"}
|
|
@@ -46,6 +46,7 @@ export const commonSessionContext = readTemplate("scripts/common/session_context
|
|
|
46
46
|
export const commonPackagesContext = readTemplate("scripts/common/packages_context.py");
|
|
47
47
|
export const commonWorkflowPhase = readTemplate("scripts/common/workflow_phase.py");
|
|
48
48
|
export const commonTrellisConfig = readTemplate("scripts/common/trellis_config.py");
|
|
49
|
+
export const commonSafeCommit = readTemplate("scripts/common/safe_commit.py");
|
|
49
50
|
// Python scripts - main
|
|
50
51
|
export const getDeveloperScript = readTemplate("scripts/get_developer.py");
|
|
51
52
|
export const initDeveloperScript = readTemplate("scripts/init_developer.py");
|
|
@@ -84,6 +85,7 @@ export function getAllScripts() {
|
|
|
84
85
|
scripts.set("common/packages_context.py", commonPackagesContext);
|
|
85
86
|
scripts.set("common/workflow_phase.py", commonWorkflowPhase);
|
|
86
87
|
scripts.set("common/trellis_config.py", commonTrellisConfig);
|
|
88
|
+
scripts.set("common/safe_commit.py", commonSafeCommit);
|
|
87
89
|
// Main
|
|
88
90
|
scripts.set("get_developer.py", getDeveloperScript);
|
|
89
91
|
scripts.set("init_developer.py", initDeveloperScript);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/templates/trellis/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAEH,OAAO,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AACvC,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;AAED,gCAAgC;AAChC,MAAM,CAAC,MAAM,WAAW,GAAG,YAAY,CAAC,qBAAqB,CAAC,CAAC;AAE/D,0BAA0B;AAC1B,MAAM,CAAC,MAAM,UAAU,GAAG,YAAY,CAAC,4BAA4B,CAAC,CAAC;AACrE,MAAM,CAAC,MAAM,WAAW,GAAG,YAAY,CAAC,yBAAyB,CAAC,CAAC;AACnE,MAAM,CAAC,MAAM,eAAe,GAAG,YAAY,CAAC,6BAA6B,CAAC,CAAC;AAC3E,MAAM,CAAC,MAAM,gBAAgB,GAAG,YAAY,CAAC,+BAA+B,CAAC,CAAC;AAC9E,MAAM,CAAC,MAAM,eAAe,GAAG,YAAY,CAAC,8BAA8B,CAAC,CAAC;AAC5E,MAAM,CAAC,MAAM,eAAe,GAAG,YAAY,CAAC,8BAA8B,CAAC,CAAC;AAC5E,MAAM,CAAC,MAAM,gBAAgB,GAAG,YAAY,CAAC,+BAA+B,CAAC,CAAC;AAC9E,MAAM,CAAC,MAAM,gBAAgB,GAAG,YAAY,CAAC,+BAA+B,CAAC,CAAC;AAC9E,MAAM,CAAC,MAAM,YAAY,GAAG,YAAY,CAAC,0BAA0B,CAAC,CAAC;AACrE,MAAM,CAAC,MAAM,QAAQ,GAAG,YAAY,CAAC,sBAAsB,CAAC,CAAC;AAC7D,MAAM,CAAC,MAAM,SAAS,GAAG,YAAY,CAAC,uBAAuB,CAAC,CAAC;AAC/D,MAAM,CAAC,MAAM,SAAS,GAAG,YAAY,CAAC,uBAAuB,CAAC,CAAC;AAC/D,MAAM,CAAC,MAAM,WAAW,GAAG,YAAY,CAAC,yBAAyB,CAAC,CAAC;AACnE,MAAM,CAAC,MAAM,WAAW,GAAG,YAAY,CAAC,yBAAyB,CAAC,CAAC;AACnE,MAAM,CAAC,MAAM,iBAAiB,GAAG,YAAY,CAAC,gCAAgC,CAAC,CAAC;AAChF,MAAM,CAAC,MAAM,eAAe,GAAG,YAAY,CAAC,8BAA8B,CAAC,CAAC;AAC5E,MAAM,CAAC,MAAM,oBAAoB,GAAG,YAAY,CAC9C,mCAAmC,CACpC,CAAC;AACF,MAAM,CAAC,MAAM,qBAAqB,GAAG,YAAY,CAC/C,oCAAoC,CACrC,CAAC;AACF,MAAM,CAAC,MAAM,mBAAmB,GAAG,YAAY,CAC7C,kCAAkC,CACnC,CAAC;AACF,MAAM,CAAC,MAAM,mBAAmB,GAAG,YAAY,CAC7C,kCAAkC,CACnC,CAAC;
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/templates/trellis/index.ts"],"names":[],"mappings":"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"}
|
|
@@ -23,7 +23,6 @@ from __future__ import annotations
|
|
|
23
23
|
|
|
24
24
|
import argparse
|
|
25
25
|
import re
|
|
26
|
-
import subprocess
|
|
27
26
|
import sys
|
|
28
27
|
from datetime import datetime
|
|
29
28
|
from pathlib import Path
|
|
@@ -37,9 +36,15 @@ from common.paths import (
|
|
|
37
36
|
)
|
|
38
37
|
from common.developer import ensure_developer
|
|
39
38
|
from common.git import run_git
|
|
39
|
+
from common.safe_commit import (
|
|
40
|
+
print_gitignore_warning,
|
|
41
|
+
safe_git_add,
|
|
42
|
+
safe_trellis_paths_to_add,
|
|
43
|
+
)
|
|
40
44
|
from common.tasks import load_task
|
|
41
45
|
from common.config import (
|
|
42
46
|
get_packages,
|
|
47
|
+
get_session_auto_commit,
|
|
43
48
|
get_session_commit_message,
|
|
44
49
|
get_max_journal_lines,
|
|
45
50
|
is_monorepo,
|
|
@@ -314,36 +319,57 @@ def update_index(
|
|
|
314
319
|
# =============================================================================
|
|
315
320
|
|
|
316
321
|
def _auto_commit_workspace(repo_root: Path) -> None:
|
|
317
|
-
"""Stage
|
|
322
|
+
"""Stage Trellis-owned workspace + task paths and commit.
|
|
323
|
+
|
|
324
|
+
Path scope is restricted to specific products (journal files, index.md,
|
|
325
|
+
active task dirs, the archive subtree). We never `git add` the whole
|
|
326
|
+
`.trellis/` tree, and if `.gitignore` blocks the specific paths we
|
|
327
|
+
warn + skip — never retry with ``-f``.
|
|
328
|
+
|
|
329
|
+
Honors ``session_auto_commit`` in ``.trellis/config.yaml``: when set to
|
|
330
|
+
``false``, this function returns immediately without touching git
|
|
331
|
+
(journal/index files are still written to disk by the caller).
|
|
332
|
+
"""
|
|
333
|
+
if not get_session_auto_commit(repo_root):
|
|
334
|
+
print(
|
|
335
|
+
"[OK] session_auto_commit: false — skipping git stage/commit.",
|
|
336
|
+
file=sys.stderr,
|
|
337
|
+
)
|
|
338
|
+
return
|
|
339
|
+
|
|
318
340
|
commit_msg = get_session_commit_message(repo_root)
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
)
|
|
325
|
-
if
|
|
326
|
-
|
|
327
|
-
|
|
341
|
+
paths = safe_trellis_paths_to_add(repo_root)
|
|
342
|
+
if not paths:
|
|
343
|
+
print("[OK] No workspace changes to commit.", file=sys.stderr)
|
|
344
|
+
return
|
|
345
|
+
|
|
346
|
+
success, _, err = safe_git_add(paths, repo_root)
|
|
347
|
+
if not success:
|
|
348
|
+
if err and "ignored by" in err.lower():
|
|
349
|
+
print_gitignore_warning(paths)
|
|
350
|
+
else:
|
|
351
|
+
print(
|
|
352
|
+
f"[WARN] git add failed: {err.strip() if err else 'unknown error'}",
|
|
353
|
+
file=sys.stderr,
|
|
354
|
+
)
|
|
328
355
|
return
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
cwd=repo_root
|
|
356
|
+
|
|
357
|
+
# Check if there are staged changes for the paths we just staged.
|
|
358
|
+
rc, _, _ = run_git(
|
|
359
|
+
["diff", "--cached", "--quiet", "--", *paths], cwd=repo_root
|
|
333
360
|
)
|
|
334
|
-
if
|
|
361
|
+
if rc == 0:
|
|
335
362
|
print("[OK] No workspace changes to commit.", file=sys.stderr)
|
|
336
363
|
return
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
capture_output=True,
|
|
341
|
-
text=True,
|
|
342
|
-
)
|
|
343
|
-
if commit_result.returncode == 0:
|
|
364
|
+
|
|
365
|
+
rc, _, commit_err = run_git(["commit", "-m", commit_msg], cwd=repo_root)
|
|
366
|
+
if rc == 0:
|
|
344
367
|
print(f"[OK] Auto-committed: {commit_msg}", file=sys.stderr)
|
|
345
368
|
else:
|
|
346
|
-
print(
|
|
369
|
+
print(
|
|
370
|
+
f"[WARN] Auto-commit failed: {commit_err.strip()}",
|
|
371
|
+
file=sys.stderr,
|
|
372
|
+
)
|
|
347
373
|
|
|
348
374
|
|
|
349
375
|
def add_session(
|
|
@@ -36,6 +36,29 @@ def _unquote(s: str) -> str:
|
|
|
36
36
|
return s
|
|
37
37
|
|
|
38
38
|
|
|
39
|
+
def _strip_inline_comment(value: str) -> str:
|
|
40
|
+
"""Strip ` # …` inline comments while preserving `#` inside quoted strings.
|
|
41
|
+
|
|
42
|
+
YAML treats ` #` (space-hash) as a comment opener; bare `#` inside a token
|
|
43
|
+
is part of the value. Quoted strings are immune.
|
|
44
|
+
|
|
45
|
+
Mirrors :func:`common.trellis_config._strip_inline_comment` so both
|
|
46
|
+
parsers handle ``key: value # comment`` identically.
|
|
47
|
+
"""
|
|
48
|
+
in_quote: str | None = None
|
|
49
|
+
for idx, ch in enumerate(value):
|
|
50
|
+
if in_quote:
|
|
51
|
+
if ch == in_quote:
|
|
52
|
+
in_quote = None
|
|
53
|
+
continue
|
|
54
|
+
if ch in ('"', "'"):
|
|
55
|
+
in_quote = ch
|
|
56
|
+
continue
|
|
57
|
+
if ch == "#" and (idx == 0 or value[idx - 1].isspace()):
|
|
58
|
+
return value[:idx]
|
|
59
|
+
return value
|
|
60
|
+
|
|
61
|
+
|
|
39
62
|
def parse_simple_yaml(content: str) -> dict:
|
|
40
63
|
"""Parse simple YAML with nested dict support (no dependencies).
|
|
41
64
|
|
|
@@ -93,7 +116,8 @@ def _parse_yaml_block(
|
|
|
93
116
|
elif ":" in stripped:
|
|
94
117
|
key, _, value = stripped.partition(":")
|
|
95
118
|
key = key.strip()
|
|
96
|
-
value =
|
|
119
|
+
value = _strip_inline_comment(value).strip()
|
|
120
|
+
value = _unquote(value)
|
|
97
121
|
current_list = None
|
|
98
122
|
|
|
99
123
|
if value:
|
|
@@ -142,6 +166,7 @@ def _next_content_line(lines: list[str], start: int) -> tuple[int, str]:
|
|
|
142
166
|
# Defaults
|
|
143
167
|
DEFAULT_SESSION_COMMIT_MESSAGE = "chore: record journal"
|
|
144
168
|
DEFAULT_MAX_JOURNAL_LINES = 2000
|
|
169
|
+
DEFAULT_SESSION_AUTO_COMMIT = True
|
|
145
170
|
|
|
146
171
|
CONFIG_FILE = "config.yaml"
|
|
147
172
|
|
|
@@ -187,6 +212,37 @@ def get_max_journal_lines(repo_root: Path | None = None) -> int:
|
|
|
187
212
|
return DEFAULT_MAX_JOURNAL_LINES
|
|
188
213
|
|
|
189
214
|
|
|
215
|
+
def get_session_auto_commit(repo_root: Path | None = None) -> bool:
|
|
216
|
+
"""Whether scripts should auto-stage + auto-commit session/task changes.
|
|
217
|
+
|
|
218
|
+
Governs both ``add_session.py:_auto_commit_workspace`` and
|
|
219
|
+
``task_store.py:_auto_commit_archive``.
|
|
220
|
+
|
|
221
|
+
Default: ``True`` (existing behavior — auto-stage + auto-commit).
|
|
222
|
+
Set ``session_auto_commit: false`` in ``.trellis/config.yaml`` to skip
|
|
223
|
+
auto-staging entirely; the journal/archive files are still written to
|
|
224
|
+
disk, but the user manages ``git add`` / ``git commit`` themselves.
|
|
225
|
+
|
|
226
|
+
Accepts native YAML booleans (``true`` / ``false``) and the string
|
|
227
|
+
aliases ``true / false / yes / no / 1 / 0 / on / off`` (case-insensitive).
|
|
228
|
+
Invalid values fall back to ``True`` with a stderr warning.
|
|
229
|
+
"""
|
|
230
|
+
config = _load_config(repo_root)
|
|
231
|
+
raw = config.get("session_auto_commit", DEFAULT_SESSION_AUTO_COMMIT)
|
|
232
|
+
if isinstance(raw, bool):
|
|
233
|
+
return raw
|
|
234
|
+
s = str(raw).strip().lower()
|
|
235
|
+
if s in ("true", "yes", "1", "on"):
|
|
236
|
+
return True
|
|
237
|
+
if s in ("false", "no", "0", "off"):
|
|
238
|
+
return False
|
|
239
|
+
print(
|
|
240
|
+
f"[WARN] invalid session_auto_commit value: {raw!r}; using true (default)",
|
|
241
|
+
file=sys.stderr,
|
|
242
|
+
)
|
|
243
|
+
return DEFAULT_SESSION_AUTO_COMMIT
|
|
244
|
+
|
|
245
|
+
|
|
190
246
|
def get_hooks(event: str, repo_root: Path | None = None) -> list[str]:
|
|
191
247
|
"""Get hook commands for a lifecycle event.
|
|
192
248
|
|