@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.
- package/CHANGELOG.md +21 -1
- package/README.md +62 -382
- package/dist/bootstrap/installer.js +4 -16
- package/dist/cli.js +26 -160
- package/dist/commands/init.js +3 -3
- package/dist/commands/status.js +145 -8
- package/dist/commands/task-add.js +64 -164
- package/dist/commands/task-done.js +117 -74
- package/dist/lib/config.js +2 -118
- package/dist/lib/git.js +1 -1
- package/dist/lib/paths.js +7 -3
- package/dist/lib/plan-llm-openai.js +0 -63
- package/dist/lib/resolve-task.js +34 -0
- package/dist/lib/task-add-llm.js +46 -233
- package/dist/lib/task-context.js +68 -0
- package/dist/lib/task-done-llm.js +148 -0
- package/dist/lib/task-merge.js +54 -159
- package/dist/lib/task-scaffold.js +39 -43
- package/dist/lib/task-start.js +72 -0
- package/dist/lib/task-summary.js +12 -239
- package/dist/lib/task.js +15 -24
- package/dist/types/config.js +0 -16
- package/package.json +3 -5
- package/templates/.cursor/rules/01-task-source-of-truth.mdc +12 -0
- package/templates/.cursor/rules/02-git-safety.mdc +11 -0
- package/templates/.cursor/rules/03-docs-on-done.mdc +13 -0
- package/templates/.cursor/skills/implement-task/SKILL.md +22 -0
- package/templates/.cursor/skills/plan-task/SKILL.md +19 -0
- package/templates/AGENTS.md +26 -87
- package/templates/docs/logs/README.md +4 -35
- package/templates/docs/project/03-architecture.md +10 -29
- package/templates/docs/project/05-current-state.md +7 -27
- package/templates/docs/project/06-decisions.md +6 -17
- package/templates/docs/tasks/TASK-000-template.md +9 -44
- package/dist/agent/loader.js +0 -71
- package/dist/agent/prompt.js +0 -66
- package/dist/commands/agent-list.js +0 -32
- package/dist/commands/agent-prompt.js +0 -59
- package/dist/commands/agent-show.js +0 -26
- package/dist/commands/done.js +0 -160
- package/dist/commands/github-init.js +0 -554
- package/dist/commands/github-status.js +0 -164
- package/dist/commands/next.js +0 -283
- package/dist/commands/notion-init.js +0 -764
- package/dist/commands/notion-sync.js +0 -405
- package/dist/commands/notion-test.js +0 -595
- package/dist/commands/plan.js +0 -146
- package/dist/commands/rollback.js +0 -126
- package/dist/commands/start.js +0 -129
- package/dist/commands/task-check.js +0 -155
- package/dist/commands/task-generate.js +0 -206
- package/dist/commands/task-pull.js +0 -277
- package/dist/commands/task-rollback.js +0 -174
- package/dist/commands/task-start.js +0 -102
- package/dist/commands/task-status.js +0 -122
- package/dist/lib/brief.js +0 -356
- package/dist/lib/cursor-task-prompt.js +0 -60
- package/dist/lib/done-follow-up.js +0 -308
- package/dist/lib/github-cli.js +0 -247
- package/dist/lib/guide-state.js +0 -69
- package/dist/lib/mvp-artifacts.js +0 -204
- package/dist/lib/mvp-constants.js +0 -15
- package/dist/lib/mvp-diff-summary.js +0 -43
- package/dist/lib/mvp-done-summary.js +0 -335
- package/dist/lib/notion-client.js +0 -459
- package/dist/lib/notion-discovery.js +0 -671
- package/dist/lib/notion-env.js +0 -140
- package/dist/lib/notion-mappers.js +0 -148
- package/dist/lib/notion-schema.js +0 -272
- package/dist/lib/notion-sync.js +0 -337
- package/dist/lib/notion-target.js +0 -247
- package/dist/lib/package-json.js +0 -133
- package/dist/lib/plan-apply-planner.js +0 -131
- package/dist/lib/plan-codex-pkce.js +0 -234
- package/dist/lib/plan-llm-models.js +0 -237
- package/dist/lib/plan-llm-normalize.js +0 -146
- package/dist/lib/plan-llm-session.js +0 -682
- package/dist/lib/plan-post-apply-setup.js +0 -116
- package/dist/lib/project-docs.js +0 -95
- package/dist/lib/prompt-builder.js +0 -135
- package/dist/lib/resolve-command-task.js +0 -40
- package/dist/lib/task-add-build-prompt.js +0 -39
- package/dist/lib/task-add-cursor.js +0 -81
- package/dist/lib/task-generator.js +0 -183
- package/dist/lib/task-prompt.js +0 -23
- package/dist/lib/task-pull.js +0 -354
- package/dist/lib/task-status-brief.js +0 -132
- package/dist/lib/workflow-guide.js +0 -485
- package/dist/status/collector.js +0 -103
- package/dist/status/format.js +0 -181
- package/dist/types/brief.js +0 -126
- package/templates/.cursor/rules/00-project-governance.mdc +0 -28
- package/templates/.cursor/rules/01-agent-orchestration.mdc +0 -48
- package/templates/.cursor/rules/02-task-workflow.mdc +0 -38
- package/templates/.cursor/rules/03-git-safety.mdc +0 -30
- package/templates/.cursor/rules/04-docs-update.mdc +0 -22
- package/templates/.vibeops/agents/architect.md +0 -47
- package/templates/.vibeops/agents/builder.md +0 -38
- package/templates/.vibeops/agents/docs.md +0 -54
- package/templates/.vibeops/agents/orchestrator.md +0 -40
- package/templates/.vibeops/agents/planner.md +0 -60
- package/templates/.vibeops/agents/recovery.md +0 -49
- package/templates/.vibeops/agents/reviewer.md +0 -47
- package/templates/.vibeops/agents/tester.md +0 -43
- package/templates/.vibeops/prompts/create-plan.md +0 -33
- package/templates/.vibeops/prompts/generate-tasks.md +0 -41
- package/templates/.vibeops/prompts/implement-task.md +0 -39
- package/templates/.vibeops/prompts/review-task.md +0 -34
- package/templates/.vibeops/prompts/rollback.md +0 -32
- package/templates/.vibeops/prompts/start-project.md +0 -39
- package/templates/.vibeops/workflows/notion-sync.md +0 -53
- package/templates/.vibeops/workflows/project-start.md +0 -73
- package/templates/.vibeops/workflows/rollback.md +0 -45
- package/templates/.vibeops/workflows/task-lifecycle.md +0 -71
- package/templates/docs/project/00-overview.md +0 -27
- package/templates/docs/project/01-requirements.md +0 -30
- package/templates/docs/project/02-mvp-scope.md +0 -36
- package/templates/docs/project/04-tech-stack.md +0 -29
- package/templates/docs/project/07-backlog.md +0 -23
- package/templates/docs/project/08-env.md +0 -29
- package/templates/docs/project/09-deployment.md +0 -28
|
@@ -1,98 +1,141 @@
|
|
|
1
|
-
import {
|
|
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 {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
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
|
|
11
|
-
const
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
const
|
|
15
|
-
|
|
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(
|
|
35
|
+
export async function taskDoneCommand(taskRef, options = {}) {
|
|
18
36
|
const cwd = resolve(options.cwd ?? process.cwd());
|
|
19
37
|
const paths = projectPaths(cwd);
|
|
20
|
-
const
|
|
21
|
-
|
|
22
|
-
|
|
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
|
-
|
|
29
|
-
log.info(`
|
|
30
|
-
log.info(` ${dim("
|
|
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
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
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 (
|
|
48
|
-
log.error(
|
|
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
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
log.info(
|
|
58
|
-
log.
|
|
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
|
-
|
|
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
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
if (
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
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
|
-
|
|
77
|
-
|
|
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
|
-
|
|
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
|
}
|
package/dist/lib/config.js
CHANGED
|
@@ -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 {
|
|
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
|
-
|
|
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
|
|
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
|
+
}
|