flower-trellis 0.6.10-beta.4 → 0.6.10-beta.5

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.
@@ -603,6 +603,17 @@ def _tracked_files(repo_root: Path, relative: str) -> list[str]:
603
603
  return sorted(path for path in output.split("\0") if path)
604
604
 
605
605
 
606
+ def _untracked_files(repo_root: Path, relative: str) -> list[str]:
607
+ """列出路径下可由 Git 纳管的未跟踪文件。"""
608
+ code, output, error = run_git(
609
+ ["ls-files", "-z", "--others", "--exclude-standard", "--", relative],
610
+ cwd=repo_root,
611
+ )
612
+ if code != 0:
613
+ raise TaskLifecycleError("git-ls-files-failed", error.strip() or "无法读取未跟踪文件")
614
+ return sorted(path for path in output.split("\0") if path)
615
+
616
+
606
617
  def _run_git_with_index(
607
618
  args: list[str],
608
619
  repo_root: Path,
@@ -1245,18 +1256,36 @@ def _write_batch_and_commit(
1245
1256
  message: str,
1246
1257
  baseline: dict[str, str],
1247
1258
  transaction: dict[str, Any],
1259
+ *,
1260
+ commit_paths: Optional[list[str]] = None,
1261
+ expected_files: Optional[set[str]] = None,
1248
1262
  ) -> str:
1249
- """原子写入一批 JSON,并以可恢复的精确 pathspec 创建本地提交。"""
1263
+ """原子写入一批 JSON,并以可恢复的精确 pathspec 创建本地提交。
1264
+
1265
+ Args:
1266
+ repo_root: Git worktree 根目录。
1267
+ writes: 需要原子写入的 JSON 路径与数据。
1268
+ message: maintenance commit 消息。
1269
+ baseline: 事务开始时固定的完整分支引用与 HEAD。
1270
+ transaction: 操作类型与可恢复结果。
1271
+ commit_paths: 可选的精确提交 pathspec;缺省仅提交写入的 JSON。
1272
+ expected_files: 可选的预期提交文件全集;缺省等于写入的 JSON。
1273
+
1274
+ Returns:
1275
+ 新创建并绑定到固定分支的提交哈希。
1276
+ """
1250
1277
  originals = [(path, path.read_bytes()) for path, _ in writes]
1251
1278
  relative_paths = [_task_relative(repo_root, path) for path, _ in writes]
1279
+ exact_paths = relative_paths if commit_paths is None else commit_paths
1280
+ exact_files = set(relative_paths) if expected_files is None else expected_files
1252
1281
  try:
1253
1282
  for path, data in writes:
1254
1283
  if not write_json(path, data):
1255
1284
  raise TaskLifecycleError("write-failed", f"无法写入:{_task_relative(repo_root, path)}")
1256
1285
  return _commit_exact_paths(
1257
1286
  repo_root,
1258
- relative_paths,
1259
- set(relative_paths),
1287
+ exact_paths,
1288
+ exact_files,
1260
1289
  message,
1261
1290
  baseline,
1262
1291
  transaction,
@@ -1337,18 +1366,31 @@ def reconcile_legacy_tasks(repo_root: Path, migration_time: Optional[str] = None
1337
1366
  return result
1338
1367
  timestamp = migration_time or utc_now()
1339
1368
  writes: list[tuple[Path, dict[str, Any]]] = []
1369
+ commit_paths: list[str] = []
1370
+ expected_files: set[str] = set()
1340
1371
  for task_dir, data in legacy_dirs:
1341
1372
  task_json = task_dir / FILE_TASK_JSON
1342
1373
  task_ref = _task_relative(repo_root, task_dir)
1374
+ task_json_ref = f"{task_ref}/{FILE_TASK_JSON}"
1343
1375
  path_status = _git_path_status(repo_root, [task_ref])
1376
+ status_records = _status_records(path_status)
1344
1377
  dirty_paths = {
1345
1378
  path
1346
- for _, paths in _status_records(path_status)
1379
+ for _, paths in status_records
1347
1380
  for path in paths
1348
1381
  }
1382
+ tracked_files = _tracked_files(repo_root, task_ref)
1383
+ untracked_files = set(_untracked_files(repo_root, task_ref)) if not tracked_files else set()
1384
+ entirely_untracked = bool(
1385
+ status_records
1386
+ and not tracked_files
1387
+ and task_json_ref in untracked_files
1388
+ and dirty_paths == untracked_files
1389
+ and all(git_status == "??" for git_status, _ in status_records)
1390
+ )
1349
1391
  runner_owned = (
1350
1392
  task_ref in recorded
1351
- and dirty_paths == {f"{task_ref}/{FILE_TASK_JSON}"}
1393
+ and dirty_paths == {task_json_ref}
1352
1394
  and (
1353
1395
  recorded_digests.get(task_ref) == _task_json_sha256(task_json)
1354
1396
  or _legacy_runner_bookkeeping_matches(
@@ -1359,10 +1401,10 @@ def reconcile_legacy_tasks(repo_root: Path, migration_time: Optional[str] = None
1359
1401
  )
1360
1402
  )
1361
1403
  )
1362
- if path_status and not runner_owned:
1404
+ if path_status and not runner_owned and not entirely_untracked:
1363
1405
  result["deferred"].append({"task": task_ref, "reason": "candidate-dirty"})
1364
1406
  continue
1365
- if not _tracked_files(repo_root, task_ref):
1407
+ if not tracked_files and not entirely_untracked:
1366
1408
  result["deferred"].append({"task": task_ref, "reason": "candidate-untracked"})
1367
1409
  continue
1368
1410
  next_data = dict(data)
@@ -1375,7 +1417,7 @@ def reconcile_legacy_tasks(repo_root: Path, migration_time: Optional[str] = None
1375
1417
  task_dir,
1376
1418
  repo_root,
1377
1419
  next_data,
1378
- delivery_verified=not path_status or runner_owned,
1420
+ delivery_verified=not path_status or runner_owned or entirely_untracked,
1379
1421
  closed_at=timestamp,
1380
1422
  )
1381
1423
  if evaluated["status"] == "error":
@@ -1389,6 +1431,13 @@ def reconcile_legacy_tasks(repo_root: Path, migration_time: Optional[str] = None
1389
1431
  result["deferred"].append({"task": task_ref, "reason": "unknown-task-status"})
1390
1432
  continue
1391
1433
  writes.append((task_json, next_data))
1434
+ if entirely_untracked:
1435
+ # 新任务尚无 HEAD 基线,只能把可纳管的完整目录作为一个原子任务记录提交。
1436
+ commit_paths.append(task_ref)
1437
+ expected_files.update(untracked_files)
1438
+ else:
1439
+ commit_paths.append(task_json_ref)
1440
+ expected_files.add(task_json_ref)
1392
1441
  result["migrated"].append({"task": task_ref, "closeout": migration_status})
1393
1442
  if writes:
1394
1443
  result["commit"] = _write_batch_and_commit(
@@ -1397,6 +1446,8 @@ def reconcile_legacy_tasks(repo_root: Path, migration_time: Optional[str] = None
1397
1446
  "chore(task): reconcile closeout metadata",
1398
1447
  baseline,
1399
1448
  {"operation": "reconciliation", "result": result},
1449
+ commit_paths=commit_paths,
1450
+ expected_files=expected_files,
1400
1451
  )
1401
1452
  return result
1402
1453
 
@@ -1,7 +1,7 @@
1
1
  {
2
- "syncedAt": "2026-09-23T07:35:26.298Z",
2
+ "syncedAt": "2026-09-23T09:09:05.054Z",
3
3
  "syncedFrom": "vendor/skill-garden",
4
- "sourceCommit": "4ac24e07dffb6fa8d6e32a1344394881da9c7f15",
4
+ "sourceCommit": "c2a53653f8b80d2ff96ab6d340aec31f54eae864",
5
5
  "common": {
6
6
  "codexSkills": [
7
7
  "aliyun-ops",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "flower-trellis",
3
- "version": "0.6.10-beta.4",
3
+ "version": "0.6.10-beta.5",
4
4
  "description": "一键安装/升级 Trellis 并自动融合 skill-garden 强化包(默认 Claude + agents)",
5
5
  "type": "module",
6
6
  "bin": {
@@ -70,9 +70,9 @@
70
70
  "commit-and-tag-version": "^12.7.3"
71
71
  },
72
72
  "flowerReleaseNotes": {
73
- "version": "0.6.10-beta.4",
73
+ "version": "0.6.10-beta.5",
74
74
  "source": "CHANGELOG.md",
75
- "body": "### ✨ 新功能 Features\n\n* **aliyun-ops:** 集成 ACK Workbench 只读查询 ([224ef02](https://github.com/SilentFlower/flower-trellis/commit/224ef029beb82e032bb4e336bdc7febe29a96982))\n* **session:** 精简 SessionStart 开场注入 ([2b68c13](https://github.com/SilentFlower/flower-trellis/commit/2b68c1367199cdb3254d0adc639db20cc0b2b648))\n* **task:** 重构 Close 与物理 GC 生命周期 ([20c14f0](https://github.com/SilentFlower/flower-trellis/commit/20c14f032825eac226887c4334ef6e6e69ffc3dd))\n\n\n### 🐛 修复 Bug Fixes\n\n* **aliyun-ops:** 完善 SLS 引导与跨平台回归 ([e146855](https://github.com/SilentFlower/flower-trellis/commit/e146855122f91ef48c1155baf3aaf63681a0557b))\n* **release-ci:** 修复无依赖发布快照同步 ([a5c954a](https://github.com/SilentFlower/flower-trellis/commit/a5c954aee20d85d455f74583a0597e6b859223c3))",
75
+ "body": "### ✨ 新功能 Features\n\n* **task:** 支持精确收敛未跟踪旧任务 ([4b6871e](https://github.com/SilentFlower/flower-trellis/commit/4b6871e22abc3566aca9373d480ab9bf3d516bd3))",
76
76
  "truncated": false
77
77
  },
78
78
  "optionalDependencies": {