atris 3.31.0 → 3.33.3

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/lib/task-db.js CHANGED
@@ -469,6 +469,43 @@ function claimTask(db, { id, claimedBy }) {
469
469
  return { claimed: false, reason: 'already_' + row.status, claimed_by: row.claimed_by };
470
470
  }
471
471
 
472
+ function releaseTask(db, { id, actor }) {
473
+ if (!id) throw new Error('id required');
474
+ if (!actor) throw new Error('actor required');
475
+ const row = getTask(db, id);
476
+ if (!row) return { released: false, reason: 'not_found' };
477
+ if (row.status !== 'claimed') {
478
+ return { released: false, reason: `already_${row.status}`, claimed_by: row.claimed_by || null };
479
+ }
480
+ if (row.claimed_by !== actor) {
481
+ return { released: false, reason: 'held_by_other', claimed_by: row.claimed_by || null };
482
+ }
483
+ const now = Date.now();
484
+ const result = withBusyRetry(() => db.prepare(`
485
+ UPDATE tasks
486
+ SET status = 'open',
487
+ claimed_by = NULL,
488
+ claimed_at = NULL,
489
+ updated_at = ?
490
+ WHERE id = ?
491
+ AND status = 'claimed'
492
+ AND claimed_by = ?
493
+ `).run(now, id, actor));
494
+ if (result.changes !== 1) return { released: false, reason: 'stale_task_state', claimed_by: row.claimed_by || null };
495
+ const updated = getTask(db, id);
496
+ const event = appendTaskEvent(db, {
497
+ taskId: id,
498
+ workspaceRoot: updated.workspace_root,
499
+ actor,
500
+ eventType: 'claim_released',
501
+ payload: {
502
+ released_by: actor,
503
+ previous_claimed_by: row.claimed_by || null,
504
+ },
505
+ });
506
+ return { released: true, row: updated, event };
507
+ }
508
+
472
509
  function doneTask(db, { id, status, actor, allowReview = false, action, proof } = {}) {
473
510
  if (!id) throw new Error('id required');
474
511
  const final = status || 'done';
@@ -1198,7 +1235,7 @@ function chatTask(db, { id, actor, content, goal, summary }) {
1198
1235
  };
1199
1236
  }
1200
1237
 
1201
- function reviewTask(db, { id, actor, reward, lesson, nextTask, proof, verify, careerXpEligible = false, clearedFields = [] }) {
1238
+ function reviewTask(db, { id, actor, reward, lesson, nextTask, proof, verify, careerXpEligible = false, clearedFields = [], landing = {} }) {
1202
1239
  if (!id) throw new Error('id required');
1203
1240
  const row = getTask(db, id);
1204
1241
  if (!row) return { reviewed: false, reason: 'not_found' };
@@ -1213,6 +1250,15 @@ function reviewTask(db, { id, actor, reward, lesson, nextTask, proof, verify, ca
1213
1250
  const clearedReviewFields = Array.isArray(clearedFields)
1214
1251
  ? Array.from(new Set(clearedFields.filter(field => field === 'lesson' || field === 'next_task')))
1215
1252
  : [];
1253
+ const landingInput = landing && typeof landing === 'object' ? landing : {};
1254
+ let landingUpdated = false;
1255
+ for (const key of ['happened', 'checked', 'tested', 'decision']) {
1256
+ const cleaned = cleanLandingText(landingInput[key]);
1257
+ if (cleaned) {
1258
+ metadata[`landing_${key}`] = cleaned;
1259
+ landingUpdated = true;
1260
+ }
1261
+ }
1216
1262
  const reviewingPendingProof = row.status === 'review'
1217
1263
  && metadata.approval_status === 'pending'
1218
1264
  && numericReward <= 0
@@ -1250,7 +1296,7 @@ function reviewTask(db, { id, actor, reward, lesson, nextTask, proof, verify, ca
1250
1296
  metadata.accepted_at = new Date().toISOString();
1251
1297
  metadata.accepted_by = reviewer;
1252
1298
  }
1253
- if (reviewingPendingProof || updatingPendingReviewProof || (numericReward > 0 && row.status === 'done')) {
1299
+ if (reviewingPendingProof || updatingPendingReviewProof || (numericReward > 0 && row.status === 'done') || landingUpdated) {
1254
1300
  withBusyRetry(() => db.prepare(`
1255
1301
  UPDATE tasks
1256
1302
  SET metadata = ?,
@@ -1271,6 +1317,14 @@ function reviewTask(db, { id, actor, reward, lesson, nextTask, proof, verify, ca
1271
1317
  payload.agent_certified = metadata.agent_certified === true;
1272
1318
  payload.agent_certification_policy = metadata.agent_certification_policy || null;
1273
1319
  }
1320
+ if (landingUpdated) {
1321
+ payload.landing = {
1322
+ happened: metadata.landing_happened || null,
1323
+ checked: metadata.landing_checked || null,
1324
+ tested: metadata.landing_tested || null,
1325
+ decision: metadata.landing_decision || null,
1326
+ };
1327
+ }
1274
1328
  if (clearedReviewFields.length) payload.cleared_review_fields = clearedReviewFields;
1275
1329
  const event = appendTaskEvent(db, {
1276
1330
  taskId: id,
@@ -1704,6 +1758,7 @@ module.exports = {
1704
1758
  getTask,
1705
1759
  listTasks,
1706
1760
  claimTask,
1761
+ releaseTask,
1707
1762
  backlogTask,
1708
1763
  clearPlanTasks,
1709
1764
  doneTask,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "atris",
3
- "version": "3.31.0",
3
+ "version": "3.33.3",
4
4
  "main": "bin/atris.js",
5
5
  "bin": {
6
6
  "atris": "bin/atris.js",
@@ -12,6 +12,7 @@
12
12
  "cli/*.py",
13
13
  "commands/",
14
14
  "decks/",
15
+ "scripts/agent_worktree.py",
15
16
  "utils/",
16
17
  "lib/",
17
18
  "templates/",
@@ -55,7 +56,7 @@
55
56
  "test": "node --test",
56
57
  "lint:decks": "node scripts/lint-all-decks.js",
57
58
  "publish:release": "node scripts/publish-atris-release.js",
58
- "prepare": "cp scripts/pre-commit .git/hooks/pre-commit 2>/dev/null && chmod +x .git/hooks/pre-commit || true"
59
+ "prepare": "cp scripts/pre-commit .git/hooks/pre-commit 2>/dev/null && cp scripts/commit-msg .git/hooks/commit-msg 2>/dev/null && cp scripts/prepare-commit-msg .git/hooks/prepare-commit-msg 2>/dev/null && chmod +x .git/hooks/pre-commit .git/hooks/commit-msg .git/hooks/prepare-commit-msg || true"
59
60
  },
60
61
  "keywords": [
61
62
  "atrisdev",
@@ -0,0 +1,72 @@
1
+ #!/usr/bin/env python3
2
+ """Compatibility wrapper for older agent worktree instructions.
3
+
4
+ Use the canonical Atris CLI:
5
+ atris worktree start --agent <agent> --task "<task>"
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ import argparse
11
+ import subprocess
12
+ import sys
13
+ from pathlib import Path
14
+
15
+
16
+ def build_parser() -> argparse.ArgumentParser:
17
+ parser = argparse.ArgumentParser(
18
+ prog="scripts/agent_worktree.py",
19
+ description="Compatibility wrapper around `atris worktree start`.",
20
+ )
21
+ subcommands = parser.add_subparsers(dest="command", required=True)
22
+
23
+ create = subcommands.add_parser("create", help="Create an Atris agent worktree.")
24
+ owner = create.add_mutually_exclusive_group(required=True)
25
+ owner.add_argument("--agent")
26
+ owner.add_argument("--member")
27
+ create.add_argument("--task", required=True)
28
+ create.add_argument("--base")
29
+ create.add_argument("--path")
30
+ create.add_argument("--branch")
31
+ create.add_argument("--claim", action="store_true")
32
+ return parser
33
+
34
+
35
+ def main(argv: list[str]) -> int:
36
+ args = build_parser().parse_args(argv)
37
+ repo_root = Path(__file__).resolve().parents[1]
38
+ atris_bin = repo_root / "bin" / "atris.js"
39
+
40
+ # Run the Node CLI directly so this wrapper works before a global `atris`
41
+ # binary is on PATH.
42
+ command = ["node", str(atris_bin), "worktree", "start"]
43
+ display = ["atris", "worktree", "start"]
44
+ if args.member:
45
+ command += ["--member", args.member]
46
+ display += ["--member", args.member]
47
+ else:
48
+ command += ["--agent", args.agent]
49
+ display += ["--agent", args.agent]
50
+ command += ["--task", args.task]
51
+ display += ["--task", args.task]
52
+ if args.base:
53
+ command += ["--base", args.base]
54
+ display += ["--base", args.base]
55
+ if args.path:
56
+ command += ["--path", args.path]
57
+ display += ["--path", args.path]
58
+ if args.branch:
59
+ command += ["--branch", args.branch]
60
+ display += ["--branch", args.branch]
61
+ if args.claim:
62
+ command.append("--claim")
63
+ display.append("--claim")
64
+
65
+ print("compat: scripts/agent_worktree.py maps to:")
66
+ print(f" {' '.join(display)}")
67
+ result = subprocess.run(command)
68
+ return int(result.returncode or 0)
69
+
70
+
71
+ if __name__ == "__main__":
72
+ raise SystemExit(main(sys.argv[1:]))