@starlove/orbit 1.0.2 → 1.0.4

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.
@@ -1,9 +1,10 @@
1
- import { existsSync, rmSync } from "node:fs";
1
+ import { existsSync, rmSync, unlinkSync } from "node:fs";
2
2
  import { basename, dirname, join, relative } from "node:path";
3
+ import { removeOrbitAgentsSection } from "./agents-md.js";
3
4
  import { requireBoardAccess } from "./auth.js";
4
5
  import { backupBoardDatabase, backupRegistry, cancelAutomaticBoardBackup } from "./backups.js";
5
6
  import { closeConnection } from "./db.js";
6
- import { deleteBoard, openBoardDb } from "./registry.js";
7
+ import { deleteBoard, listBoards, openBoardDb } from "./registry.js";
7
8
  import { httpError, normalizePath } from "./util.js";
8
9
 
9
10
  function isInside(parent, child) {
@@ -26,6 +27,24 @@ function boardDbDeleteTargets(boardRow) {
26
27
  return [dbPath, `${dbPath}-wal`, `${dbPath}-shm`, `${dbPath}-journal`];
27
28
  }
28
29
 
30
+ function isLastBoardForRepo(boardRow) {
31
+ const repoRoot = normalizePath(boardRow.repo_path || "");
32
+ return !listBoards().some((row) => row.id !== boardRow.id && normalizePath(row.repo_path || "") === repoRoot);
33
+ }
34
+
35
+ function repoArtifactTargets(boardRow) {
36
+ const repoRoot = normalizePath(boardRow.repo_path || "");
37
+ const dbPath = normalizePath(boardRow.db_path || "");
38
+ const orbitRoot = normalizePath(join(repoRoot, ".orbit"));
39
+ if (!repoRoot || !dbPath || !isInside(orbitRoot, dbPath) || basename(dbPath) !== "board.db") {
40
+ throw httpError(409, "unsafe_board_db_path");
41
+ }
42
+ return {
43
+ orbitRoot,
44
+ skillMd: join(repoRoot, "SKILL-ORBIT.md")
45
+ };
46
+ }
47
+
29
48
  export function deleteRegisteredBoard(boardRow, body, actor) {
30
49
  requireBoardAccess(actor, { slug: boardRow.slug });
31
50
 
@@ -33,14 +52,37 @@ export function deleteRegisteredBoard(boardRow, body, actor) {
33
52
  if (confirmation !== boardRow.slug) throw httpError(400, "board_slug_confirmation_required");
34
53
 
35
54
  const deleteFiles = body?.delete_files !== false;
36
- const targets = deleteFiles ? boardDbDeleteTargets(boardRow) : [];
55
+ const removeRepoArtifacts = deleteFiles && isLastBoardForRepo(boardRow);
56
+ const repoArtifacts = removeRepoArtifacts ? repoArtifactTargets(boardRow) : null;
57
+ const targets = deleteFiles
58
+ ? removeRepoArtifacts
59
+ ? [repoArtifacts.orbitRoot]
60
+ : boardDbDeleteTargets(boardRow)
61
+ : [];
62
+ const artifactResults = [];
37
63
  cancelAutomaticBoardBackup(boardRow.id);
38
64
  if (deleteFiles) backupBoardDatabase(boardRow, openBoardDb(boardRow), "pre-board-delete");
39
65
  backupRegistry("pre-board-delete");
40
66
  closeConnection(boardRow.db_path);
41
67
 
42
68
  for (const target of targets) {
43
- if (existsSync(target)) rmSync(target, { recursive: true, force: true });
69
+ if (existsSync(target)) {
70
+ rmSync(target, { recursive: true, force: true });
71
+ artifactResults.push({ path: target, removed: true });
72
+ }
73
+ }
74
+ if (repoArtifacts?.skillMd && existsSync(repoArtifacts.skillMd)) {
75
+ unlinkSync(repoArtifacts.skillMd);
76
+ artifactResults.push({ path: repoArtifacts.skillMd, removed: true });
77
+ }
78
+ if (removeRepoArtifacts) {
79
+ const agentsCleanup = removeOrbitAgentsSection(boardRow.repo_path);
80
+ artifactResults.push({
81
+ path: agentsCleanup.path,
82
+ removed: agentsCleanup.removed,
83
+ ok: agentsCleanup.ok,
84
+ reason: agentsCleanup.reason
85
+ });
44
86
  }
45
87
 
46
88
  deleteBoard(boardRow.id);
@@ -48,6 +90,8 @@ export function deleteRegisteredBoard(boardRow, body, actor) {
48
90
  ok: true,
49
91
  deleted_board_id: boardRow.id,
50
92
  deleted_board_slug: boardRow.slug,
51
- deleted_files: deleteFiles
93
+ deleted_files: deleteFiles,
94
+ deleted_repo_artifacts: removeRepoArtifacts,
95
+ artifacts: artifactResults
52
96
  };
53
97
  }
@@ -45,7 +45,10 @@ export function provisionRepoBoard(repoPathRaw, options = {}) {
45
45
 
46
46
  const db = openConnection(dbPath);
47
47
  createBoardSchema(db);
48
- const seeded = seedIfEmpty(db, repoRoot, { includeExamples: options.includeExamples });
48
+ const seeded = seedIfEmpty(db, repoRoot, {
49
+ enableAi: options.enableAi,
50
+ includeExamples: options.includeExamples
51
+ });
49
52
  if (!seeded) {
50
53
  const boardMeta = readBoardMetaFromSqlite(db);
51
54
  if (!boardMeta) {
@@ -60,7 +63,10 @@ export function provisionRepoBoard(repoPathRaw, options = {}) {
60
63
  function provisionExistingDbFile(repoRoot, dbPath, options = {}) {
61
64
  const db = openConnection(dbPath);
62
65
  createBoardSchema(db);
63
- const seeded = seedIfEmpty(db, repoRoot, { includeExamples: options.includeExamples });
66
+ const seeded = seedIfEmpty(db, repoRoot, {
67
+ enableAi: options.enableAi,
68
+ includeExamples: options.includeExamples
69
+ });
64
70
  if (seeded) {
65
71
  return insertAfterSeed(repoRoot, dbPath, db, seeded, "repaired");
66
72
  }
package/src/core/seed.js CHANGED
@@ -23,8 +23,8 @@ export function seedIfEmpty(db, repoPath, options = {}) {
23
23
  db.prepare(
24
24
  `INSERT INTO boards
25
25
  (id, slug, name, repo_url, system_path, default_branch,
26
- project_notes, agent_instructions, created_at, updated_at)
27
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`
26
+ project_notes, agent_instructions, ai_enabled, created_at, updated_at)
27
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`
28
28
  ).run(
29
29
  boardId,
30
30
  seed.boardSlug,
@@ -34,17 +34,26 @@ export function seedIfEmpty(db, repoPath, options = {}) {
34
34
  seed.defaultBranch,
35
35
  seed.projectNotes,
36
36
  seed.agentInstructions,
37
+ options.enableAi === false ? 0 : 1,
37
38
  time,
38
39
  time
39
40
  );
40
41
 
41
- const states = [
42
+ const states = options.enableAi === false ? [
42
43
  ["Backlog", null],
43
44
  ["Todo", null],
44
45
  ["In Progress", "in_progress"],
45
46
  ["Review", "review"],
46
47
  ["Done", "done"],
47
48
  ["Cancelled", null]
49
+ ] : [
50
+ ["Backlog", null],
51
+ ["Todo", null],
52
+ ["AI Ready", "ai_ready"],
53
+ ["In Progress", "in_progress"],
54
+ ["Review", "review"],
55
+ ["Done", "done"],
56
+ ["Cancelled", null]
48
57
  ];
49
58
  const stateIds = new Map();
50
59
  states.forEach(([name, role], position) => {
@@ -91,7 +100,7 @@ export function seedIfEmpty(db, repoPath, options = {}) {
91
100
  const firstTicket = insertSeedTicket(db, boardId, 2, {
92
101
  title: "Fill agent context & personal notes",
93
102
  description:
94
- "Open Settings → AI (Agent Instructions) and Settings → Notes, and replace the seed text with real project context plus your own reminders.",
103
+ "Open Settings → AI (Agent Instructions) and Settings → Notes, and add real project context plus your own reminders.",
95
104
  type: "feature",
96
105
  parentTicketId: epicTicket,
97
106
  stateId: stateIds.get("Todo"),
@@ -184,7 +193,6 @@ export function buildBoardFromRepo(projectRoot) {
184
193
  "main";
185
194
  const defaultProjectNotes =
186
195
  "Personal reminders (Settings → Notes: Notes For You). Point yourself at SKILL-ORBIT.md, runbooks, or nudges for your tools.";
187
- const defaultAgentInstructions = `Describe why ${boardName} exists, who it serves, and what success looks like. Add features, stack, constraints, and anything agents should know — this project-level context is provided to agents when they work on tickets.`;
188
196
  return {
189
197
  boardName,
190
198
  boardSlug,
@@ -192,7 +200,7 @@ export function buildBoardFromRepo(projectRoot) {
192
200
  systemPath,
193
201
  defaultBranch,
194
202
  projectNotes: readSeedValue("project_notes") || defaultProjectNotes,
195
- agentInstructions: readSeedValue("agent_instructions") || defaultAgentInstructions
203
+ agentInstructions: readSeedValue("agent_instructions") || ""
196
204
  };
197
205
  }
198
206