@mindfoldhq/trellis 0.6.0-beta.5 → 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.
@@ -0,0 +1,16 @@
1
+ {
2
+ "version": "0.5.11",
3
+ "description": "Patch: drop 0.5.10 `git add -f` retry + new `session_auto_commit` config + session-start update hint.",
4
+ "breaking": false,
5
+ "recommendMigrate": false,
6
+ "changelog": "**Bug Fixes:**\n- fix(scripts): drop `git add -f` auto-retry from 0.5.10. When `.gitignore` excludes `.trellis/`, `add_session.py` and `task.py archive` print a warning and skip auto-commit instead of force-staging.\n\n**Enhancements:**\n- feat(scripts): new `session_auto_commit: true | false` in `.trellis/config.yaml` (default `true`). Set `false` to skip auto stage + commit — journal / archive files still write to disk. Closes #245.\n- feat(scripts): `get_context.py` shows `Trellis update available: <current> -> <latest>` once per session when local install lags. 1-second timeout, failures silent. Closes #254.",
7
+ "migrations": [],
8
+ "configSectionsAdded": [
9
+ {
10
+ "file": ".trellis/config.yaml",
11
+ "sentinel": "session_auto_commit:",
12
+ "sectionHeading": "Session Auto-Commit"
13
+ }
14
+ ],
15
+ "notes": "Patch on top of 0.5.10. Run `trellis update` — your `.trellis/config.yaml` gets a commented-out `session_auto_commit: true` block appended automatically. Uncomment and flip to `false` if your `.gitignore` excludes `.trellis/` and you want auto-commit off entirely."
16
+ }
@@ -0,0 +1,16 @@
1
+ {
2
+ "version": "0.6.0-beta.6",
3
+ "description": "Beta patch: brings v0.5.11 fixes into 0.6 beta line — drops `git add -f` auto-retry + new `session_auto_commit` config.",
4
+ "breaking": false,
5
+ "recommendMigrate": false,
6
+ "changelog": "**Bug Fixes:**\n- fix(scripts): drop `git add -f` auto-retry from 0.6.0-beta.5. When `.gitignore` excludes `.trellis/`, `add_session.py` and `task.py archive` print a warning and skip auto-commit instead of force-staging.\n\n**Enhancements:**\n- feat(scripts): new `session_auto_commit: true | false` in `.trellis/config.yaml` (default `true`). Set `false` to skip auto stage + commit — journal / archive files still write to disk. Closes #245.",
7
+ "migrations": [],
8
+ "configSectionsAdded": [
9
+ {
10
+ "file": ".trellis/config.yaml",
11
+ "sentinel": "session_auto_commit:",
12
+ "sectionHeading": "Session Auto-Commit"
13
+ }
14
+ ],
15
+ "notes": "Beta patch on top of 0.6.0-beta.5. Run `trellis update` — your `.trellis/config.yaml` gets a commented-out `session_auto_commit: true` block appended automatically. Uncomment and flip to `false` if your `.gitignore` excludes `.trellis/` and you want auto-commit off entirely."
16
+ }
@@ -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
  #-------------------------------------------------------------------------------
@@ -44,6 +44,7 @@ from common.safe_commit import (
44
44
  from common.tasks import load_task
45
45
  from common.config import (
46
46
  get_packages,
47
+ get_session_auto_commit,
47
48
  get_session_commit_message,
48
49
  get_max_journal_lines,
49
50
  is_monorepo,
@@ -322,16 +323,27 @@ def _auto_commit_workspace(repo_root: Path) -> None:
322
323
 
323
324
  Path scope is restricted to specific products (journal files, index.md,
324
325
  active task dirs, the archive subtree). We never `git add` the whole
325
- `.trellis/` tree, and if `.gitignore` blocks the specific paths we retry
326
- with `git add -f <those-specific-paths>` — never `-f .trellis/`.
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).
327
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
+
328
340
  commit_msg = get_session_commit_message(repo_root)
329
341
  paths = safe_trellis_paths_to_add(repo_root)
330
342
  if not paths:
331
343
  print("[OK] No workspace changes to commit.", file=sys.stderr)
332
344
  return
333
345
 
334
- success, used_force, err = safe_git_add(paths, repo_root)
346
+ success, _, err = safe_git_add(paths, repo_root)
335
347
  if not success:
336
348
  if err and "ignored by" in err.lower():
337
349
  print_gitignore_warning(paths)
@@ -342,12 +354,6 @@ def _auto_commit_workspace(repo_root: Path) -> None:
342
354
  )
343
355
  return
344
356
 
345
- if used_force:
346
- print(
347
- "[OK] Staged Trellis-owned paths with -f (specific paths, not .trellis/).",
348
- file=sys.stderr,
349
- )
350
-
351
357
  # Check if there are staged changes for the paths we just staged.
352
358
  rc, _, _ = run_git(
353
359
  ["diff", "--cached", "--quiet", "--", *paths], cwd=repo_root
@@ -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 = _unquote(value.strip())
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
 
@@ -15,14 +15,19 @@ Design
15
15
  ------
16
16
  - Scripts only stage SPECIFIC product paths (journal files, index.md, the
17
17
  current task dir, the archive dir). Never the whole `.trellis/` tree.
18
- - If plain `git add <specific>` fails with "ignored by", retry with
19
- `git add -f <specific>` forcing only the paths the script knows it owns.
20
- This is safe because the paths are narrow; it is NOT equivalent to
21
- `git add -f .trellis/` (which would fan out to backups/worktrees/runtime).
22
- - If the -f retry also fails, print an explicit warning that includes a
23
- negative example: ``Do NOT use `git add -f .trellis/` ...``
24
-
25
- The wider-grain forbidden command stays forbidden.
18
+ - If plain `git add <specific>` fails with "ignored by", DO NOT retry with
19
+ ``-f``. The presence of `.trellis/` in `.gitignore` is treated as user
20
+ intent ("keep .trellis/ local-only"). The script warns and skips the
21
+ auto-commit; users who want auto-staging can either fix their `.gitignore`
22
+ or set ``session_auto_commit: false`` and manage git themselves.
23
+ - The warning includes a negative example: ``Do NOT use `git add -f .trellis/` ...``
24
+ so any AI rereading the log doesn't reinvent the bug.
25
+
26
+ History note: 0.5.10 introduced an automatic ``git add -f`` retry on the
27
+ specific paths. That was reverted in 0.5.11 — auto-forcing into a tree the
28
+ user had gitignored violates user intent even when the path list is narrow.
29
+ The wider-grain forbidden command stays forbidden, and the narrow-grain auto
30
+ ``-f`` is gone too.
26
31
  """
27
32
 
28
33
  from __future__ import annotations
@@ -145,20 +150,18 @@ def _stderr_indicates_ignored(stderr: str) -> bool:
145
150
  def safe_git_add(
146
151
  paths: list[str], repo_root: Path
147
152
  ) -> tuple[bool, bool, str]:
148
- """Run `git add` on specific paths, retrying with -f if .gitignore blocks.
153
+ """Run `git add` on specific paths; never retry with -f.
149
154
 
150
- Returns (success, used_force, stderr). On success, callers should still
151
- `git diff --cached` to detect whether anything was actually staged.
155
+ Returns ``(success, used_force, stderr)``. The ``used_force`` field is
156
+ kept for signature compatibility with the 0.5.10 implementation but is
157
+ always ``False`` — we never auto-force.
152
158
 
153
159
  Behavior:
154
160
  - No paths passed → success, no force, empty stderr.
155
- - Plain `git add <paths>` succeeds → return.
156
- - Plain fails with "ignored by" retry with `git add -f <paths>`.
157
- - Retry succeeds return success with used_force=True.
158
- - Retry fails → return failure; caller should print the gitignore
159
- warning (see :func:`print_gitignore_warning`).
160
- - Plain fails with a non-ignored error → return failure; do NOT retry
161
- with -f (we only force when ignore is the cause).
161
+ - Plain ``git add -- <paths>`` succeeds → return success.
162
+ - Plain fails (any reason ignored or otherwise) return failure with
163
+ the stderr. Callers should inspect the stderr (see
164
+ :func:`print_gitignore_warning`) and skip the auto-commit.
162
165
  """
163
166
  if not paths:
164
167
  return True, False, ""
@@ -166,14 +169,7 @@ def safe_git_add(
166
169
  rc, _, err = run_git(["add", "--", *paths], cwd=repo_root)
167
170
  if rc == 0:
168
171
  return True, False, ""
169
-
170
- if not _stderr_indicates_ignored(err):
171
- return False, False, err
172
-
173
- rc2, _, err2 = run_git(["add", "-f", "--", *paths], cwd=repo_root)
174
- if rc2 == 0:
175
- return True, True, err2 or err
176
- return False, True, err2 or err
172
+ return False, False, err
177
173
 
178
174
 
179
175
  def print_gitignore_warning(paths: list[str]) -> None:
@@ -187,6 +183,15 @@ def print_gitignore_warning(paths: list[str]) -> None:
187
183
  "[WARN] git add failed because .trellis/ paths are ignored by your .gitignore.",
188
184
  file=sys.stderr,
189
185
  )
186
+ print(
187
+ "[WARN] Skipping auto-commit. The journal/task files were still written to disk;",
188
+ file=sys.stderr,
189
+ )
190
+ print(
191
+ "[WARN] git was not touched.",
192
+ file=sys.stderr,
193
+ )
194
+ print("[WARN]", file=sys.stderr)
190
195
  print(
191
196
  "[WARN] Trellis manages these specific paths and they should be tracked:",
192
197
  file=sys.stderr,
@@ -219,6 +224,27 @@ def print_gitignore_warning(paths: list[str]) -> None:
219
224
  for sub in TRELLIS_IGNORED_SUBPATHS:
220
225
  print(f"[WARN] {sub}", file=sys.stderr)
221
226
  print("[WARN]", file=sys.stderr)
227
+ print(
228
+ "[WARN] Or, if you intentionally keep .trellis/ local-only, set in",
229
+ file=sys.stderr,
230
+ )
231
+ print(
232
+ "[WARN] .trellis/config.yaml:",
233
+ file=sys.stderr,
234
+ )
235
+ print(
236
+ "[WARN] session_auto_commit: false",
237
+ file=sys.stderr,
238
+ )
239
+ print(
240
+ "[WARN] so the scripts skip git entirely and you can review / commit",
241
+ file=sys.stderr,
242
+ )
243
+ print(
244
+ "[WARN] manually with `git status` / `git add` / `git commit`.",
245
+ file=sys.stderr,
246
+ )
247
+ print("[WARN]", file=sys.stderr)
222
248
  print(
223
249
  "[WARN] Do NOT use `git add -f .trellis/` — it pulls in backups, worktrees,",
224
250
  file=sys.stderr,
@@ -24,6 +24,7 @@ from pathlib import Path
24
24
 
25
25
  from .config import (
26
26
  get_packages,
27
+ get_session_auto_commit,
27
28
  is_monorepo,
28
29
  resolve_package,
29
30
  validate_package,
@@ -391,16 +392,28 @@ def _auto_commit_archive(task_name: str, repo_root: Path) -> None:
391
392
  """Stage Trellis-owned task paths and commit after archive.
392
393
 
393
394
  Only stages specific subpaths (the archive subtree and active task dirs),
394
- never the whole `.trellis/` tree. If `.gitignore` excludes `.trellis/`,
395
- falls back to `git add -f <specific>` and emits a warning that explicitly
396
- forbids `git add -f .trellis/` (which would fan out to caches/backups).
395
+ never the whole ``.trellis/`` tree. If ``.gitignore`` blocks the paths,
396
+ we warn + skip — we do NOT retry with ``git add -f``. The warning
397
+ explicitly forbids ``git add -f .trellis/`` (which would fan out to
398
+ caches/backups) and points users at ``session_auto_commit: false``.
399
+
400
+ Honors ``session_auto_commit`` in ``.trellis/config.yaml``: when set to
401
+ ``false``, this function returns immediately without touching git
402
+ (the archive directory move on disk is unaffected).
397
403
  """
404
+ if not get_session_auto_commit(repo_root):
405
+ print(
406
+ "[OK] session_auto_commit: false — skipping git stage/commit.",
407
+ file=sys.stderr,
408
+ )
409
+ return
410
+
398
411
  paths = safe_archive_paths_to_add(repo_root)
399
412
  if not paths:
400
413
  print("[OK] No task changes to commit.", file=sys.stderr)
401
414
  return
402
415
 
403
- success, used_force, err = safe_git_add(paths, repo_root)
416
+ success, _, err = safe_git_add(paths, repo_root)
404
417
  if not success:
405
418
  if err and "ignored by" in err.lower():
406
419
  print_gitignore_warning(paths)
@@ -411,12 +424,6 @@ def _auto_commit_archive(task_name: str, repo_root: Path) -> None:
411
424
  )
412
425
  return
413
426
 
414
- if used_force:
415
- print(
416
- "[OK] Staged Trellis-owned paths with -f (specific paths, not .trellis/).",
417
- file=sys.stderr,
418
- )
419
-
420
427
  rc, _, _ = run_git(
421
428
  ["diff", "--cached", "--quiet", "--", *paths], cwd=repo_root
422
429
  )
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mindfoldhq/trellis",
3
- "version": "0.6.0-beta.5",
3
+ "version": "0.6.0-beta.6",
4
4
  "description": "AI capabilities grow like ivy — Trellis provides the structure to guide them along a disciplined path",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",