ndomo 0.1.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/.bun-version +1 -0
- package/.dockerignore +79 -0
- package/.editorconfig +18 -0
- package/.env.example +19 -0
- package/.github/CODEOWNERS +8 -0
- package/.github/ISSUE_TEMPLATE/bug_report.yml +62 -0
- package/.github/ISSUE_TEMPLATE/config.yml +2 -0
- package/.github/ISSUE_TEMPLATE/feature_request.yml +34 -0
- package/.github/dependabot.yml +36 -0
- package/.github/pull_request_template.md +24 -0
- package/.github/release.yml +30 -0
- package/.github/workflows/gitleaks.yml +28 -0
- package/.github/workflows/release-please.yml +27 -0
- package/.github/workflows/smoke.yml +29 -0
- package/.husky/commit-msg +1 -0
- package/CHANGELOG.md +114 -0
- package/Dockerfile +32 -0
- package/README.es.md +174 -0
- package/README.md +187 -0
- package/agents/chronicler.md +98 -0
- package/agents/ci-smith.md +136 -0
- package/agents/craftsman.md +341 -0
- package/agents/deploy-smith.md +138 -0
- package/agents/foreman.md +377 -0
- package/agents/go-smith.md +164 -0
- package/agents/guild.md +188 -0
- package/agents/inspector.md +83 -0
- package/agents/js-smith.md +127 -0
- package/agents/ops-scout.md +173 -0
- package/agents/painter.md +200 -0
- package/agents/python-smith.md +120 -0
- package/agents/ranger.md +307 -0
- package/agents/release-smith.md +165 -0
- package/agents/rust-smith.md +159 -0
- package/agents/sage.md +178 -0
- package/agents/scout.md +144 -0
- package/agents/scribe.md +156 -0
- package/agents/smith.md +201 -0
- package/agents/vue-smith.md +155 -0
- package/agents/warden.md +216 -0
- package/agents/zig-smith.md +156 -0
- package/bin/ndomo-analyses.ts +4 -0
- package/bin/ndomo-status.ts +4 -0
- package/biome.json +57 -0
- package/bun.lock +514 -0
- package/commitlint.config.js +3 -0
- package/config/ndomo.config.json +258 -0
- package/config/ndomo.schema.json +166 -0
- package/docs/agents.md +375 -0
- package/docs/bugs/plan-create-orphan-fk.md +131 -0
- package/docs/bugs/task_create_batch-order-index-collision.md +158 -0
- package/docs/configuration.md +276 -0
- package/docs/database.md +364 -0
- package/docs/features/feature-flexible-builder-v1.md +724 -0
- package/docs/features/feature-flexible-builder-v2.md +882 -0
- package/docs/features/feature-flexible-builder.md +974 -0
- package/docs/http-server.md +244 -0
- package/docs/installation.md +259 -0
- package/docs/integrations.md +129 -0
- package/docs/operations/anti-pattern-sub-agent-verify-2026-06-21.md +32 -0
- package/docs/operations/audit-v1.md +417 -0
- package/docs/operations/audit-v2.md +197 -0
- package/docs/operations/audit-v3.md +306 -0
- package/docs/operations/db-optimize-foundations.md +123 -0
- package/docs/operations/verify-gate-architecture.md +82 -0
- package/docs/workflows.md +448 -0
- package/opencode.json +5 -0
- package/package.json +65 -0
- package/release-please-config.json +11 -0
- package/scripts/dev-bust-cache.sh +164 -0
- package/scripts/install.sh +688 -0
- package/scripts/smoke-e2e.ts +704 -0
- package/scripts/smoke-hot.ts +417 -0
- package/scripts/smoke-http.sh +228 -0
- package/scripts/smoke-v4.ts +256 -0
- package/scripts/smoke-v5.ts +397 -0
- package/scripts/smoke.sh +9 -0
- package/scripts/uninstall.sh +224 -0
- package/skills/api-security-best-practices/SKILL.md +915 -0
- package/skills/bash-scripting/SKILL.md +201 -0
- package/skills/bun/SKILL.md +313 -0
- package/skills/cavecrew/SKILL.md +82 -0
- package/skills/caveman/SKILL.md +74 -0
- package/skills/caveman-review/README.md +33 -0
- package/skills/caveman-review/SKILL.md +55 -0
- package/skills/find-skills/SKILL.md +142 -0
- package/skills/frontend-design/LICENSE.txt +177 -0
- package/skills/frontend-design/SKILL.md +55 -0
- package/skills/golang-patterns/SKILL.md +674 -0
- package/skills/golang-security/SKILL.md +185 -0
- package/skills/golang-security/evals/evals.json +595 -0
- package/skills/golang-security/references/architecture.md +268 -0
- package/skills/golang-security/references/checklist.md +80 -0
- package/skills/golang-security/references/cookies.md +200 -0
- package/skills/golang-security/references/cryptography.md +424 -0
- package/skills/golang-security/references/filesystem.md +285 -0
- package/skills/golang-security/references/injection.md +315 -0
- package/skills/golang-security/references/logging.md +163 -0
- package/skills/golang-security/references/memory-safety.md +241 -0
- package/skills/golang-security/references/network.md +253 -0
- package/skills/golang-security/references/secrets.md +189 -0
- package/skills/golang-security/references/third-party.md +159 -0
- package/skills/golang-security/references/threat-modeling.md +189 -0
- package/skills/golang-testing/SKILL.md +720 -0
- package/skills/grill-me/SKILL.md +7 -0
- package/skills/javascript-testing-patterns/SKILL.md +537 -0
- package/skills/javascript-testing-patterns/references/advanced-testing-patterns.md +513 -0
- package/skills/modern-javascript-patterns/SKILL.md +43 -0
- package/skills/modern-javascript-patterns/references/advanced-patterns.md +487 -0
- package/skills/modern-javascript-patterns/references/details.md +457 -0
- package/skills/python-anti-patterns/SKILL.md +349 -0
- package/skills/python-design-patterns/SKILL.md +85 -0
- package/skills/python-design-patterns/references/details.md +353 -0
- package/skills/python-error-handling/SKILL.md +193 -0
- package/skills/python-error-handling/references/details.md +171 -0
- package/skills/python-testing-patterns/SKILL.md +278 -0
- package/skills/python-testing-patterns/references/advanced-patterns.md +411 -0
- package/skills/python-testing-patterns/references/details.md +349 -0
- package/skills/rust-patterns/SKILL.md +500 -0
- package/skills/rust-testing/SKILL.md +501 -0
- package/skills/security-review/SKILL.md +504 -0
- package/skills/security-review/cloud-infrastructure-security.md +361 -0
- package/skills/vue-best-practices/SKILL.md +154 -0
- package/skills/vue-best-practices/references/animation-class-based-technique.md +254 -0
- package/skills/vue-best-practices/references/animation-state-driven-technique.md +291 -0
- package/skills/vue-best-practices/references/component-async.md +97 -0
- package/skills/vue-best-practices/references/component-data-flow.md +307 -0
- package/skills/vue-best-practices/references/component-fallthrough-attrs.md +174 -0
- package/skills/vue-best-practices/references/component-keep-alive.md +137 -0
- package/skills/vue-best-practices/references/component-slots.md +216 -0
- package/skills/vue-best-practices/references/component-suspense.md +228 -0
- package/skills/vue-best-practices/references/component-teleport.md +108 -0
- package/skills/vue-best-practices/references/component-transition-group.md +128 -0
- package/skills/vue-best-practices/references/component-transition.md +125 -0
- package/skills/vue-best-practices/references/composables.md +290 -0
- package/skills/vue-best-practices/references/directives.md +162 -0
- package/skills/vue-best-practices/references/perf-avoid-component-abstraction-in-lists.md +159 -0
- package/skills/vue-best-practices/references/perf-v-once-v-memo-directives.md +182 -0
- package/skills/vue-best-practices/references/perf-virtualize-large-lists.md +187 -0
- package/skills/vue-best-practices/references/plugins.md +166 -0
- package/skills/vue-best-practices/references/reactivity.md +344 -0
- package/skills/vue-best-practices/references/render-functions.md +201 -0
- package/skills/vue-best-practices/references/sfc.md +310 -0
- package/skills/vue-best-practices/references/state-management.md +135 -0
- package/skills/vue-best-practices/references/updated-hook-performance.md +187 -0
- package/skills/vue-pinia-best-practices/SKILL.md +21 -0
- package/skills/vue-pinia-best-practices/reference/pinia-no-active-pinia-error.md +248 -0
- package/skills/vue-pinia-best-practices/reference/pinia-setup-store-return-all-state.md +227 -0
- package/skills/vue-pinia-best-practices/reference/pinia-store-destructuring-breaks-reactivity.md +193 -0
- package/skills/vue-pinia-best-practices/reference/state-url-for-ephemeral-filters.md +238 -0
- package/skills/vue-pinia-best-practices/reference/state-use-pinia-for-large-apps.md +262 -0
- package/skills/vue-pinia-best-practices/reference/store-method-binding-parentheses.md +191 -0
- package/skills/zig-0.16/SKILL.md +840 -0
- package/skills/zig-0.16/scripts/check-zig-version.sh +21 -0
- package/src/cli/analyses.ts +280 -0
- package/src/cli/index.ts +108 -0
- package/src/cli/serve.ts +192 -0
- package/src/cli/smoke.ts +131 -0
- package/src/cli/status.test.ts +204 -0
- package/src/cli/status.ts +263 -0
- package/src/cli/vacuum.test.ts +82 -0
- package/src/cli/vacuum.ts +96 -0
- package/src/config/schema.test.ts +88 -0
- package/src/config/schema.ts +64 -0
- package/src/db/analyses-migration.test.ts +210 -0
- package/src/db/analyses.test.ts +466 -0
- package/src/db/analyses.ts +375 -0
- package/src/db/auto-checkpoint.ts +131 -0
- package/src/db/client.test.ts +129 -0
- package/src/db/client.ts +55 -0
- package/src/db/fts-escape.ts +20 -0
- package/src/db/incidents.test.ts +201 -0
- package/src/db/incidents.ts +93 -0
- package/src/db/index.ts +86 -0
- package/src/db/migrations-v13.test.ts +141 -0
- package/src/db/migrations-v8.test.ts +301 -0
- package/src/db/migrations.ts +147 -0
- package/src/db/plan-archive.test.ts +180 -0
- package/src/db/plan-archive.ts +274 -0
- package/src/db/plan-create.test.ts +276 -0
- package/src/db/plan-create.ts +78 -0
- package/src/db/plan-files.test.ts +289 -0
- package/src/db/plan-update-status.ts +287 -0
- package/src/db/plans.test.ts +490 -0
- package/src/db/plans.ts +534 -0
- package/src/db/resolve-project-dir.test.ts +143 -0
- package/src/db/resolve-project-dir.ts +75 -0
- package/src/db/rollbacks.test.ts +150 -0
- package/src/db/rollbacks.ts +67 -0
- package/src/db/schema.ts +907 -0
- package/src/db/sessions.test.ts +80 -0
- package/src/db/sessions.ts +135 -0
- package/src/db/shutdown.test.ts +147 -0
- package/src/db/shutdown.ts +45 -0
- package/src/db/tasks.test.ts +921 -0
- package/src/db/tasks.ts +747 -0
- package/src/db/types.ts +619 -0
- package/src/http/__tests__/auth.test.ts +196 -0
- package/src/http/__tests__/routes.test.ts +465 -0
- package/src/http/__tests__/sse.test.ts +317 -0
- package/src/http/auth.ts +72 -0
- package/src/http/middleware/cors.ts +53 -0
- package/src/http/middleware/security-headers.ts +21 -0
- package/src/http/routes/events.ts +112 -0
- package/src/http/routes/health.ts +51 -0
- package/src/http/routes/plans.ts +66 -0
- package/src/http/routes/sessions.ts +50 -0
- package/src/http/routes/tasks.ts +60 -0
- package/src/http/server.ts +95 -0
- package/src/http/sse.ts +116 -0
- package/src/index.ts +37 -0
- package/src/lib.ts +65 -0
- package/src/mem/scoped.ts +65 -0
- package/src/orchestrator/background.test.ts +268 -0
- package/src/orchestrator/background.ts +293 -0
- package/src/orchestrator/memory-hook.ts +182 -0
- package/src/orchestrator/reconciler.ts +123 -0
- package/src/orchestrator/scheduler.test.ts +300 -0
- package/src/orchestrator/scheduler.ts +243 -0
- package/src/plugin.test.ts +2574 -0
- package/src/plugin.ts +1690 -0
- package/src/sdk/client.ts +66 -0
- package/src/worktrees/manager.ts +236 -0
- package/src/worktrees/state.ts +87 -0
- package/tests/integration/ranger-flow.test.ts +257 -0
- package/tools/analysis_archive.ts +28 -0
- package/tools/analysis_create.ts +55 -0
- package/tools/analysis_get.ts +33 -0
- package/tools/analysis_link_plan.ts +44 -0
- package/tools/analysis_list.ts +48 -0
- package/tools/analysis_search.ts +36 -0
- package/tools/analysis_update.ts +44 -0
- package/tools/plan_approve.ts +31 -0
- package/tools/plan_create.ts +58 -0
- package/tools/plan_get.ts +40 -0
- package/tools/plan_list.ts +37 -0
- package/tools/plan_search.ts +34 -0
- package/tools/plan_update_status.ts +71 -0
- package/tools/session_checkpoint.ts +31 -0
- package/tools/session_end.ts +26 -0
- package/tools/session_start.ts +43 -0
- package/tools/task_create_batch.ts +70 -0
- package/tools/task_list.ts +35 -0
- package/tools/task_next_for_agent.ts +30 -0
- package/tools/task_search.ts +34 -0
- package/tools/task_update_status.ts +37 -0
- package/tsconfig.json +31 -0
|
@@ -0,0 +1,256 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ndomo v4 migration smoke test.
|
|
3
|
+
*
|
|
4
|
+
* Verifies all 6 fixes work correctly:
|
|
5
|
+
* 1. priority CHECK trigger (1-4)
|
|
6
|
+
* 2. slug format validation trigger (kebab-case)
|
|
7
|
+
* 3. plan_progress view
|
|
8
|
+
* 4. FTS5 diacritics normalization
|
|
9
|
+
* 5. result/error truncation (code-level)
|
|
10
|
+
* 6. metadata DEFAULT '{}' trigger
|
|
11
|
+
*
|
|
12
|
+
* Usage: bun run scripts/smoke-v4.ts
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import { Database } from "bun:sqlite";
|
|
16
|
+
import { runMigrations } from "../src/db/migrations.ts";
|
|
17
|
+
import { createPlan, getPlanProgress } from "../src/db/plans.ts";
|
|
18
|
+
import { createTasksBatch, getTask, updateTaskStatus } from "../src/db/tasks.ts";
|
|
19
|
+
import type { Plan } from "../src/db/types.ts";
|
|
20
|
+
|
|
21
|
+
let pass = 0;
|
|
22
|
+
let fail = 0;
|
|
23
|
+
|
|
24
|
+
function assert(cond: boolean, msg: string): void {
|
|
25
|
+
if (cond) {
|
|
26
|
+
pass++;
|
|
27
|
+
console.log(` ✓ ${msg}`);
|
|
28
|
+
} else {
|
|
29
|
+
fail++;
|
|
30
|
+
console.error(` ✗ ${msg}`);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function assertThrows(fn: () => void, msg: string): void {
|
|
35
|
+
try {
|
|
36
|
+
fn();
|
|
37
|
+
fail++;
|
|
38
|
+
console.error(` ✗ ${msg} (expected error, got none)`);
|
|
39
|
+
} catch {
|
|
40
|
+
pass++;
|
|
41
|
+
console.log(` ✓ ${msg}`);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const PLAN_DEFAULTS = {
|
|
46
|
+
title: "Test",
|
|
47
|
+
status: "draft" as const,
|
|
48
|
+
priority: 3,
|
|
49
|
+
overview: "test",
|
|
50
|
+
complexity: 3,
|
|
51
|
+
createdBy: "smoke",
|
|
52
|
+
updatedBy: "smoke",
|
|
53
|
+
metadata: {},
|
|
54
|
+
approvedAt: null,
|
|
55
|
+
completedAt: null,
|
|
56
|
+
sessionId: null,
|
|
57
|
+
approach: null,
|
|
58
|
+
sourceSessionId: null,
|
|
59
|
+
sourceMessageId: null,
|
|
60
|
+
category: null,
|
|
61
|
+
archivedAt: null,
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
function mkPlan(
|
|
65
|
+
overrides: Pick<Plan, "id" | "slug"> &
|
|
66
|
+
Partial<Omit<Plan, "id" | "slug" | "createdAt" | "updatedAt">>,
|
|
67
|
+
): Omit<Plan, "createdAt" | "updatedAt"> {
|
|
68
|
+
return { ...PLAN_DEFAULTS, ...overrides };
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const TASK_DEFAULTS = {
|
|
72
|
+
agent: "smoke",
|
|
73
|
+
files: [] as string[],
|
|
74
|
+
complexity: 3,
|
|
75
|
+
createdBy: "smoke",
|
|
76
|
+
updatedBy: "smoke",
|
|
77
|
+
sourceSessionId: null,
|
|
78
|
+
sourceMessageId: null,
|
|
79
|
+
reviewedBy: null,
|
|
80
|
+
tokensUsed: null,
|
|
81
|
+
durationMs: null,
|
|
82
|
+
artifacts: [] as string[],
|
|
83
|
+
dependencies: [] as string[],
|
|
84
|
+
metadata: {},
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
// Use in-memory DB for isolated smoke test
|
|
88
|
+
const db = new Database(":memory:");
|
|
89
|
+
db.exec("PRAGMA journal_mode = WAL");
|
|
90
|
+
db.exec("PRAGMA foreign_keys = ON");
|
|
91
|
+
|
|
92
|
+
console.log("ndomo v4 smoke test\n");
|
|
93
|
+
|
|
94
|
+
// ── Run migrations ──────────────────────────────────────────────────────────
|
|
95
|
+
console.log("Running migrations v1→v4...");
|
|
96
|
+
runMigrations(db);
|
|
97
|
+
const ver = db.query("SELECT MAX(version) as v FROM schema_version").get() as { v: number };
|
|
98
|
+
assert(ver.v >= 4, `schema_version >= 4 (got ${ver.v})`);
|
|
99
|
+
|
|
100
|
+
// ── Fix 1: priority CHECK ───────────────────────────────────────────────────
|
|
101
|
+
console.log("\nFix 1: priority CHECK trigger");
|
|
102
|
+
createPlan(db, mkPlan({ id: "p1", slug: "test-plan", priority: 1 }));
|
|
103
|
+
assert(true, "priority=1 accepted");
|
|
104
|
+
|
|
105
|
+
createPlan(db, mkPlan({ id: "p2", slug: "test-plan-2", priority: 4 }));
|
|
106
|
+
assert(true, "priority=4 accepted");
|
|
107
|
+
|
|
108
|
+
assertThrows(
|
|
109
|
+
() => createPlan(db, mkPlan({ id: "p-bad", slug: "bad-priority", priority: 5 })),
|
|
110
|
+
"priority=5 rejected",
|
|
111
|
+
);
|
|
112
|
+
|
|
113
|
+
assertThrows(
|
|
114
|
+
() => createPlan(db, mkPlan({ id: "p-bad2", slug: "bad-priority-2", priority: 0 })),
|
|
115
|
+
"priority=0 rejected",
|
|
116
|
+
);
|
|
117
|
+
|
|
118
|
+
// ── Fix 2: slug validation ──────────────────────────────────────────────────
|
|
119
|
+
console.log("\nFix 2: slug format validation");
|
|
120
|
+
assertThrows(
|
|
121
|
+
() => createPlan(db, mkPlan({ id: "s-bad1", slug: "Foo Bar" })),
|
|
122
|
+
"slug 'Foo Bar' rejected (space)",
|
|
123
|
+
);
|
|
124
|
+
assertThrows(
|
|
125
|
+
() => createPlan(db, mkPlan({ id: "s-bad2", slug: "foo_bar" })),
|
|
126
|
+
"slug 'foo_bar' rejected (underscore)",
|
|
127
|
+
);
|
|
128
|
+
assertThrows(
|
|
129
|
+
() => createPlan(db, mkPlan({ id: "s-bad3", slug: "foo--bar" })),
|
|
130
|
+
"slug 'foo--bar' rejected (double dash)",
|
|
131
|
+
);
|
|
132
|
+
assertThrows(
|
|
133
|
+
() => createPlan(db, mkPlan({ id: "s-bad4", slug: "foo-" })),
|
|
134
|
+
"slug 'foo-' rejected (trailing dash)",
|
|
135
|
+
);
|
|
136
|
+
|
|
137
|
+
// Valid slugs
|
|
138
|
+
createPlan(db, mkPlan({ id: "s-ok1", slug: "foo-bar" }));
|
|
139
|
+
assert(true, "slug 'foo-bar' accepted");
|
|
140
|
+
createPlan(db, mkPlan({ id: "s-ok2", slug: "foo123" }));
|
|
141
|
+
assert(true, "slug 'foo123' accepted");
|
|
142
|
+
createPlan(db, mkPlan({ id: "s-ok3", slug: "foo" }));
|
|
143
|
+
assert(true, "slug 'foo' accepted");
|
|
144
|
+
|
|
145
|
+
// ── Fix 3: plan_progress view ───────────────────────────────────────────────
|
|
146
|
+
console.log("\nFix 3: plan_progress view");
|
|
147
|
+
|
|
148
|
+
createTasksBatch(db, "p1", [
|
|
149
|
+
{ ...TASK_DEFAULTS, description: "task a", orderIndex: 0 },
|
|
150
|
+
{ ...TASK_DEFAULTS, description: "task b", orderIndex: 1 },
|
|
151
|
+
{ ...TASK_DEFAULTS, description: "task c", orderIndex: 2 },
|
|
152
|
+
]);
|
|
153
|
+
|
|
154
|
+
const tasks = db
|
|
155
|
+
.query("SELECT id FROM plan_tasks WHERE plan_id = 'p1' ORDER BY order_index")
|
|
156
|
+
.all() as Array<{ id: string }>;
|
|
157
|
+
const t0 = tasks[0]?.id;
|
|
158
|
+
const t1 = tasks[1]?.id;
|
|
159
|
+
const t2 = tasks[2]?.id;
|
|
160
|
+
if (t0) updateTaskStatus(db, t0, "done", { result: "ok" });
|
|
161
|
+
if (t1) updateTaskStatus(db, t1, "failed", { error: "oops" });
|
|
162
|
+
// t2 remains pending
|
|
163
|
+
|
|
164
|
+
const progress = getPlanProgress(db, "p1");
|
|
165
|
+
assert(progress.length === 1, "plan_progress returns 1 row for p1");
|
|
166
|
+
const p = progress[0];
|
|
167
|
+
if (p) {
|
|
168
|
+
assert(p.totalTasks === 3, "total_tasks = 3");
|
|
169
|
+
assert(p.done === 1, "done = 1");
|
|
170
|
+
assert(p.failed === 1, "failed = 1");
|
|
171
|
+
assert(p.pending === 1, "pending = 1");
|
|
172
|
+
assert(p.progressPct === 33, "progress_pct = 33 (1/3 * 100 rounded)");
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// All plans
|
|
176
|
+
const allProgress = getPlanProgress(db);
|
|
177
|
+
assert(allProgress.length >= 3, `plan_progress returns ${allProgress.length} rows (all plans)`);
|
|
178
|
+
|
|
179
|
+
// ── Fix 4: FTS5 diacritics ──────────────────────────────────────────────────
|
|
180
|
+
console.log("\nFix 4: FTS5 diacritics normalization");
|
|
181
|
+
|
|
182
|
+
createPlan(
|
|
183
|
+
db,
|
|
184
|
+
mkPlan({
|
|
185
|
+
id: "fts1",
|
|
186
|
+
slug: "fts-test",
|
|
187
|
+
title: "Implementación de acción correctiva",
|
|
188
|
+
overview: "Revisión del módulo de conexión",
|
|
189
|
+
}),
|
|
190
|
+
);
|
|
191
|
+
|
|
192
|
+
const ftsResults = db
|
|
193
|
+
.query("SELECT id FROM plans_fts_v2 WHERE plans_fts_v2 MATCH ?")
|
|
194
|
+
.all("accion") as Array<{ id: string }>;
|
|
195
|
+
assert(ftsResults.length === 1, "FTS5: search 'accion' finds 'acción' (diacritics normalized)");
|
|
196
|
+
|
|
197
|
+
const ftsResults2 = db
|
|
198
|
+
.query("SELECT id FROM plans_fts_v2 WHERE plans_fts_v2 MATCH ?")
|
|
199
|
+
.all("implementacion") as Array<{ id: string }>;
|
|
200
|
+
assert(ftsResults2.length === 1, "FTS5: search 'implementacion' finds 'Implementación'");
|
|
201
|
+
|
|
202
|
+
// Task FTS
|
|
203
|
+
if (t0) updateTaskStatus(db, t0, "done", { result: "Corrección aplicada exitosamente" });
|
|
204
|
+
const taskFts = db
|
|
205
|
+
.query("SELECT id FROM tasks_fts WHERE tasks_fts MATCH ?")
|
|
206
|
+
.all("correccion") as Array<{ id: string }>;
|
|
207
|
+
assert(taskFts.length === 1, "tasks_fts: search 'correccion' finds 'Corrección'");
|
|
208
|
+
|
|
209
|
+
// ── Fix 5: result/error truncation ───────────────────────────────────────────
|
|
210
|
+
console.log("\nFix 5: result/error truncation");
|
|
211
|
+
|
|
212
|
+
const bigResult = "x".repeat(20 * 1024); // 20 KB
|
|
213
|
+
if (t2) updateTaskStatus(db, t2, "done", { result: bigResult });
|
|
214
|
+
const truncated = t2 ? getTask(db, t2) : null;
|
|
215
|
+
assert(truncated !== null, "task exists after update");
|
|
216
|
+
if (truncated?.result) {
|
|
217
|
+
assert(
|
|
218
|
+
truncated.result.length <= 16 * 1024,
|
|
219
|
+
`result truncated: ${truncated.result.length} bytes <= 16384`,
|
|
220
|
+
);
|
|
221
|
+
assert(truncated.result.endsWith("…[truncated]"), "result ends with truncation marker");
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
// ── Fix 6: metadata DEFAULT trigger ─────────────────────────────────────────
|
|
225
|
+
console.log("\nFix 6: metadata DEFAULT '{}' trigger");
|
|
226
|
+
|
|
227
|
+
db.query(
|
|
228
|
+
`INSERT INTO plan_tasks (id, plan_id, order_index, description, agent, files, complexity, status, dependencies, metadata, created_by, updated_by)
|
|
229
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
230
|
+
).run(
|
|
231
|
+
"meta-test",
|
|
232
|
+
"p1",
|
|
233
|
+
99,
|
|
234
|
+
"meta test",
|
|
235
|
+
"smoke",
|
|
236
|
+
"[]",
|
|
237
|
+
3,
|
|
238
|
+
"pending",
|
|
239
|
+
"[]",
|
|
240
|
+
null,
|
|
241
|
+
"smoke",
|
|
242
|
+
"smoke",
|
|
243
|
+
);
|
|
244
|
+
const metaTask = db.query("SELECT metadata FROM plan_tasks WHERE id = 'meta-test'").get() as {
|
|
245
|
+
metadata: string;
|
|
246
|
+
} | null;
|
|
247
|
+
assert(metaTask !== null, "direct insert with NULL metadata succeeded");
|
|
248
|
+
if (metaTask) {
|
|
249
|
+
assert(metaTask.metadata === "{}", `metadata defaulted to '{}': got '${metaTask.metadata}'`);
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
// ── Summary ─────────────────────────────────────────────────────────────────
|
|
253
|
+
console.log(`\n${"─".repeat(50)}`);
|
|
254
|
+
console.log(`Results: ${pass} passed, ${fail} failed`);
|
|
255
|
+
db.close();
|
|
256
|
+
process.exit(fail > 0 ? 1 : 0);
|
|
@@ -0,0 +1,397 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ndomo v5 migration smoke test.
|
|
3
|
+
*
|
|
4
|
+
* Verifies soft delete + auto-archive functionality:
|
|
5
|
+
* 1. Create plan + 2 tasks + 1 session, mark completed → auto-archive fires
|
|
6
|
+
* 2. listPlans() (default) excludes archived plan
|
|
7
|
+
* 3. listPlans({ includeArchived: true }) includes archived plan
|
|
8
|
+
* 4. searchPlans() (default) excludes archived plan
|
|
9
|
+
* 5. Markdown file exists, >500 bytes, contains title/tasks/sessions
|
|
10
|
+
* 6. plan_tasks.archived_at set for both tasks
|
|
11
|
+
* 7. sessions.archived_at set for the session
|
|
12
|
+
* 8. Re-archive throws "already archived"
|
|
13
|
+
* 9. searchPlans returns results ordered by rank (FTS5 relevance)
|
|
14
|
+
* 10. nextTaskForAgent skips archived tasks by default
|
|
15
|
+
* 11. findPlansByCategory excludes archived by default
|
|
16
|
+
* 12. plan_progress view excludes archived tasks from counts
|
|
17
|
+
*
|
|
18
|
+
* Usage: bun run scripts/smoke-v5.ts
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
import { Database } from "bun:sqlite";
|
|
22
|
+
import { existsSync, mkdirSync, readFileSync, rmSync } from "node:fs";
|
|
23
|
+
import { tmpdir } from "node:os";
|
|
24
|
+
import { join } from "node:path";
|
|
25
|
+
import { runMigrations } from "../src/db/migrations.ts";
|
|
26
|
+
import { archivePlan } from "../src/db/plan-archive.ts";
|
|
27
|
+
import {
|
|
28
|
+
createPlan,
|
|
29
|
+
findPlansByCategory,
|
|
30
|
+
getPlanProgress,
|
|
31
|
+
listPlans,
|
|
32
|
+
searchPlans,
|
|
33
|
+
} from "../src/db/plans.ts";
|
|
34
|
+
import { startSession } from "../src/db/sessions.ts";
|
|
35
|
+
import { createTasksBatch, listTasksByPlan, nextTaskForAgent } from "../src/db/tasks.ts";
|
|
36
|
+
import type { Plan } from "../src/db/types.ts";
|
|
37
|
+
|
|
38
|
+
let pass = 0;
|
|
39
|
+
let fail = 0;
|
|
40
|
+
|
|
41
|
+
function assert(cond: boolean, msg: string): void {
|
|
42
|
+
if (cond) {
|
|
43
|
+
pass++;
|
|
44
|
+
console.log(` ✓ ${msg}`);
|
|
45
|
+
} else {
|
|
46
|
+
fail++;
|
|
47
|
+
console.error(` ✗ ${msg}`);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function assertThrows(fn: () => void, expectedMsg: string, msg: string): void {
|
|
52
|
+
try {
|
|
53
|
+
fn();
|
|
54
|
+
fail++;
|
|
55
|
+
console.error(` ✗ ${msg} (expected error, got none)`);
|
|
56
|
+
} catch (err) {
|
|
57
|
+
const errMsg = err instanceof Error ? err.message : String(err);
|
|
58
|
+
if (errMsg.includes(expectedMsg)) {
|
|
59
|
+
pass++;
|
|
60
|
+
console.log(` ✓ ${msg}`);
|
|
61
|
+
} else {
|
|
62
|
+
fail++;
|
|
63
|
+
console.error(` ✗ ${msg} (expected '${expectedMsg}', got '${errMsg}')`);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const PLAN_DEFAULTS = {
|
|
69
|
+
title: "Test",
|
|
70
|
+
status: "draft" as const,
|
|
71
|
+
priority: 3,
|
|
72
|
+
overview: "test",
|
|
73
|
+
complexity: 3,
|
|
74
|
+
createdBy: "smoke",
|
|
75
|
+
updatedBy: "smoke",
|
|
76
|
+
metadata: {},
|
|
77
|
+
approvedAt: null,
|
|
78
|
+
completedAt: null,
|
|
79
|
+
sessionId: null,
|
|
80
|
+
approach: "Test approach for smoke validation",
|
|
81
|
+
sourceSessionId: null,
|
|
82
|
+
sourceMessageId: null,
|
|
83
|
+
category: null as Plan["category"],
|
|
84
|
+
archivedAt: null,
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
const TASK_DEFAULTS = {
|
|
88
|
+
agent: "smoke",
|
|
89
|
+
files: [] as string[],
|
|
90
|
+
complexity: 3,
|
|
91
|
+
createdBy: "smoke",
|
|
92
|
+
updatedBy: "smoke",
|
|
93
|
+
sourceSessionId: null,
|
|
94
|
+
sourceMessageId: null,
|
|
95
|
+
reviewedBy: null,
|
|
96
|
+
tokensUsed: null,
|
|
97
|
+
durationMs: null,
|
|
98
|
+
artifacts: [] as string[],
|
|
99
|
+
dependencies: [] as string[],
|
|
100
|
+
metadata: {},
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
// Use in-memory DB + temp dir for isolated smoke test
|
|
104
|
+
const db = new Database(":memory:");
|
|
105
|
+
db.exec("PRAGMA journal_mode = WAL");
|
|
106
|
+
db.exec("PRAGMA foreign_keys = ON");
|
|
107
|
+
|
|
108
|
+
const testMemDir = join(tmpdir(), `ndomo-smoke-v5-${Date.now()}`);
|
|
109
|
+
mkdirSync(testMemDir, { recursive: true });
|
|
110
|
+
|
|
111
|
+
console.log("ndomo v5 smoke test\n");
|
|
112
|
+
|
|
113
|
+
// ── Run migrations ──────────────────────────────────────────────────────────
|
|
114
|
+
console.log("Running migrations v1→v5...");
|
|
115
|
+
runMigrations(db);
|
|
116
|
+
const ver = db.query("SELECT MAX(version) as v FROM schema_version").get() as { v: number };
|
|
117
|
+
assert(ver.v === 5, "schema_version = 5");
|
|
118
|
+
|
|
119
|
+
// Verify archived_at columns exist
|
|
120
|
+
const planCols = db.query("PRAGMA table_info(plans)").all() as Array<{ name: string }>;
|
|
121
|
+
assert(
|
|
122
|
+
planCols.some((c) => c.name === "archived_at"),
|
|
123
|
+
"plans.archived_at column exists",
|
|
124
|
+
);
|
|
125
|
+
|
|
126
|
+
// ── Test 1: Create plan + tasks + session, archive via function ─────────────
|
|
127
|
+
console.log("\nTest 1: Create plan + 2 tasks + 1 session, archive");
|
|
128
|
+
|
|
129
|
+
const planOverrides = {
|
|
130
|
+
id: "archive-test",
|
|
131
|
+
slug: "archive-test-plan",
|
|
132
|
+
title: "Archive Test Plan",
|
|
133
|
+
overview: "A plan for testing the archive flow with tasks and sessions",
|
|
134
|
+
};
|
|
135
|
+
|
|
136
|
+
createPlan(db, {
|
|
137
|
+
...PLAN_DEFAULTS,
|
|
138
|
+
...planOverrides,
|
|
139
|
+
status: "draft" as const,
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
createTasksBatch(db, "archive-test", [
|
|
143
|
+
{ ...TASK_DEFAULTS, description: "Implement feature A", orderIndex: 0 },
|
|
144
|
+
{ ...TASK_DEFAULTS, description: "Write tests for A", orderIndex: 1 },
|
|
145
|
+
]);
|
|
146
|
+
|
|
147
|
+
startSession(db, {
|
|
148
|
+
id: "session-archive-test",
|
|
149
|
+
goal: "Build feature A",
|
|
150
|
+
planId: "archive-test",
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
// Archive the plan
|
|
154
|
+
const result = archivePlan(db, "archive-test", { memDir: testMemDir });
|
|
155
|
+
assert(result.planId === "archive-test", `archivePlan returned planId=${result.planId}`);
|
|
156
|
+
assert(result.tasksCount === 2, `archivePlan tasksCount=${result.tasksCount}`);
|
|
157
|
+
assert(result.sessionsCount === 1, `archivePlan sessionsCount=${result.sessionsCount}`);
|
|
158
|
+
assert(result.filePath.startsWith(testMemDir), `filePath in testMemDir: ${result.filePath}`);
|
|
159
|
+
|
|
160
|
+
// ── Test 2: listPlans() default excludes archived ───────────────────────────
|
|
161
|
+
console.log("\nTest 2: listPlans() excludes archived by default");
|
|
162
|
+
|
|
163
|
+
// Create another active plan to ensure listPlans still returns it
|
|
164
|
+
createPlan(db, {
|
|
165
|
+
...PLAN_DEFAULTS,
|
|
166
|
+
id: "active-plan",
|
|
167
|
+
slug: "active-plan",
|
|
168
|
+
title: "Active Plan",
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
const activePlans = listPlans(db);
|
|
172
|
+
assert(
|
|
173
|
+
activePlans.every((p) => p.archivedAt === null),
|
|
174
|
+
"listPlans() returns only active plans (archived_at IS NULL)",
|
|
175
|
+
);
|
|
176
|
+
assert(
|
|
177
|
+
!activePlans.some((p) => p.id === "archive-test"),
|
|
178
|
+
"listPlans() does NOT include archived plan",
|
|
179
|
+
);
|
|
180
|
+
|
|
181
|
+
// ── Test 3: listPlans({ includeArchived: true }) includes archived ──────────
|
|
182
|
+
console.log("\nTest 3: listPlans({ includeArchived: true }) includes archived");
|
|
183
|
+
|
|
184
|
+
const allPlans = listPlans(db, { includeArchived: true });
|
|
185
|
+
assert(
|
|
186
|
+
allPlans.some((p) => p.id === "archive-test"),
|
|
187
|
+
"listPlans({ includeArchived: true }) includes archived plan",
|
|
188
|
+
);
|
|
189
|
+
|
|
190
|
+
// ── Test 4: searchPlans() excludes archived ─────────────────────────────────
|
|
191
|
+
console.log("\nTest 4: searchPlans() excludes archived by default");
|
|
192
|
+
|
|
193
|
+
// FTS search for a word from the archived plan's title
|
|
194
|
+
const searchResults = searchPlans(db, "archive");
|
|
195
|
+
assert(
|
|
196
|
+
!searchResults.some((p) => p.id === "archive-test"),
|
|
197
|
+
"searchPlans() does NOT find archived plan",
|
|
198
|
+
);
|
|
199
|
+
|
|
200
|
+
// ── Test 5: Markdown file validation ────────────────────────────────────────
|
|
201
|
+
console.log("\nTest 5: Markdown file exists and is valid");
|
|
202
|
+
|
|
203
|
+
const mdFile = result.filePath;
|
|
204
|
+
assert(existsSync(mdFile), `markdown file exists: ${mdFile}`);
|
|
205
|
+
|
|
206
|
+
const mdContent = readFileSync(mdFile, "utf-8");
|
|
207
|
+
assert(mdContent.length > 500, `markdown > 500 bytes: ${mdContent.length} bytes`);
|
|
208
|
+
assert(
|
|
209
|
+
mdContent.includes("# Plan: Archive Test Plan"),
|
|
210
|
+
"markdown contains '# Plan: Archive Test Plan'",
|
|
211
|
+
);
|
|
212
|
+
assert(mdContent.includes("Implement feature A"), "markdown contains task 1 description");
|
|
213
|
+
assert(mdContent.includes("Write tests for A"), "markdown contains task 2 description");
|
|
214
|
+
assert(mdContent.includes("session-"), "markdown contains session ID prefix");
|
|
215
|
+
|
|
216
|
+
// ── Test 6: plan_tasks.archived_at set ──────────────────────────────────────
|
|
217
|
+
console.log("\nTest 6: plan_tasks.archived_at is set");
|
|
218
|
+
|
|
219
|
+
const archivedTasks = listTasksByPlan(db, "archive-test", { includeArchived: true });
|
|
220
|
+
assert(
|
|
221
|
+
archivedTasks.length === 2 && archivedTasks.every((t) => t.archivedAt !== null),
|
|
222
|
+
"both tasks have archived_at set",
|
|
223
|
+
);
|
|
224
|
+
|
|
225
|
+
// ── Test 7: sessions.archived_at set ────────────────────────────────────────
|
|
226
|
+
console.log("\nTest 7: sessions.archived_at is set");
|
|
227
|
+
|
|
228
|
+
const archivedSession = db
|
|
229
|
+
.query("SELECT archived_at FROM sessions WHERE id = ?")
|
|
230
|
+
.get("session-archive-test") as { archived_at: number | null } | null;
|
|
231
|
+
assert(
|
|
232
|
+
archivedSession !== null && archivedSession.archived_at !== null,
|
|
233
|
+
"session.archived_at is set",
|
|
234
|
+
);
|
|
235
|
+
|
|
236
|
+
// ── Test 8: Re-archive throws "already archived" ────────────────────────────
|
|
237
|
+
console.log("\nTest 8: Re-archive throws 'already archived'");
|
|
238
|
+
|
|
239
|
+
assertThrows(
|
|
240
|
+
() => archivePlan(db, "archive-test", { memDir: testMemDir }),
|
|
241
|
+
"already archived",
|
|
242
|
+
"re-archive throws 'ndomo: plan already archived'",
|
|
243
|
+
);
|
|
244
|
+
|
|
245
|
+
// ── Test 9: searchPlans returns results ordered by rank ─────────────────────
|
|
246
|
+
console.log("\nTest 9: searchPlans returns results ordered by rank (FTS5 relevance)");
|
|
247
|
+
|
|
248
|
+
// Create 3 plans with different match-strength keywords
|
|
249
|
+
createPlan(db, {
|
|
250
|
+
...PLAN_DEFAULTS,
|
|
251
|
+
id: "rank-1",
|
|
252
|
+
slug: "rank-test-1",
|
|
253
|
+
title: "Database migration tool",
|
|
254
|
+
overview: "A tool for running database migration automatically",
|
|
255
|
+
});
|
|
256
|
+
|
|
257
|
+
createPlan(db, {
|
|
258
|
+
...PLAN_DEFAULTS,
|
|
259
|
+
id: "rank-2",
|
|
260
|
+
slug: "rank-test-2",
|
|
261
|
+
title: "Migration helper",
|
|
262
|
+
overview: "A helper utility for migration tasks in the database layer",
|
|
263
|
+
});
|
|
264
|
+
|
|
265
|
+
createPlan(db, {
|
|
266
|
+
...PLAN_DEFAULTS,
|
|
267
|
+
id: "rank-3",
|
|
268
|
+
slug: "rank-test-3",
|
|
269
|
+
title: "Simple utility",
|
|
270
|
+
overview: "A basic utility for common tasks",
|
|
271
|
+
});
|
|
272
|
+
|
|
273
|
+
// Search for "database" — rank-1 and rank-2 should match, rank-1 has it in title
|
|
274
|
+
const rankResults = searchPlans(db, "database", 10);
|
|
275
|
+
assert(rankResults.length >= 2, `searchPlans found ${rankResults.length} results for "database"`);
|
|
276
|
+
if (rankResults[0] && rankResults.length >= 2) {
|
|
277
|
+
// rank-1 has "Database" in title (higher rank), should be first
|
|
278
|
+
assert(
|
|
279
|
+
rankResults[0].id === "rank-1",
|
|
280
|
+
`searchPlans rank: rank-1 is first (got ${rankResults[0].id})`,
|
|
281
|
+
);
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
// ── Test 10: nextTaskForAgent skips archived tasks ──────────────────────────
|
|
285
|
+
console.log("\nTest 10: nextTaskForAgent skips archived tasks by default");
|
|
286
|
+
|
|
287
|
+
// Create a plan with tasks, then archive it
|
|
288
|
+
createPlan(db, {
|
|
289
|
+
...PLAN_DEFAULTS,
|
|
290
|
+
id: "agent-archived-plan",
|
|
291
|
+
slug: "agent-archived-plan",
|
|
292
|
+
title: "Agent Archived Plan",
|
|
293
|
+
});
|
|
294
|
+
|
|
295
|
+
createTasksBatch(db, "agent-archived-plan", [
|
|
296
|
+
{ ...TASK_DEFAULTS, description: "Task for archived plan", orderIndex: 0, agent: "test-agent" },
|
|
297
|
+
]);
|
|
298
|
+
|
|
299
|
+
// Archive the plan (this archives tasks too)
|
|
300
|
+
archivePlan(db, "agent-archived-plan", { memDir: testMemDir });
|
|
301
|
+
|
|
302
|
+
// nextTaskForAgent should NOT return archived task
|
|
303
|
+
const archivedTask = nextTaskForAgent(db, "test-agent");
|
|
304
|
+
assert(
|
|
305
|
+
archivedTask === null,
|
|
306
|
+
"nextTaskForAgent returns null for archived task (default includeArchived=false)",
|
|
307
|
+
);
|
|
308
|
+
|
|
309
|
+
// With includeArchived: true, should return the task
|
|
310
|
+
const archivedTaskIncluded = nextTaskForAgent(db, "test-agent", { includeArchived: true });
|
|
311
|
+
assert(
|
|
312
|
+
archivedTaskIncluded !== null && archivedTaskIncluded.planId === "agent-archived-plan",
|
|
313
|
+
"nextTaskForAgent with includeArchived=true returns archived task",
|
|
314
|
+
);
|
|
315
|
+
|
|
316
|
+
// ── Test 11: findPlansByCategory excludes archived by default ───────────────
|
|
317
|
+
console.log("\nTest 11: findPlansByCategory excludes archived by default");
|
|
318
|
+
|
|
319
|
+
// Create a plan with category, then archive it
|
|
320
|
+
createPlan(db, {
|
|
321
|
+
...PLAN_DEFAULTS,
|
|
322
|
+
id: "cat-archived-plan",
|
|
323
|
+
slug: "cat-archived-plan",
|
|
324
|
+
title: "Category Archived Plan",
|
|
325
|
+
category: "feature",
|
|
326
|
+
});
|
|
327
|
+
|
|
328
|
+
archivePlan(db, "cat-archived-plan", { memDir: testMemDir });
|
|
329
|
+
|
|
330
|
+
// Create an active plan with same category
|
|
331
|
+
createPlan(db, {
|
|
332
|
+
...PLAN_DEFAULTS,
|
|
333
|
+
id: "cat-active-plan",
|
|
334
|
+
slug: "cat-active-plan",
|
|
335
|
+
title: "Category Active Plan",
|
|
336
|
+
category: "feature",
|
|
337
|
+
});
|
|
338
|
+
|
|
339
|
+
const catResults = findPlansByCategory(db, "feature");
|
|
340
|
+
assert(
|
|
341
|
+
!catResults.some((p) => p.id === "cat-archived-plan"),
|
|
342
|
+
"findPlansByCategory default excludes archived plan",
|
|
343
|
+
);
|
|
344
|
+
assert(
|
|
345
|
+
catResults.some((p) => p.id === "cat-active-plan"),
|
|
346
|
+
"findPlansByCategory default includes active plan",
|
|
347
|
+
);
|
|
348
|
+
|
|
349
|
+
const catResultsAll = findPlansByCategory(db, "feature", 20, { includeArchived: true });
|
|
350
|
+
assert(
|
|
351
|
+
catResultsAll.some((p) => p.id === "cat-archived-plan"),
|
|
352
|
+
"findPlansByCategory with includeArchived=true includes archived plan",
|
|
353
|
+
);
|
|
354
|
+
|
|
355
|
+
// ── Test 12: plan_progress view excludes archived tasks ─────────────────────
|
|
356
|
+
console.log("\nTest 12: plan_progress view excludes archived tasks from counts");
|
|
357
|
+
|
|
358
|
+
// Create a plan with tasks, check progress, archive, check again
|
|
359
|
+
createPlan(db, {
|
|
360
|
+
...PLAN_DEFAULTS,
|
|
361
|
+
id: "progress-test",
|
|
362
|
+
slug: "progress-test",
|
|
363
|
+
title: "Progress Test Plan",
|
|
364
|
+
});
|
|
365
|
+
|
|
366
|
+
createTasksBatch(db, "progress-test", [
|
|
367
|
+
{ ...TASK_DEFAULTS, description: "Progress task 1", orderIndex: 0 },
|
|
368
|
+
{ ...TASK_DEFAULTS, description: "Progress task 2", orderIndex: 1 },
|
|
369
|
+
]);
|
|
370
|
+
|
|
371
|
+
// Check progress before archive — should show 2 tasks
|
|
372
|
+
const progressBefore = getPlanProgress(db, "progress-test");
|
|
373
|
+
const progressBeforeFirst = progressBefore[0];
|
|
374
|
+
assert(
|
|
375
|
+
progressBefore.length === 1 && progressBeforeFirst?.totalTasks === 2,
|
|
376
|
+
`plan_progress before archive: totalTasks=${progressBeforeFirst?.totalTasks ?? 0}`,
|
|
377
|
+
);
|
|
378
|
+
|
|
379
|
+
// Archive the plan (archives tasks too)
|
|
380
|
+
archivePlan(db, "progress-test", { memDir: testMemDir });
|
|
381
|
+
|
|
382
|
+
// Check progress after archive — should show 0 tasks (archived excluded)
|
|
383
|
+
const progressAfter = getPlanProgress(db, "progress-test");
|
|
384
|
+
const progressAfterFirst = progressAfter[0];
|
|
385
|
+
assert(
|
|
386
|
+
progressAfter.length === 1 && progressAfterFirst?.totalTasks === 0,
|
|
387
|
+
`plan_progress after archive: totalTasks=${progressAfterFirst?.totalTasks ?? 0} (archived excluded)`,
|
|
388
|
+
);
|
|
389
|
+
|
|
390
|
+
// ── Cleanup ─────────────────────────────────────────────────────────────────
|
|
391
|
+
rmSync(testMemDir, { recursive: true, force: true });
|
|
392
|
+
db.close();
|
|
393
|
+
|
|
394
|
+
// ── Summary ─────────────────────────────────────────────────────────────────
|
|
395
|
+
console.log(`\n${"─".repeat(50)}`);
|
|
396
|
+
console.log(`Results: ${pass} passed, ${fail} failed`);
|
|
397
|
+
process.exit(fail > 0 ? 1 : 0);
|
package/scripts/smoke.sh
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# ndomo smoke tests wrapper — runs src/cli/smoke.ts via bun
|
|
3
|
+
# Exit 0 if all smoke checks pass, exit 1 on any failure.
|
|
4
|
+
set -euo pipefail
|
|
5
|
+
|
|
6
|
+
cd "$(dirname "$0")/.."
|
|
7
|
+
|
|
8
|
+
echo "[smoke] running ndomo smoke tests via bun..."
|
|
9
|
+
bun run src/cli/smoke.ts
|