@goodtek/vibeops 0.7.22 → 1.0.0

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.
Files changed (121) hide show
  1. package/CHANGELOG.md +21 -1
  2. package/README.md +62 -382
  3. package/dist/bootstrap/installer.js +4 -16
  4. package/dist/cli.js +26 -160
  5. package/dist/commands/init.js +3 -3
  6. package/dist/commands/status.js +145 -8
  7. package/dist/commands/task-add.js +64 -164
  8. package/dist/commands/task-done.js +117 -74
  9. package/dist/lib/config.js +2 -118
  10. package/dist/lib/git.js +1 -1
  11. package/dist/lib/paths.js +7 -3
  12. package/dist/lib/plan-llm-openai.js +0 -63
  13. package/dist/lib/resolve-task.js +34 -0
  14. package/dist/lib/task-add-llm.js +46 -233
  15. package/dist/lib/task-context.js +68 -0
  16. package/dist/lib/task-done-llm.js +148 -0
  17. package/dist/lib/task-merge.js +54 -159
  18. package/dist/lib/task-scaffold.js +39 -43
  19. package/dist/lib/task-start.js +72 -0
  20. package/dist/lib/task-summary.js +12 -239
  21. package/dist/lib/task.js +15 -24
  22. package/dist/types/config.js +0 -16
  23. package/package.json +3 -5
  24. package/templates/.cursor/rules/01-task-source-of-truth.mdc +12 -0
  25. package/templates/.cursor/rules/02-git-safety.mdc +11 -0
  26. package/templates/.cursor/rules/03-docs-on-done.mdc +13 -0
  27. package/templates/.cursor/skills/implement-task/SKILL.md +22 -0
  28. package/templates/.cursor/skills/plan-task/SKILL.md +19 -0
  29. package/templates/AGENTS.md +26 -87
  30. package/templates/docs/logs/README.md +4 -35
  31. package/templates/docs/project/03-architecture.md +10 -29
  32. package/templates/docs/project/05-current-state.md +7 -27
  33. package/templates/docs/project/06-decisions.md +6 -17
  34. package/templates/docs/tasks/TASK-000-template.md +9 -44
  35. package/dist/agent/loader.js +0 -71
  36. package/dist/agent/prompt.js +0 -66
  37. package/dist/commands/agent-list.js +0 -32
  38. package/dist/commands/agent-prompt.js +0 -59
  39. package/dist/commands/agent-show.js +0 -26
  40. package/dist/commands/done.js +0 -160
  41. package/dist/commands/github-init.js +0 -554
  42. package/dist/commands/github-status.js +0 -164
  43. package/dist/commands/next.js +0 -283
  44. package/dist/commands/notion-init.js +0 -764
  45. package/dist/commands/notion-sync.js +0 -405
  46. package/dist/commands/notion-test.js +0 -595
  47. package/dist/commands/plan.js +0 -146
  48. package/dist/commands/rollback.js +0 -126
  49. package/dist/commands/start.js +0 -129
  50. package/dist/commands/task-check.js +0 -155
  51. package/dist/commands/task-generate.js +0 -206
  52. package/dist/commands/task-pull.js +0 -277
  53. package/dist/commands/task-rollback.js +0 -174
  54. package/dist/commands/task-start.js +0 -102
  55. package/dist/commands/task-status.js +0 -122
  56. package/dist/lib/brief.js +0 -356
  57. package/dist/lib/cursor-task-prompt.js +0 -60
  58. package/dist/lib/done-follow-up.js +0 -308
  59. package/dist/lib/github-cli.js +0 -247
  60. package/dist/lib/guide-state.js +0 -69
  61. package/dist/lib/mvp-artifacts.js +0 -204
  62. package/dist/lib/mvp-constants.js +0 -15
  63. package/dist/lib/mvp-diff-summary.js +0 -43
  64. package/dist/lib/mvp-done-summary.js +0 -335
  65. package/dist/lib/notion-client.js +0 -459
  66. package/dist/lib/notion-discovery.js +0 -671
  67. package/dist/lib/notion-env.js +0 -140
  68. package/dist/lib/notion-mappers.js +0 -148
  69. package/dist/lib/notion-schema.js +0 -272
  70. package/dist/lib/notion-sync.js +0 -337
  71. package/dist/lib/notion-target.js +0 -247
  72. package/dist/lib/package-json.js +0 -133
  73. package/dist/lib/plan-apply-planner.js +0 -131
  74. package/dist/lib/plan-codex-pkce.js +0 -234
  75. package/dist/lib/plan-llm-models.js +0 -237
  76. package/dist/lib/plan-llm-normalize.js +0 -146
  77. package/dist/lib/plan-llm-session.js +0 -682
  78. package/dist/lib/plan-post-apply-setup.js +0 -116
  79. package/dist/lib/project-docs.js +0 -95
  80. package/dist/lib/prompt-builder.js +0 -135
  81. package/dist/lib/resolve-command-task.js +0 -40
  82. package/dist/lib/task-add-build-prompt.js +0 -39
  83. package/dist/lib/task-add-cursor.js +0 -81
  84. package/dist/lib/task-generator.js +0 -183
  85. package/dist/lib/task-prompt.js +0 -23
  86. package/dist/lib/task-pull.js +0 -354
  87. package/dist/lib/task-status-brief.js +0 -132
  88. package/dist/lib/workflow-guide.js +0 -485
  89. package/dist/status/collector.js +0 -103
  90. package/dist/status/format.js +0 -181
  91. package/dist/types/brief.js +0 -126
  92. package/templates/.cursor/rules/00-project-governance.mdc +0 -28
  93. package/templates/.cursor/rules/01-agent-orchestration.mdc +0 -48
  94. package/templates/.cursor/rules/02-task-workflow.mdc +0 -38
  95. package/templates/.cursor/rules/03-git-safety.mdc +0 -30
  96. package/templates/.cursor/rules/04-docs-update.mdc +0 -22
  97. package/templates/.vibeops/agents/architect.md +0 -47
  98. package/templates/.vibeops/agents/builder.md +0 -38
  99. package/templates/.vibeops/agents/docs.md +0 -54
  100. package/templates/.vibeops/agents/orchestrator.md +0 -40
  101. package/templates/.vibeops/agents/planner.md +0 -60
  102. package/templates/.vibeops/agents/recovery.md +0 -49
  103. package/templates/.vibeops/agents/reviewer.md +0 -47
  104. package/templates/.vibeops/agents/tester.md +0 -43
  105. package/templates/.vibeops/prompts/create-plan.md +0 -33
  106. package/templates/.vibeops/prompts/generate-tasks.md +0 -41
  107. package/templates/.vibeops/prompts/implement-task.md +0 -39
  108. package/templates/.vibeops/prompts/review-task.md +0 -34
  109. package/templates/.vibeops/prompts/rollback.md +0 -32
  110. package/templates/.vibeops/prompts/start-project.md +0 -39
  111. package/templates/.vibeops/workflows/notion-sync.md +0 -53
  112. package/templates/.vibeops/workflows/project-start.md +0 -73
  113. package/templates/.vibeops/workflows/rollback.md +0 -45
  114. package/templates/.vibeops/workflows/task-lifecycle.md +0 -71
  115. package/templates/docs/project/00-overview.md +0 -27
  116. package/templates/docs/project/01-requirements.md +0 -30
  117. package/templates/docs/project/02-mvp-scope.md +0 -36
  118. package/templates/docs/project/04-tech-stack.md +0 -29
  119. package/templates/docs/project/07-backlog.md +0 -23
  120. package/templates/docs/project/08-env.md +0 -29
  121. package/templates/docs/project/09-deployment.md +0 -28
@@ -1,98 +1,141 @@
1
- import { relative, resolve } from "node:path";
1
+ import { resolve } from "node:path";
2
+ import { askYesNo } from "../lib/inquirer-helpers.js";
2
3
  import { readText } from "../lib/filesystem.js";
3
4
  import { bold, cyan, dim, green, log, yellow } from "../lib/logger.js";
4
5
  import { projectPaths } from "../lib/paths.js";
5
- import { findTaskFile, hasNonEmptySection, parseTaskFilename, pickNextTask, readGitContext, readTaskFile, scanTasks, statusDisplay, updateInlineStatus, } from "../lib/task.js";
6
- function relOrAbs(root, p) {
7
- const r = relative(root, p);
8
- return r === "" ? "." : r.startsWith("..") ? p : r;
6
+ import { resolveTask, taskNotFoundMessage } from "../lib/resolve-task.js";
7
+ import { mergeTaskBranch } from "../lib/task-merge.js";
8
+ import { applyTaskDoneMemory, fallbackResultSections, llmCompleteTaskDone, writeTaskResultSections, } from "../lib/task-done-llm.js";
9
+ import { relPath } from "../lib/task-context.js";
10
+ import { gitAddPaths, listWorkingTreeRelPaths, partitionPathsForAutoCommit, readGitInfo, runGit, } from "../lib/git.js";
11
+ import { hasNonEmptySection, markGitContextDone, parseTaskFilename, readGitContext, readTaskFile, updateInlineStatus, } from "../lib/task.js";
12
+ function commitMessageFor(taskId, title) {
13
+ const slug = title.replace(/^TASK-\d+\s*[:\-]\s*/i, "").trim() || taskId;
14
+ return `feat(${taskId.toLowerCase()}): ${slug}`;
9
15
  }
10
- function commitMessageFor(taskId, title, mvpPhase) {
11
- const trimmed = (title || taskId).trim();
12
- const slug = trimmed.replace(/^TASK-\d+\s*[·:\-]\s*/i, "").trim() || trimmed;
13
- const scope = taskId.toLowerCase();
14
- const typePrefix = typeof mvpPhase === "string" && /rollback/i.test(mvpPhase) ? "chore" : "feat";
15
- return `${typePrefix}(${scope}): ${slug}`;
16
+ async function tryCommitDirty(cwd, message, dryRun) {
17
+ const git = await readGitInfo(cwd);
18
+ if (!git.isRepo || git.dirty !== true)
19
+ return false;
20
+ const { committable, excluded } = partitionPathsForAutoCommit(await listWorkingTreeRelPaths(cwd), { unmerged: [] });
21
+ if (committable.length === 0)
22
+ return false;
23
+ if (excluded.length > 0) {
24
+ log.warn(`Skipping ${excluded.length} build artifact path(s) from auto-commit.`);
25
+ }
26
+ if (dryRun) {
27
+ log.info(dim(` would commit ${committable.length} path(s)`));
28
+ return true;
29
+ }
30
+ await gitAddPaths(cwd, committable);
31
+ await runGit(cwd, ["commit", "-q", "-m", message]);
32
+ log.ok(`Committed: ${message}`);
33
+ return true;
16
34
  }
17
- export async function taskDoneCommand(taskId, options = {}) {
35
+ export async function taskDoneCommand(taskRef, options = {}) {
18
36
  const cwd = resolve(options.cwd ?? process.cwd());
19
37
  const paths = projectPaths(cwd);
20
- const taskFile = await findTaskFile(paths.docsTasks, taskId);
21
- if (!taskFile) {
22
- log.error(`TASK not found: ${taskId} (looked in ${relOrAbs(cwd, paths.docsTasks)})`);
38
+ const dryRun = options.dryRun === true;
39
+ const interactive = !dryRun && process.stdin.isTTY === true && options.yes !== true;
40
+ const resolved = await resolveTask(paths, cwd, taskRef);
41
+ if (resolved === null) {
42
+ log.error(taskNotFoundMessage(taskRef));
23
43
  process.exitCode = 1;
24
44
  return;
25
45
  }
46
+ const { taskId, taskFile } = resolved;
26
47
  const meta = await readTaskFile(taskFile);
27
48
  const body = await readText(taskFile);
28
- log.info(bold(`vibeops task done ${meta.id}`));
29
- log.info(` ${dim("file")} ${relOrAbs(cwd, taskFile)}`);
30
- log.info(` ${dim("title")} ${meta.title || dim("(no title)")}`);
31
- log.info(` ${dim("status")} ${statusDisplay(meta.status)}`);
49
+ const relFile = relPath(cwd, taskFile);
50
+ log.info(bold(`vibeops task done ${taskId}`));
51
+ log.info(` ${dim("file")} ${relFile}`);
32
52
  log.blank();
33
- const resultOk = hasNonEmptySection(body, "Result");
34
- const testResultOk = hasNonEmptySection(body, "Test Result");
35
- const docCheck = [
36
- ["Result section", resultOk],
37
- ["Test Result section", testResultOk],
38
- ];
39
- log.info(bold("Required sections"));
40
- let missing = 0;
41
- for (const [label, ok] of docCheck) {
42
- if (!ok)
43
- missing++;
44
- log.info(` ${ok ? green("✓") : yellow("·")} ${label}${ok ? "" : dim(" (empty or placeholder)")}`);
53
+ let resultOk = hasNonEmptySection(body, "Result");
54
+ let testOk = hasNonEmptySection(body, "Test Result");
55
+ if (!resultOk || !testOk) {
56
+ log.step("LLM — Result / Test Result + project memory");
57
+ if (dryRun) {
58
+ log.info(dim(" would call LLM and patch docs/project/*"));
59
+ }
60
+ else {
61
+ const patch = await llmCompleteTaskDone({
62
+ cwd,
63
+ taskId,
64
+ taskTitle: meta.title,
65
+ taskBody: body,
66
+ taskFileRel: relFile,
67
+ });
68
+ if (patch !== null) {
69
+ await writeTaskResultSections(taskFile, patch.result, patch.testResult);
70
+ log.ok(`Result / Test Result updated (${patch.provider})`);
71
+ const memory = await applyTaskDoneMemory(cwd, patch);
72
+ for (const p of memory.updated)
73
+ log.ok(`Updated ${p}`);
74
+ for (const p of memory.skipped)
75
+ log.skip(`Skipped ${p}`);
76
+ }
77
+ else {
78
+ log.warn("LLM unavailable — using git diff fallback for Result sections.");
79
+ const fb = await fallbackResultSections(cwd, taskId, taskFile);
80
+ await writeTaskResultSections(taskFile, fb.result, fb.testResult);
81
+ }
82
+ }
83
+ const body2 = dryRun ? body : await readText(taskFile);
84
+ resultOk = hasNonEmptySection(body2, "Result");
85
+ testOk = hasNonEmptySection(body2, "Test Result");
45
86
  }
87
+ log.info(bold("Sections"));
88
+ log.info(` ${resultOk ? green("✓") : yellow("·")} Result`);
89
+ log.info(` ${testOk ? green("✓") : yellow("·")} Test Result`);
46
90
  log.blank();
47
- if (missing > 0) {
48
- log.error(`${missing} required section(s) still empty. Fill Result / Test Result first, then re-run \`vibeops task done\`.`);
91
+ if (!dryRun && (!resultOk || !testOk)) {
92
+ log.error("Fill Result and Test Result in the TASK file, then rerun.");
49
93
  process.exitCode = 1;
50
94
  return;
51
95
  }
52
- const target = options.finalize === true ? "done" : "review";
53
- const targetDisplay = statusDisplay(target);
54
- if (options.dryRun === true) {
55
- log.info(bold("dry-run — would perform:"));
56
- log.info(` · update Status → ${targetDisplay} in ${relOrAbs(cwd, taskFile)}`);
57
- log.info(` · (no git commit, no Notion call)`);
58
- log.blank();
59
- }
60
- else {
61
- await updateInlineStatus(taskFile, target);
62
- log.ok(`Status → ${targetDisplay} (${relOrAbs(cwd, taskFile)})`);
63
- log.blank();
96
+ if (dryRun) {
97
+ log.info(bold("dry-run — would:"));
98
+ if (!resultOk || !testOk) {
99
+ log.info(" · LLM fill Result / Test Result + patch docs/project/*");
100
+ }
101
+ log.info(" · Status → Done");
102
+ log.info(" · commit if dirty, merge task branch (unless --no-merge)");
103
+ return;
64
104
  }
65
- const ctx = await readGitContext(taskFile);
105
+ await updateInlineStatus(taskFile, "done");
106
+ await markGitContextDone(taskFile);
107
+ log.ok("Status → Done");
66
108
  const parts = parseTaskFilename(taskFile);
67
- const commitMsg = commitMessageFor(parts.id, meta.title, meta.mvpPhase);
68
- log.info(bold("Suggested commit"));
69
- log.info(` ${cyan(`git add -A && git commit -m "${commitMsg}"`)}`);
70
- if (ctx !== null) {
71
- log.info(` ${dim(`after commit on ${ctx.taskBranch}: open PR or run \`git switch ${ctx.baseBranch} && git merge --no-ff ${ctx.taskBranch}\``)}`);
72
- }
73
- else {
74
- log.info(` ${dim("(no Git Context section — skipping merge guidance)")}`);
109
+ const commitMsg = commitMessageFor(parts.id, meta.title);
110
+ await tryCommitDirty(cwd, commitMsg, false);
111
+ const gitCtx = await readGitContext(taskFile);
112
+ if (options.noMerge !== true && gitCtx !== null) {
113
+ let doMerge = options.yes === true || !interactive;
114
+ if (interactive) {
115
+ doMerge = await askYesNo({
116
+ message: `Merge into ${gitCtx.baseBranch} and delete ${gitCtx.taskBranch}?`,
117
+ nonInteractive: false,
118
+ defaultValue: true,
119
+ });
120
+ }
121
+ if (doMerge) {
122
+ const mergeResult = await mergeTaskBranch({
123
+ cwd,
124
+ taskId: meta.id,
125
+ taskTitle: meta.title,
126
+ gitCtx,
127
+ allowDirty: options.allowDirty,
128
+ });
129
+ if (!mergeResult.ok)
130
+ process.exitCode = 1;
131
+ }
132
+ else {
133
+ log.info(dim("Merge skipped."));
134
+ }
75
135
  }
76
- log.blank();
77
- log.info(bold("Notion"));
78
- log.info(` ${dim("TODO: run `vibeops notion sync` to push this TASK metadata.")}`);
79
- log.blank();
80
- const all = await scanTasks(paths.docsTasks);
81
- const next = pickNextTask(all.filter((t) => t.id.toUpperCase() !== meta.id.toUpperCase()));
82
- log.info(bold("Next TASK candidate"));
83
- if (next) {
84
- log.info(` → ${cyan(next.id)} — ${next.title || dim("(no title)")} ${dim(`[${statusDisplay(next.status)}]`)}`);
85
- log.info(` file: ${dim(relOrAbs(cwd, next.filePath))}`);
86
- log.info(` start: ${dim(`vibeops task start ${next.id}`)}`);
87
- }
88
- else {
89
- log.info(` ${dim("(no remaining planned / in-progress / review TASK)")}`);
136
+ else if (gitCtx === null) {
137
+ log.info(dim("No Git Context — merge skipped."));
90
138
  }
91
139
  log.blank();
92
- if (target === "review") {
93
- log.info(`Status moved to ${cyan("Review")}. After human or Reviewer Agent review, finalize with ${cyan(`vibeops task done ${meta.id} --finalize`)}.`);
94
- }
95
- else {
96
- log.ok(`${meta.id} finalized → Done.`);
97
- }
140
+ log.info(`Next: ${cyan("vibeops task add")} or ${cyan("vibeops status")}`);
98
141
  }
@@ -2,39 +2,7 @@ import { join } from "node:path";
2
2
  import { readTextOrNull, writeText } from "./filesystem.js";
3
3
  import { VIBEOPS_CONFIG_FILE } from "./paths.js";
4
4
  import { VERSION } from "../version.js";
5
- import { DEFAULT_GITHUB_CONFIG, VIBEOPS_CONFIG_SCHEMA_VERSION, } from "../types/config.js";
6
- function parseNotionSection(raw) {
7
- if (raw === null || typeof raw !== "object")
8
- return undefined;
9
- const r = raw;
10
- const out = {
11
- enabled: typeof r.enabled === "boolean" ? r.enabled : false,
12
- projectsTargetId: typeof r.projectsTargetId === "string" ? r.projectsTargetId : "",
13
- tasksTargetId: typeof r.tasksTargetId === "string" ? r.tasksTargetId : "",
14
- projectsDatabaseId: typeof r.projectsDatabaseId === "string" ? r.projectsDatabaseId : "",
15
- tasksDatabaseId: typeof r.tasksDatabaseId === "string" ? r.tasksDatabaseId : "",
16
- };
17
- return out;
18
- }
19
- function parseGithubVisibility(raw) {
20
- if (raw === "public" || raw === "private")
21
- return raw;
22
- return "";
23
- }
24
- function parseGithubSection(raw) {
25
- if (raw === null || typeof raw !== "object")
26
- return undefined;
27
- const r = raw;
28
- return {
29
- enabled: typeof r.enabled === "boolean" ? r.enabled : false,
30
- mode: r.mode === "gh-cli" ? "gh-cli" : "gh-cli",
31
- owner: typeof r.owner === "string" ? r.owner : "",
32
- repo: typeof r.repo === "string" ? r.repo : "",
33
- remote: typeof r.remote === "string" && r.remote.length > 0 ? r.remote : "origin",
34
- visibility: parseGithubVisibility(r.visibility),
35
- url: typeof r.url === "string" ? r.url : "",
36
- };
37
- }
5
+ import { VIBEOPS_CONFIG_SCHEMA_VERSION } from "../types/config.js";
38
6
  export async function readConfig(root) {
39
7
  const text = await readTextOrNull(join(root, VIBEOPS_CONFIG_FILE));
40
8
  if (text === null)
@@ -45,19 +13,12 @@ export async function readConfig(root) {
45
13
  typeof parsed.vibeopsVersion === "string" &&
46
14
  typeof parsed.createdAt === "string" &&
47
15
  parsed.schemaVersion === VIBEOPS_CONFIG_SCHEMA_VERSION) {
48
- const notion = parseNotionSection(parsed.notion);
49
- const github = parseGithubSection(parsed.github);
50
- const config = {
16
+ return {
51
17
  name: parsed.name,
52
18
  vibeopsVersion: parsed.vibeopsVersion,
53
19
  schemaVersion: VIBEOPS_CONFIG_SCHEMA_VERSION,
54
20
  createdAt: parsed.createdAt,
55
21
  };
56
- if (notion)
57
- config.notion = notion;
58
- if (github)
59
- config.github = github;
60
- return config;
61
22
  }
62
23
  return null;
63
24
  }
@@ -79,80 +40,3 @@ export function configToJson(config) {
79
40
  export async function writeConfig(root, config) {
80
41
  await writeText(join(root, VIBEOPS_CONFIG_FILE), configToJson(config));
81
42
  }
82
- /** Merge a partial notion section into an existing config without touching other fields. */
83
- export function mergeNotionConfig(base, patch) {
84
- const current = base.notion ?? {
85
- enabled: false,
86
- projectsTargetId: "",
87
- tasksTargetId: "",
88
- projectsDatabaseId: "",
89
- tasksDatabaseId: "",
90
- };
91
- const next = {
92
- enabled: patch.enabled ?? current.enabled,
93
- projectsTargetId: patch.projectsTargetId !== undefined && patch.projectsTargetId.length > 0
94
- ? patch.projectsTargetId
95
- : current.projectsTargetId,
96
- tasksTargetId: patch.tasksTargetId !== undefined && patch.tasksTargetId.length > 0
97
- ? patch.tasksTargetId
98
- : current.tasksTargetId,
99
- projectsDatabaseId: patch.projectsDatabaseId !== undefined && patch.projectsDatabaseId.length > 0
100
- ? patch.projectsDatabaseId
101
- : current.projectsDatabaseId,
102
- tasksDatabaseId: patch.tasksDatabaseId !== undefined && patch.tasksDatabaseId.length > 0
103
- ? patch.tasksDatabaseId
104
- : current.tasksDatabaseId,
105
- };
106
- const changed = base.notion === undefined ||
107
- next.enabled !== current.enabled ||
108
- next.projectsTargetId !== current.projectsTargetId ||
109
- next.tasksTargetId !== current.tasksTargetId ||
110
- next.projectsDatabaseId !== current.projectsDatabaseId ||
111
- next.tasksDatabaseId !== current.tasksDatabaseId;
112
- return { merged: { ...base, notion: next }, changed };
113
- }
114
- /** Preferred Projects target id for API calls: data_source target first, legacy DB fallback. */
115
- export function notionProjectsTargetId(notion) {
116
- return notion.projectsTargetId.length > 0
117
- ? notion.projectsTargetId
118
- : notion.projectsDatabaseId;
119
- }
120
- /** Preferred Tasks target id for API calls: data_source target first, legacy DB fallback. */
121
- export function notionTasksTargetId(notion) {
122
- return notion.tasksTargetId.length > 0 ? notion.tasksTargetId : notion.tasksDatabaseId;
123
- }
124
- /**
125
- * Merge a partial github section into an existing config without touching
126
- * other fields (notion, name, etc.). Empty strings in the patch are treated
127
- * as "no value provided" — the existing value is kept. Pass through
128
- * `enabled` / `mode` always (booleans / enums always overwrite).
129
- */
130
- export function mergeGithubConfig(base, patch) {
131
- const current = base.github ?? DEFAULT_GITHUB_CONFIG;
132
- const next = {
133
- enabled: patch.enabled ?? current.enabled,
134
- mode: patch.mode ?? current.mode,
135
- owner: patch.owner !== undefined && patch.owner.length > 0
136
- ? patch.owner
137
- : current.owner,
138
- repo: patch.repo !== undefined && patch.repo.length > 0
139
- ? patch.repo
140
- : current.repo,
141
- remote: patch.remote !== undefined && patch.remote.length > 0
142
- ? patch.remote
143
- : current.remote,
144
- visibility: patch.visibility !== undefined && patch.visibility.length > 0
145
- ? patch.visibility
146
- : current.visibility,
147
- url: patch.url !== undefined && patch.url.length > 0 ? patch.url : current.url,
148
- };
149
- const changed = base.github === undefined ||
150
- next.enabled !== current.enabled ||
151
- next.mode !== current.mode ||
152
- next.owner !== current.owner ||
153
- next.repo !== current.repo ||
154
- next.remote !== current.remote ||
155
- next.visibility !== current.visibility ||
156
- next.url !== current.url;
157
- return { merged: { ...base, github: next }, changed };
158
- }
package/dist/lib/git.js CHANGED
@@ -370,7 +370,7 @@ export async function gitAddPaths(cwd, paths) {
370
370
  /**
371
371
  * When the index/worktree is dirty, classify whether every changed path is
372
372
  * limited to governance docs and VibeOps metadata (`.vibeops/**`, docs/tasks, etc.).
373
- * Used by `vibeops task start` so `task done --finalize` on main does not
373
+ * Used when switching task branches so governance paths are not left off-disk after switch.
374
374
  * block the next TASK unless application code is also dirty.
375
375
  */
376
376
  export async function gitGovernanceOnlyDirty(cwd) {
package/dist/lib/paths.js CHANGED
@@ -14,13 +14,17 @@ export function projectPaths(root) {
14
14
  envExample: join(abs, VIBEOPS_ENV_EXAMPLE_FILE),
15
15
  agentsMd: join(abs, "AGENTS.md"),
16
16
  cursorRules: join(abs, ".cursor", "rules"),
17
+ cursorSkills: join(abs, ".cursor", "skills"),
17
18
  docsProject: join(abs, "docs", "project"),
18
19
  docsTasks: join(abs, "docs", "tasks"),
19
20
  docsLogs: join(abs, "docs", "logs"),
20
21
  vibeopsDir: join(abs, ".vibeops"),
21
- vibeopsAgents: join(abs, ".vibeops", "agents"),
22
- vibeopsPrompts: join(abs, ".vibeops", "prompts"),
23
- vibeopsWorkflows: join(abs, ".vibeops", "workflows"),
24
22
  gitignore: join(abs, ".gitignore"),
25
23
  };
26
24
  }
25
+ /** Project memory files updated by `vibeops task done`. */
26
+ export const PROJECT_MEMORY_FILES = {
27
+ currentState: "docs/project/05-current-state.md",
28
+ architecture: "docs/project/03-architecture.md",
29
+ decisions: "docs/project/06-decisions.md",
30
+ };
@@ -29,7 +29,6 @@ export async function openAiChatCompletionJson(params) {
29
29
  }
30
30
  return content.trim();
31
31
  }
32
- /** Plain assistant text (no `response_format`) — used for planner markdown apply. */
33
32
  export async function openAiChatCompletionText(params) {
34
33
  const key = process.env.OPENAI_API_KEY?.trim();
35
34
  if (!key) {
@@ -70,65 +69,3 @@ export function extractJsonObject(text) {
70
69
  return trimmed.slice(start, end + 1);
71
70
  return trimmed;
72
71
  }
73
- export function parsePlanLlmTurn(jsonText) {
74
- const raw = JSON.parse(jsonText);
75
- const turn = raw.turn;
76
- if (turn === "done") {
77
- const pb = raw.projectBrief;
78
- if (!pb || typeof pb !== "object" || Array.isArray(pb)) {
79
- throw new Error('Invalid JSON: "done" turn requires object "projectBrief".');
80
- }
81
- const plannerAssumptions = raw.plannerAssumptions;
82
- const pa = Array.isArray(plannerAssumptions) && plannerAssumptions.every((x) => typeof x === "string")
83
- ? plannerAssumptions
84
- : undefined;
85
- return {
86
- turn: "done",
87
- projectBrief: pb,
88
- plannerAssumptions: pa,
89
- };
90
- }
91
- if (turn === "confirm") {
92
- const readableSummary = typeof raw.readableSummary === "string" ? raw.readableSummary.trim() : "";
93
- if (readableSummary.length === 0) {
94
- throw new Error('Invalid JSON: "confirm" turn needs non-empty "readableSummary" (markdown).');
95
- }
96
- const plannerNote = typeof raw.plannerNote === "string" ? raw.plannerNote.trim() : undefined;
97
- return {
98
- turn: "confirm",
99
- readableSummary,
100
- plannerNote: plannerNote && plannerNote.length > 0 ? plannerNote : undefined,
101
- };
102
- }
103
- if (turn === "question") {
104
- const message = typeof raw.message === "string" ? raw.message : "";
105
- if (message.length === 0) {
106
- throw new Error('Invalid JSON: "question" turn needs non-empty "message".');
107
- }
108
- const options = Array.isArray(raw.options)
109
- ? raw.options.filter((x) => typeof x === "string" && x.trim().length > 0)
110
- : [];
111
- const rawQt = raw.questionType;
112
- let questionType;
113
- if (rawQt === "single" || rawQt === "multi" || rawQt === "text") {
114
- questionType = rawQt;
115
- }
116
- else if (options.length >= 2) {
117
- /** Model often omits or mistypes questionType — multi is the safer default when lists are offered. */
118
- questionType = "multi";
119
- }
120
- else if (options.length === 1) {
121
- questionType = "single";
122
- }
123
- else {
124
- questionType = "text";
125
- }
126
- return {
127
- turn: "question",
128
- message,
129
- questionType,
130
- options: options.length > 0 ? options : undefined,
131
- };
132
- }
133
- throw new Error('Invalid JSON: "turn" must be "question", "confirm", or "done".');
134
- }
@@ -0,0 +1,34 @@
1
+ import { findTaskFile, loadActionableTasks, pickActiveTask, pickInProgressTask, } from "./task.js";
2
+ export function normalizeTaskRef(ref) {
3
+ const t = ref.trim();
4
+ if (/^task-/i.test(t) && !/^TASK-/i.test(t)) {
5
+ return `TASK-${t.replace(/^task-/i, "")}`;
6
+ }
7
+ return t.toUpperCase().startsWith("TASK-") ? t.toUpperCase() : `TASK-${t}`;
8
+ }
9
+ export async function resolveTask(paths, cwd, taskRef) {
10
+ const trimmed = taskRef?.trim();
11
+ if (trimmed !== undefined && trimmed.length > 0) {
12
+ const taskId = normalizeTaskRef(trimmed);
13
+ const taskFile = await findTaskFile(paths.docsTasks, taskId);
14
+ if (taskFile === null)
15
+ return null;
16
+ return { taskId, taskFile };
17
+ }
18
+ const tasks = await loadActionableTasks(paths.docsTasks);
19
+ const inProgress = pickInProgressTask(tasks);
20
+ if (inProgress !== null) {
21
+ return { taskId: inProgress.id, taskFile: inProgress.filePath };
22
+ }
23
+ const onBranch = await pickActiveTask(paths.docsTasks, cwd);
24
+ if (onBranch !== null && onBranch.status !== "done") {
25
+ return { taskId: onBranch.id, taskFile: onBranch.filePath };
26
+ }
27
+ return null;
28
+ }
29
+ export function taskNotFoundMessage(taskRef) {
30
+ if (taskRef?.trim()) {
31
+ return `TASK not found: ${normalizeTaskRef(taskRef)}. Check docs/tasks/.`;
32
+ }
33
+ return "No open TASK found. Run `vibeops task add` or pass `vibeops task done TASK-NNN`.";
34
+ }