@dailephd/my-dev-kit-lab 0.2.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/README.md +272 -0
- package/benchmarks/contracts/benchmark-project-profiles.json +1199 -0
- package/benchmarks/contracts/todo-behavior.md +70 -0
- package/benchmarks/contracts/todo-benchmark-case.json +227 -0
- package/benchmarks/projects/README.md +34 -0
- package/benchmarks/projects/task-analytics-large-mixed/README.md +1 -0
- package/benchmarks/projects/task-analytics-large-mixed/py/task_analytics/__init__.py +3 -0
- package/benchmarks/projects/task-analytics-large-mixed/py/task_analytics/fixtures.py +6 -0
- package/benchmarks/projects/task-analytics-large-mixed/py/task_analytics/metrics.py +29 -0
- package/benchmarks/projects/task-analytics-large-mixed/py/task_analytics/models.py +21 -0
- package/benchmarks/projects/task-analytics-large-mixed/py/task_analytics/parser.py +16 -0
- package/benchmarks/projects/task-analytics-large-mixed/py/task_analytics/pipeline.py +9 -0
- package/benchmarks/projects/task-analytics-large-mixed/py/task_analytics/quality.py +8 -0
- package/benchmarks/projects/task-analytics-large-mixed/py/task_analytics/reporting.py +11 -0
- package/benchmarks/projects/task-analytics-large-mixed/py/tests/test_metrics.py +19 -0
- package/benchmarks/projects/task-analytics-large-mixed/py/tests/test_parser.py +15 -0
- package/benchmarks/projects/task-analytics-large-mixed/py/tests/test_quality.py +19 -0
- package/benchmarks/projects/task-analytics-large-mixed/py/tests/test_reporting.py +15 -0
- package/benchmarks/projects/task-analytics-large-mixed/ts/package.json +12 -0
- package/benchmarks/projects/task-analytics-large-mixed/ts/src/index.ts +11 -0
- package/benchmarks/projects/task-analytics-large-mixed/ts/src/models/analyticsSnapshot.ts +20 -0
- package/benchmarks/projects/task-analytics-large-mixed/ts/src/models/project.ts +5 -0
- package/benchmarks/projects/task-analytics-large-mixed/ts/src/models/task.ts +10 -0
- package/benchmarks/projects/task-analytics-large-mixed/ts/src/reporting/buildProjectLeaderboard.ts +7 -0
- package/benchmarks/projects/task-analytics-large-mixed/ts/src/reporting/formatTaskHealthReport.ts +13 -0
- package/benchmarks/projects/task-analytics-large-mixed/ts/src/services/buildAnalyticsSnapshot.ts +39 -0
- package/benchmarks/projects/task-analytics-large-mixed/ts/src/services/completeTask.ts +10 -0
- package/benchmarks/projects/task-analytics-large-mixed/ts/src/services/createTask.ts +21 -0
- package/benchmarks/projects/task-analytics-large-mixed/ts/src/services/listTasksByProject.ts +6 -0
- package/benchmarks/projects/task-analytics-large-mixed/ts/src/store/projectStore.ts +20 -0
- package/benchmarks/projects/task-analytics-large-mixed/ts/src/store/taskStore.ts +44 -0
- package/benchmarks/projects/task-analytics-large-mixed/ts/src/validation/projectValidation.ts +12 -0
- package/benchmarks/projects/task-analytics-large-mixed/ts/src/validation/taskValidation.ts +18 -0
- package/benchmarks/projects/task-analytics-large-mixed/ts/tests/buildAnalyticsSnapshot.test.ts +48 -0
- package/benchmarks/projects/task-analytics-large-mixed/ts/tests/completeTask.test.ts +21 -0
- package/benchmarks/projects/task-analytics-large-mixed/ts/tests/createTask.test.ts +31 -0
- package/benchmarks/projects/task-analytics-large-mixed/ts/tests/listTasksByProject.test.ts +18 -0
- package/benchmarks/projects/task-analytics-large-mixed/ts/tests/reporting.test.ts +19 -0
- package/benchmarks/projects/task-analytics-large-mixed/ts/tsconfig.json +12 -0
- package/benchmarks/projects/task-analytics-large-mixed/ts/vitest.config.ts +5 -0
- package/benchmarks/projects/task-workflow-medium-ts/README.md +1 -0
- package/benchmarks/projects/task-workflow-medium-ts/package.json +12 -0
- package/benchmarks/projects/task-workflow-medium-ts/src/index.ts +9 -0
- package/benchmarks/projects/task-workflow-medium-ts/src/models/project.ts +6 -0
- package/benchmarks/projects/task-workflow-medium-ts/src/models/task.ts +39 -0
- package/benchmarks/projects/task-workflow-medium-ts/src/services/completeTask.ts +15 -0
- package/benchmarks/projects/task-workflow-medium-ts/src/services/createTask.ts +26 -0
- package/benchmarks/projects/task-workflow-medium-ts/src/services/filterTasks.ts +17 -0
- package/benchmarks/projects/task-workflow-medium-ts/src/services/importTasks.ts +33 -0
- package/benchmarks/projects/task-workflow-medium-ts/src/services/summarizeTasks.ts +30 -0
- package/benchmarks/projects/task-workflow-medium-ts/src/store/taskStore.ts +76 -0
- package/benchmarks/projects/task-workflow-medium-ts/src/utils/deterministicId.ts +3 -0
- package/benchmarks/projects/task-workflow-medium-ts/src/validation/taskValidation.ts +45 -0
- package/benchmarks/projects/task-workflow-medium-ts/tests/completeTask.test.ts +16 -0
- package/benchmarks/projects/task-workflow-medium-ts/tests/createTask.test.ts +21 -0
- package/benchmarks/projects/task-workflow-medium-ts/tests/filterTasks.test.ts +18 -0
- package/benchmarks/projects/task-workflow-medium-ts/tests/importTasks.test.ts +22 -0
- package/benchmarks/projects/task-workflow-medium-ts/tests/summarizeTasks.test.ts +29 -0
- package/benchmarks/projects/task-workflow-medium-ts/tsconfig.json +12 -0
- package/benchmarks/projects/task-workflow-medium-ts/vitest.config.ts +5 -0
- package/benchmarks/projects/todo-js/README.md +3 -0
- package/benchmarks/projects/todo-js/package.json +11 -0
- package/benchmarks/projects/todo-js/src/index.js +2 -0
- package/benchmarks/projects/todo-js/src/taskService.js +37 -0
- package/benchmarks/projects/todo-js/src/taskStore.js +28 -0
- package/benchmarks/projects/todo-js/tests/taskService.test.js +45 -0
- package/benchmarks/projects/todo-js/vitest.config.js +5 -0
- package/benchmarks/projects/todo-mixed-ts-py/README.md +3 -0
- package/benchmarks/projects/todo-mixed-ts-py/package.json +13 -0
- package/benchmarks/projects/todo-mixed-ts-py/python/task_service.py +76 -0
- package/benchmarks/projects/todo-mixed-ts-py/src/taskCli.ts +38 -0
- package/benchmarks/projects/todo-mixed-ts-py/tests/mixedBoundary.test.ts +18 -0
- package/benchmarks/projects/todo-mixed-ts-py/tsconfig.json +12 -0
- package/benchmarks/projects/todo-mixed-ts-py/vitest.config.ts +5 -0
- package/benchmarks/projects/todo-python/README.md +3 -0
- package/benchmarks/projects/todo-python/src/__init__.py +4 -0
- package/benchmarks/projects/todo-python/src/task_service.py +32 -0
- package/benchmarks/projects/todo-python/src/task_store.py +28 -0
- package/benchmarks/projects/todo-python/tests/test_task_service.py +52 -0
- package/benchmarks/projects/todo-ts/README.md +3 -0
- package/benchmarks/projects/todo-ts/package.json +12 -0
- package/benchmarks/projects/todo-ts/src/index.ts +2 -0
- package/benchmarks/projects/todo-ts/src/taskService.ts +41 -0
- package/benchmarks/projects/todo-ts/src/taskStore.ts +34 -0
- package/benchmarks/projects/todo-ts/tests/taskService.test.ts +45 -0
- package/benchmarks/projects/todo-ts/tsconfig.json +12 -0
- package/benchmarks/projects/todo-ts/vitest.config.ts +5 -0
- package/dist/scripts/build-gallery.js +3 -0
- package/dist/scripts/capture-demo-report.js +3 -0
- package/dist/scripts/evaluate-token-savings.js +2 -0
- package/dist/scripts/experiments/describeExperiment.js +143 -0
- package/dist/scripts/experiments/listExperiments.js +44 -0
- package/dist/scripts/experiments/runExperiment.js +199 -0
- package/dist/scripts/generate-experiment-plots.js +3 -0
- package/dist/scripts/generate-prompt-variants.js +2 -0
- package/dist/scripts/render-experiment-report.js +2 -0
- package/dist/scripts/run-agent-prompt.js +2 -0
- package/dist/scripts/run-controlled-experiment.js +2 -0
- package/dist/scripts/run-final-demo.js +3 -0
- package/dist/scripts/run-lab-demo.js +5 -0
- package/dist/scripts/run-visualization-demos.js +3 -0
- package/dist/scripts/security/runCodeql.js +57 -0
- package/dist/scripts/security/runDependencyChecks.js +57 -0
- package/dist/scripts/security/runFuzzSmoke.js +29 -0
- package/dist/scripts/security/runPackageChecks.js +56 -0
- package/dist/scripts/security/runSemgrep.js +63 -0
- package/dist/scripts/security/validate.js +117 -0
- package/dist/scripts/verify-benchmarks.js +202 -0
- package/dist/src/agents/adapters/claudeAdapter.js +37 -0
- package/dist/src/agents/adapters/codexAdapter.js +110 -0
- package/dist/src/agents/adapters/fakeAgentAdapter.js +101 -0
- package/dist/src/agents/agentRegistry.js +21 -0
- package/dist/src/agents/index.js +7 -0
- package/dist/src/agents/parseAgentTokenUsage.js +137 -0
- package/dist/src/agents/runAgentPrompt.js +38 -0
- package/dist/src/agents/types.js +1 -0
- package/dist/src/commands/buildGalleryCommand.js +56 -0
- package/dist/src/commands/captureDemoReport.js +116 -0
- package/dist/src/commands/evaluateTokenSavings.js +175 -0
- package/dist/src/commands/generateExperimentPlotsCommand.js +38 -0
- package/dist/src/commands/generatePromptVariants.js +67 -0
- package/dist/src/commands/renderExperimentReportCommand.js +131 -0
- package/dist/src/commands/runAgentPromptCommand.js +132 -0
- package/dist/src/commands/runControlledExperimentCommand.js +174 -0
- package/dist/src/commands/runFinalDemoCommand.js +123 -0
- package/dist/src/commands/runLabDemo.js +62 -0
- package/dist/src/commands/runVisualizationDemosCommand.js +67 -0
- package/dist/src/core/commandLine.js +59 -0
- package/dist/src/core/countTokens.js +8 -0
- package/dist/src/core/fileGlobs.js +100 -0
- package/dist/src/core/localProjectTarget.js +75 -0
- package/dist/src/core/pathSafety.js +19 -0
- package/dist/src/core/pythonCommand.js +30 -0
- package/dist/src/core/resolveCommand.js +110 -0
- package/dist/src/core/runMeasuredCommand.js +143 -0
- package/dist/src/evaluation/benchmarkMetadata.js +207 -0
- package/dist/src/evaluation/buildExperimentMatrix.js +75 -0
- package/dist/src/evaluation/classifyAgentRunOutcome.js +40 -0
- package/dist/src/evaluation/compareExperimentRuns.js +79 -0
- package/dist/src/evaluation/compareTokenSavings.js +47 -0
- package/dist/src/evaluation/controlledExperimentTypes.js +1 -0
- package/dist/src/evaluation/index.js +18 -0
- package/dist/src/evaluation/parseAgentAnswer.js +230 -0
- package/dist/src/evaluation/projectComplexity.js +126 -0
- package/dist/src/evaluation/projectFileTree.js +83 -0
- package/dist/src/evaluation/readEvaluationCases.js +59 -0
- package/dist/src/evaluation/renderTokenSavingsReportInput.js +55 -0
- package/dist/src/evaluation/runControlledExperiment.js +158 -0
- package/dist/src/evaluation/runMyDevKitRetrieval.js +197 -0
- package/dist/src/evaluation/runRawFullFileBaseline.js +31 -0
- package/dist/src/evaluation/scoreCorrectness.js +127 -0
- package/dist/src/evaluation/types.js +1 -0
- package/dist/src/evaluation/writeExperimentArtifacts.js +104 -0
- package/dist/src/evaluation/writeTokenSavingsArtifacts.js +57 -0
- package/dist/src/experiments/config.js +24 -0
- package/dist/src/experiments/defaultRegistry.js +7 -0
- package/dist/src/experiments/errors.js +18 -0
- package/dist/src/experiments/index.js +9 -0
- package/dist/src/experiments/outputPaths.js +25 -0
- package/dist/src/experiments/plugins/contextStrategyComparison/config.js +37 -0
- package/dist/src/experiments/plugins/contextStrategyComparison/index.js +3 -0
- package/dist/src/experiments/plugins/contextStrategyComparison/plugin.js +83 -0
- package/dist/src/experiments/plugins/contextStrategyComparison/resultMapping.js +260 -0
- package/dist/src/experiments/plugins/index.js +1 -0
- package/dist/src/experiments/registry.js +43 -0
- package/dist/src/experiments/results.js +48 -0
- package/dist/src/experiments/runner.js +181 -0
- package/dist/src/experiments/target.js +8 -0
- package/dist/src/experiments/types.js +1 -0
- package/dist/src/gallery/index.js +2 -0
- package/dist/src/gallery/types.js +1 -0
- package/dist/src/gallery/writeGalleryManifest.js +214 -0
- package/dist/src/index.js +12 -0
- package/dist/src/plots/buildExperimentPlotData.js +137 -0
- package/dist/src/plots/index.js +4 -0
- package/dist/src/plots/renderSvgChart.js +82 -0
- package/dist/src/plots/types.js +1 -0
- package/dist/src/plots/writePlotArtifacts.js +46 -0
- package/dist/src/prompts/buildPromptContext.js +68 -0
- package/dist/src/prompts/generateMyDevKitPrompt.js +106 -0
- package/dist/src/prompts/generatePromptVariants.js +36 -0
- package/dist/src/prompts/generateRawFullFilePrompt.js +97 -0
- package/dist/src/prompts/index.js +7 -0
- package/dist/src/prompts/measurePromptComplexity.js +41 -0
- package/dist/src/prompts/types.js +1 -0
- package/dist/src/prompts/writePromptArtifacts.js +43 -0
- package/dist/src/report/buildExperimentReportInput.js +339 -0
- package/dist/src/report/experimentReportTypes.js +1 -0
- package/dist/src/report/experiments/buildPluginExperimentReport.js +153 -0
- package/dist/src/report/experiments/experimentReportModel.js +1 -0
- package/dist/src/report/experiments/index.js +4 -0
- package/dist/src/report/experiments/renderPluginExperimentReportHtml.js +133 -0
- package/dist/src/report/experiments/writePluginExperimentReports.js +30 -0
- package/dist/src/report/index.js +8 -0
- package/dist/src/report/renderExperimentHtmlReport.js +354 -0
- package/dist/src/report/renderHtmlReport.js +103 -0
- package/dist/src/report/types.js +10 -0
- package/dist/src/report/writeExperimentReportArtifacts.js +38 -0
- package/dist/src/report/writeReportArtifacts.js +39 -0
- package/dist/src/screenshot/captureReportScreenshot.js +75 -0
- package/dist/src/screenshot/index.js +2 -0
- package/dist/src/screenshot/types.js +1 -0
- package/dist/src/securityValidation/artifacts.js +15 -0
- package/dist/src/securityValidation/cliAdversarial/adversarialCliConfig.js +38 -0
- package/dist/src/securityValidation/cliAdversarial/dataVolumeChecks.js +194 -0
- package/dist/src/securityValidation/cliAdversarial/jsonStdoutChecks.js +359 -0
- package/dist/src/securityValidation/cliAdversarial/malformedArtifactChecks.js +284 -0
- package/dist/src/securityValidation/cliAdversarial/malformedArtifactFixtures.js +79 -0
- package/dist/src/securityValidation/cliAdversarial/pathBoundaryChecks.js +431 -0
- package/dist/src/securityValidation/cliAdversarial/pathCases.js +144 -0
- package/dist/src/securityValidation/cliAdversarial/readOnlyBoundaryChecks.js +294 -0
- package/dist/src/securityValidation/cliAdversarial/runAdversarialCheck.js +149 -0
- package/dist/src/securityValidation/cliAdversarial/subprocessSafetyChecks.js +214 -0
- package/dist/src/securityValidation/cliAdversarial/tempWorkspace.js +160 -0
- package/dist/src/securityValidation/commandRunner.js +136 -0
- package/dist/src/securityValidation/config.js +39 -0
- package/dist/src/securityValidation/dependencies/parseNpmAudit.js +115 -0
- package/dist/src/securityValidation/dependencies/parseNpmLs.js +71 -0
- package/dist/src/securityValidation/dependencies/parseNpmOutdated.js +41 -0
- package/dist/src/securityValidation/dependencies/runDependencyChecks.js +239 -0
- package/dist/src/securityValidation/dependencies/runOsvScanner.js +43 -0
- package/dist/src/securityValidation/fuzz/fuzzHarness.js +61 -0
- package/dist/src/securityValidation/fuzz/fuzzTargets.js +204 -0
- package/dist/src/securityValidation/fuzz/randomInput.js +0 -0
- package/dist/src/securityValidation/index.js +34 -0
- package/dist/src/securityValidation/packageChecks/forbiddenPackageContents.js +67 -0
- package/dist/src/securityValidation/packageChecks/parseNpmPackDryRun.js +56 -0
- package/dist/src/securityValidation/packageChecks/runPackageChecks.js +88 -0
- package/dist/src/securityValidation/report/renderSecurityReport.js +248 -0
- package/dist/src/securityValidation/report/securityReportTypes.js +1 -0
- package/dist/src/securityValidation/staticScans/codeql.js +66 -0
- package/dist/src/securityValidation/staticScans/semgrep.js +180 -0
- package/dist/src/securityValidation/testMatrix.js +535 -0
- package/dist/src/securityValidation/types.js +34 -0
- package/dist/src/securityValidation/validate/resolveTarget.js +32 -0
- package/dist/src/securityValidation/validate/runSecurityValidation.js +169 -0
- package/dist/src/securityValidation/validate/verdict.js +73 -0
- package/dist/src/visualizationDemos/buildMyDevKitVisualizationCommands.js +59 -0
- package/dist/src/visualizationDemos/index.js +4 -0
- package/dist/src/visualizationDemos/runVisualizationDemos.js +82 -0
- package/dist/src/visualizationDemos/types.js +1 -0
- package/dist/src/visualizationDemos/writeVisualizationDemoArtifacts.js +25 -0
- package/docs/METRICS.md +286 -0
- package/examples/demo-report-input.json +78 -0
- package/examples/lab-demo-cases.json +35 -0
- package/examples/real-agent-campaign-cases.json +118 -0
- package/examples/token-savings-cases.json +122 -0
- package/package.json +91 -0
- package/tests/fixtures/fake-adversarial-cli.js +152 -0
- package/tests/fixtures/fake-my-dev-kit-cli.js +83 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import { createTask } from "../src/services/createTask.js";
|
|
3
|
+
import { TaskWorkflowStore } from "../src/store/taskStore.js";
|
|
4
|
+
|
|
5
|
+
describe("createTask", () => {
|
|
6
|
+
it("creates deterministic task ids inside known projects", () => {
|
|
7
|
+
const store = new TaskWorkflowStore({
|
|
8
|
+
projects: [{ id: "alpha", name: "Alpha", slug: "alpha", archived: false }]
|
|
9
|
+
});
|
|
10
|
+
expect(createTask(store, { title: "Ship report", projectId: "alpha" }).id).toBe("task-1");
|
|
11
|
+
expect(createTask(store, { title: "Review notes", projectId: "alpha" }).id).toBe("task-2");
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
it("rejects blank titles and unknown projects", () => {
|
|
15
|
+
const store = new TaskWorkflowStore({
|
|
16
|
+
projects: [{ id: "alpha", name: "Alpha", slug: "alpha", archived: false }]
|
|
17
|
+
});
|
|
18
|
+
expect(() => createTask(store, { title: " ", projectId: "alpha" })).toThrow("Task title must not be empty.");
|
|
19
|
+
expect(() => createTask(store, { title: "Ready", projectId: "missing" })).toThrow("Unknown project id: missing");
|
|
20
|
+
});
|
|
21
|
+
});
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import { completeTask } from "../src/services/completeTask.js";
|
|
3
|
+
import { createTask } from "../src/services/createTask.js";
|
|
4
|
+
import { filterTasks } from "../src/services/filterTasks.js";
|
|
5
|
+
import { TaskWorkflowStore } from "../src/store/taskStore.js";
|
|
6
|
+
|
|
7
|
+
describe("filterTasks", () => {
|
|
8
|
+
it("filters by query, tag, priority, and completion state", () => {
|
|
9
|
+
const store = new TaskWorkflowStore({
|
|
10
|
+
projects: [{ id: "alpha", name: "Alpha", slug: "alpha", archived: false }]
|
|
11
|
+
});
|
|
12
|
+
const first = createTask(store, { title: "Ship release notes", projectId: "alpha", priority: "high", tags: ["Docs"] });
|
|
13
|
+
createTask(store, { title: "Review pipeline", projectId: "alpha", priority: "medium", tags: ["ops"] });
|
|
14
|
+
completeTask(store, first.id);
|
|
15
|
+
expect(filterTasks(store, { query: "pipeline" }).map((task) => task.title)).toEqual(["Review pipeline"]);
|
|
16
|
+
expect(filterTasks(store, { tag: "docs", completed: true }).map((task) => task.id)).toEqual([first.id]);
|
|
17
|
+
});
|
|
18
|
+
});
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import { importTasks } from "../src/services/importTasks.js";
|
|
3
|
+
import { TaskWorkflowStore } from "../src/store/taskStore.js";
|
|
4
|
+
|
|
5
|
+
describe("importTasks", () => {
|
|
6
|
+
it("imports tasks with deterministic ids and skips duplicate normalized titles inside the same project", () => {
|
|
7
|
+
const store = new TaskWorkflowStore({
|
|
8
|
+
projects: [{ id: "alpha", name: "Alpha", slug: "alpha", archived: false }]
|
|
9
|
+
});
|
|
10
|
+
const result = importTasks(
|
|
11
|
+
store,
|
|
12
|
+
[
|
|
13
|
+
{ title: "Ship report", projectId: "alpha", tags: ["Docs"] },
|
|
14
|
+
{ title: " Ship report ", projectId: "alpha", tags: ["docs"] },
|
|
15
|
+
{ title: "Audit metrics", projectId: "alpha", priority: "high" }
|
|
16
|
+
],
|
|
17
|
+
"csv"
|
|
18
|
+
);
|
|
19
|
+
expect(result.imported.map((task) => task.id)).toEqual(["task-1", "task-2"]);
|
|
20
|
+
expect(result.skippedDuplicates).toEqual(["task-1"]);
|
|
21
|
+
});
|
|
22
|
+
});
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import { completeTask } from "../src/services/completeTask.js";
|
|
3
|
+
import { createTask } from "../src/services/createTask.js";
|
|
4
|
+
import { summarizeTasks } from "../src/services/summarizeTasks.js";
|
|
5
|
+
import { TaskWorkflowStore } from "../src/store/taskStore.js";
|
|
6
|
+
|
|
7
|
+
describe("summarizeTasks", () => {
|
|
8
|
+
it("reports totals, per-project counts, and high-priority open titles", () => {
|
|
9
|
+
const store = new TaskWorkflowStore({
|
|
10
|
+
projects: [
|
|
11
|
+
{ id: "alpha", name: "Alpha", slug: "alpha", archived: false },
|
|
12
|
+
{ id: "beta", name: "Beta", slug: "beta", archived: false }
|
|
13
|
+
]
|
|
14
|
+
});
|
|
15
|
+
const first = createTask(store, { title: "Ship report", projectId: "alpha", priority: "high" });
|
|
16
|
+
createTask(store, { title: "Audit logs", projectId: "beta", priority: "medium" });
|
|
17
|
+
completeTask(store, first.id);
|
|
18
|
+
expect(summarizeTasks(store)).toEqual({
|
|
19
|
+
total: 2,
|
|
20
|
+
completed: 1,
|
|
21
|
+
open: 1,
|
|
22
|
+
byProject: {
|
|
23
|
+
alpha: { total: 1, completed: 1, open: 0 },
|
|
24
|
+
beta: { total: 1, completed: 0, open: 1 }
|
|
25
|
+
},
|
|
26
|
+
highPriorityOpenTitles: []
|
|
27
|
+
});
|
|
28
|
+
});
|
|
29
|
+
});
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2022",
|
|
4
|
+
"module": "NodeNext",
|
|
5
|
+
"moduleResolution": "NodeNext",
|
|
6
|
+
"strict": true,
|
|
7
|
+
"esModuleInterop": true,
|
|
8
|
+
"skipLibCheck": true,
|
|
9
|
+
"types": ["vitest/globals"]
|
|
10
|
+
},
|
|
11
|
+
"include": ["src/**/*.ts", "tests/**/*.ts"]
|
|
12
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { TaskStore } from "./taskStore.js";
|
|
2
|
+
|
|
3
|
+
export class TaskService {
|
|
4
|
+
constructor(store = new TaskStore()) {
|
|
5
|
+
this.store = store;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
createTask(title) {
|
|
9
|
+
const normalized = title.trim();
|
|
10
|
+
if (!normalized) {
|
|
11
|
+
throw new Error("Task title must not be empty.");
|
|
12
|
+
}
|
|
13
|
+
return this.store.create(normalized);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
completeTask(id) {
|
|
17
|
+
return this.store.update(id, (task) => ({ ...task, completed: true }));
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
listTasks() {
|
|
21
|
+
return this.store.list();
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
listOpenTasks() {
|
|
25
|
+
return this.store.list().filter((task) => !task.completed);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
summarizeTasks() {
|
|
29
|
+
const tasks = this.store.list();
|
|
30
|
+
const completed = tasks.filter((task) => task.completed).length;
|
|
31
|
+
return {
|
|
32
|
+
total: tasks.length,
|
|
33
|
+
open: tasks.length - completed,
|
|
34
|
+
completed
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
export class TaskStore {
|
|
2
|
+
#tasks = [];
|
|
3
|
+
#nextId = 1;
|
|
4
|
+
|
|
5
|
+
create(title) {
|
|
6
|
+
const task = {
|
|
7
|
+
id: `task-${this.#nextId++}`,
|
|
8
|
+
title,
|
|
9
|
+
completed: false
|
|
10
|
+
};
|
|
11
|
+
this.#tasks.push(task);
|
|
12
|
+
return { ...task };
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
update(taskId, updater) {
|
|
16
|
+
const index = this.#tasks.findIndex((task) => task.id === taskId);
|
|
17
|
+
if (index === -1) {
|
|
18
|
+
throw new Error(`Task not found: ${taskId}`);
|
|
19
|
+
}
|
|
20
|
+
const updated = updater(this.#tasks[index]);
|
|
21
|
+
this.#tasks[index] = updated;
|
|
22
|
+
return { ...updated };
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
list() {
|
|
26
|
+
return this.#tasks.map((task) => ({ ...task }));
|
|
27
|
+
}
|
|
28
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import { TaskService } from "../src/taskService.js";
|
|
3
|
+
|
|
4
|
+
describe("todo-js TaskService", () => {
|
|
5
|
+
it("creates tasks with deterministic ids", () => {
|
|
6
|
+
const service = new TaskService();
|
|
7
|
+
expect(service.createTask("First").id).toBe("task-1");
|
|
8
|
+
expect(service.createTask("Second").id).toBe("task-2");
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
it("completes tasks", () => {
|
|
12
|
+
const service = new TaskService();
|
|
13
|
+
const created = service.createTask("Ship benchmark");
|
|
14
|
+
const completed = service.completeTask(created.id);
|
|
15
|
+
expect(completed.completed).toBe(true);
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
it("lists all tasks", () => {
|
|
19
|
+
const service = new TaskService();
|
|
20
|
+
service.createTask("One");
|
|
21
|
+
service.createTask("Two");
|
|
22
|
+
expect(service.listTasks()).toHaveLength(2);
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
it("lists open tasks", () => {
|
|
26
|
+
const service = new TaskService();
|
|
27
|
+
const one = service.createTask("One");
|
|
28
|
+
service.createTask("Two");
|
|
29
|
+
service.completeTask(one.id);
|
|
30
|
+
expect(service.listOpenTasks().map((task) => task.title)).toEqual(["Two"]);
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
it("summarizes task counts", () => {
|
|
34
|
+
const service = new TaskService();
|
|
35
|
+
const one = service.createTask("One");
|
|
36
|
+
service.createTask("Two");
|
|
37
|
+
service.completeTask(one.id);
|
|
38
|
+
expect(service.summarizeTasks()).toEqual({ total: 2, open: 1, completed: 1 });
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
it("rejects empty task titles", () => {
|
|
42
|
+
const service = new TaskService();
|
|
43
|
+
expect(() => service.createTask(" ")).toThrowError("Task title must not be empty.");
|
|
44
|
+
});
|
|
45
|
+
});
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import json
|
|
2
|
+
import sys
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class TaskService:
|
|
6
|
+
def __init__(self) -> None:
|
|
7
|
+
self._tasks = []
|
|
8
|
+
self._next_id = 1
|
|
9
|
+
|
|
10
|
+
def create_task(self, title: str) -> dict:
|
|
11
|
+
normalized = title.strip()
|
|
12
|
+
if not normalized:
|
|
13
|
+
raise ValueError("Task title must not be empty.")
|
|
14
|
+
task = {
|
|
15
|
+
"id": f"task-{self._next_id}",
|
|
16
|
+
"title": normalized,
|
|
17
|
+
"completed": False,
|
|
18
|
+
}
|
|
19
|
+
self._next_id += 1
|
|
20
|
+
self._tasks.append(task)
|
|
21
|
+
return dict(task)
|
|
22
|
+
|
|
23
|
+
def complete_task(self, task_id: str) -> dict:
|
|
24
|
+
for task in self._tasks:
|
|
25
|
+
if task["id"] == task_id:
|
|
26
|
+
task["completed"] = True
|
|
27
|
+
return dict(task)
|
|
28
|
+
raise ValueError(f"Task not found: {task_id}")
|
|
29
|
+
|
|
30
|
+
def list_tasks(self) -> list:
|
|
31
|
+
return [dict(task) for task in self._tasks]
|
|
32
|
+
|
|
33
|
+
def list_open_tasks(self) -> list:
|
|
34
|
+
return [dict(task) for task in self._tasks if not task["completed"]]
|
|
35
|
+
|
|
36
|
+
def summarize_tasks(self) -> dict:
|
|
37
|
+
completed = len([task for task in self._tasks if task["completed"]])
|
|
38
|
+
return {
|
|
39
|
+
"total": len(self._tasks),
|
|
40
|
+
"open": len(self._tasks) - completed,
|
|
41
|
+
"completed": completed,
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def run_operations(payload: dict) -> dict:
|
|
46
|
+
service = TaskService()
|
|
47
|
+
results = []
|
|
48
|
+
for operation in payload["operations"]:
|
|
49
|
+
op_type = operation["type"]
|
|
50
|
+
if op_type == "createTask":
|
|
51
|
+
results.append(service.create_task(operation["title"]))
|
|
52
|
+
elif op_type == "completeTask":
|
|
53
|
+
results.append(service.complete_task(operation["id"]))
|
|
54
|
+
elif op_type == "listTasks":
|
|
55
|
+
results.append(service.list_tasks())
|
|
56
|
+
elif op_type == "listOpenTasks":
|
|
57
|
+
results.append(service.list_open_tasks())
|
|
58
|
+
elif op_type == "summarizeTasks":
|
|
59
|
+
results.append(service.summarize_tasks())
|
|
60
|
+
else:
|
|
61
|
+
raise ValueError(f"Unsupported operation: {op_type}")
|
|
62
|
+
return {"results": results}
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def main() -> int:
|
|
66
|
+
try:
|
|
67
|
+
payload = json.loads(sys.stdin.read())
|
|
68
|
+
sys.stdout.write(json.dumps(run_operations(payload)))
|
|
69
|
+
return 0
|
|
70
|
+
except Exception as error:
|
|
71
|
+
sys.stderr.write(str(error))
|
|
72
|
+
return 1
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
if __name__ == "__main__":
|
|
76
|
+
raise SystemExit(main())
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { spawnSync } from "node:child_process";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { fileURLToPath } from "node:url";
|
|
4
|
+
import { resolvePythonCommand } from "../../../../src/core/pythonCommand.js";
|
|
5
|
+
|
|
6
|
+
export type Operation =
|
|
7
|
+
| { type: "createTask"; title: string }
|
|
8
|
+
| { type: "completeTask"; id: string }
|
|
9
|
+
| { type: "listTasks" }
|
|
10
|
+
| { type: "listOpenTasks" }
|
|
11
|
+
| { type: "summarizeTasks" };
|
|
12
|
+
|
|
13
|
+
export function runTodoScenario(operations: Operation[]) {
|
|
14
|
+
const currentDir = path.dirname(fileURLToPath(import.meta.url));
|
|
15
|
+
const scriptPath = path.resolve(currentDir, "../python/task_service.py");
|
|
16
|
+
const python = resolvePythonCommand();
|
|
17
|
+
const result = spawnSync(python.command, [...python.argsPrefix, scriptPath], {
|
|
18
|
+
input: JSON.stringify({ operations }),
|
|
19
|
+
encoding: "utf8",
|
|
20
|
+
shell: false,
|
|
21
|
+
timeout: 10_000,
|
|
22
|
+
windowsHide: true
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
if (result.status !== 0) {
|
|
26
|
+
const details = [
|
|
27
|
+
`command=${python.command}`,
|
|
28
|
+
`args=${JSON.stringify([...python.argsPrefix, scriptPath])}`,
|
|
29
|
+
`status=${String(result.status)}`,
|
|
30
|
+
`signal=${String(result.signal)}`,
|
|
31
|
+
`error=${result.error?.message ?? ""}`,
|
|
32
|
+
`stderr=${result.stderr.trim()}`
|
|
33
|
+
].join("\n");
|
|
34
|
+
throw new Error(details || "Mixed benchmark Python process failed.");
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
return JSON.parse(result.stdout) as { results: unknown[] };
|
|
38
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import { runTodoScenario } from "../src/taskCli.js";
|
|
3
|
+
|
|
4
|
+
describe("todo-mixed-ts-py boundary", () => {
|
|
5
|
+
it("runs deterministic todo behavior through the TypeScript to Python boundary", () => {
|
|
6
|
+
const result = runTodoScenario([
|
|
7
|
+
{ type: "createTask", title: "One" },
|
|
8
|
+
{ type: "createTask", title: "Two" },
|
|
9
|
+
{ type: "completeTask", id: "task-1" },
|
|
10
|
+
{ type: "listOpenTasks" },
|
|
11
|
+
{ type: "summarizeTasks" }
|
|
12
|
+
]);
|
|
13
|
+
|
|
14
|
+
expect(result.results.at(0)).toEqual({ id: "task-1", title: "One", completed: false });
|
|
15
|
+
expect(result.results.at(3)).toEqual([{ id: "task-2", title: "Two", completed: false }]);
|
|
16
|
+
expect(result.results.at(4)).toEqual({ total: 2, open: 1, completed: 1 });
|
|
17
|
+
});
|
|
18
|
+
});
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2022",
|
|
4
|
+
"module": "NodeNext",
|
|
5
|
+
"moduleResolution": "NodeNext",
|
|
6
|
+
"strict": true,
|
|
7
|
+
"esModuleInterop": true,
|
|
8
|
+
"skipLibCheck": true,
|
|
9
|
+
"types": ["node", "vitest/globals"]
|
|
10
|
+
},
|
|
11
|
+
"include": ["src/**/*.ts", "tests/**/*.ts"]
|
|
12
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
from typing import Optional
|
|
2
|
+
|
|
3
|
+
from task_store import TaskStore
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class TaskService:
|
|
7
|
+
def __init__(self, store: Optional[TaskStore] = None) -> None:
|
|
8
|
+
self._store = store or TaskStore()
|
|
9
|
+
|
|
10
|
+
def create_task(self, title: str) -> dict:
|
|
11
|
+
normalized = title.strip()
|
|
12
|
+
if not normalized:
|
|
13
|
+
raise ValueError("Task title must not be empty.")
|
|
14
|
+
return self._store.create(normalized)
|
|
15
|
+
|
|
16
|
+
def complete_task(self, task_id: str) -> dict:
|
|
17
|
+
return self._store.update(task_id, lambda task: {**task, "completed": True})
|
|
18
|
+
|
|
19
|
+
def list_tasks(self) -> list:
|
|
20
|
+
return self._store.list()
|
|
21
|
+
|
|
22
|
+
def list_open_tasks(self) -> list:
|
|
23
|
+
return [task for task in self._store.list() if not task["completed"]]
|
|
24
|
+
|
|
25
|
+
def summarize_tasks(self) -> dict:
|
|
26
|
+
tasks = self._store.list()
|
|
27
|
+
completed = len([task for task in tasks if task["completed"]])
|
|
28
|
+
return {
|
|
29
|
+
"total": len(tasks),
|
|
30
|
+
"open": len(tasks) - completed,
|
|
31
|
+
"completed": completed,
|
|
32
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
from copy import deepcopy
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class TaskStore:
|
|
5
|
+
def __init__(self) -> None:
|
|
6
|
+
self._tasks = []
|
|
7
|
+
self._next_id = 1
|
|
8
|
+
|
|
9
|
+
def create(self, title: str) -> dict:
|
|
10
|
+
task = {
|
|
11
|
+
"id": f"task-{self._next_id}",
|
|
12
|
+
"title": title,
|
|
13
|
+
"completed": False,
|
|
14
|
+
}
|
|
15
|
+
self._next_id += 1
|
|
16
|
+
self._tasks.append(task)
|
|
17
|
+
return deepcopy(task)
|
|
18
|
+
|
|
19
|
+
def update(self, task_id: str, updater) -> dict:
|
|
20
|
+
for index, task in enumerate(self._tasks):
|
|
21
|
+
if task["id"] == task_id:
|
|
22
|
+
updated = updater(deepcopy(task))
|
|
23
|
+
self._tasks[index] = deepcopy(updated)
|
|
24
|
+
return deepcopy(updated)
|
|
25
|
+
raise ValueError(f"Task not found: {task_id}")
|
|
26
|
+
|
|
27
|
+
def list(self) -> list:
|
|
28
|
+
return deepcopy(self._tasks)
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import sys
|
|
3
|
+
import unittest
|
|
4
|
+
|
|
5
|
+
sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..", "src"))
|
|
6
|
+
|
|
7
|
+
from task_service import TaskService
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class TaskServiceTests(unittest.TestCase):
|
|
11
|
+
def test_creates_tasks_with_deterministic_ids(self) -> None:
|
|
12
|
+
service = TaskService()
|
|
13
|
+
self.assertEqual(service.create_task("First")["id"], "task-1")
|
|
14
|
+
self.assertEqual(service.create_task("Second")["id"], "task-2")
|
|
15
|
+
|
|
16
|
+
def test_completes_tasks(self) -> None:
|
|
17
|
+
service = TaskService()
|
|
18
|
+
created = service.create_task("Ship benchmark")
|
|
19
|
+
completed = service.complete_task(created["id"])
|
|
20
|
+
self.assertTrue(completed["completed"])
|
|
21
|
+
|
|
22
|
+
def test_lists_all_tasks(self) -> None:
|
|
23
|
+
service = TaskService()
|
|
24
|
+
service.create_task("One")
|
|
25
|
+
service.create_task("Two")
|
|
26
|
+
self.assertEqual(len(service.list_tasks()), 2)
|
|
27
|
+
|
|
28
|
+
def test_lists_open_tasks(self) -> None:
|
|
29
|
+
service = TaskService()
|
|
30
|
+
first = service.create_task("One")
|
|
31
|
+
service.create_task("Two")
|
|
32
|
+
service.complete_task(first["id"])
|
|
33
|
+
self.assertEqual([task["title"] for task in service.list_open_tasks()], ["Two"])
|
|
34
|
+
|
|
35
|
+
def test_summarizes_tasks(self) -> None:
|
|
36
|
+
service = TaskService()
|
|
37
|
+
first = service.create_task("One")
|
|
38
|
+
service.create_task("Two")
|
|
39
|
+
service.complete_task(first["id"])
|
|
40
|
+
self.assertEqual(
|
|
41
|
+
service.summarize_tasks(),
|
|
42
|
+
{"total": 2, "open": 1, "completed": 1},
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
def test_rejects_empty_task_titles(self) -> None:
|
|
46
|
+
service = TaskService()
|
|
47
|
+
with self.assertRaisesRegex(ValueError, "Task title must not be empty."):
|
|
48
|
+
service.create_task(" ")
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
if __name__ == "__main__":
|
|
52
|
+
unittest.main()
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { TaskStore, type Task } from "./taskStore.js";
|
|
2
|
+
|
|
3
|
+
export type TaskSummary = {
|
|
4
|
+
total: number;
|
|
5
|
+
open: number;
|
|
6
|
+
completed: number;
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
export class TaskService {
|
|
10
|
+
constructor(private readonly store = new TaskStore()) {}
|
|
11
|
+
|
|
12
|
+
createTask(title: string): Task {
|
|
13
|
+
const normalized = title.trim();
|
|
14
|
+
if (!normalized) {
|
|
15
|
+
throw new Error("Task title must not be empty.");
|
|
16
|
+
}
|
|
17
|
+
return this.store.create(normalized);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
completeTask(id: string): Task {
|
|
21
|
+
return this.store.update(id, (task) => ({ ...task, completed: true }));
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
listTasks(): Task[] {
|
|
25
|
+
return this.store.list();
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
listOpenTasks(): Task[] {
|
|
29
|
+
return this.store.list().filter((task) => !task.completed);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
summarizeTasks(): TaskSummary {
|
|
33
|
+
const tasks = this.store.list();
|
|
34
|
+
const completed = tasks.filter((task) => task.completed).length;
|
|
35
|
+
return {
|
|
36
|
+
total: tasks.length,
|
|
37
|
+
open: tasks.length - completed,
|
|
38
|
+
completed
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
export type Task = {
|
|
2
|
+
id: string;
|
|
3
|
+
title: string;
|
|
4
|
+
completed: boolean;
|
|
5
|
+
};
|
|
6
|
+
|
|
7
|
+
export class TaskStore {
|
|
8
|
+
private tasks: Task[] = [];
|
|
9
|
+
private nextId = 1;
|
|
10
|
+
|
|
11
|
+
create(title: string): Task {
|
|
12
|
+
const task: Task = {
|
|
13
|
+
id: `task-${this.nextId++}`,
|
|
14
|
+
title,
|
|
15
|
+
completed: false
|
|
16
|
+
};
|
|
17
|
+
this.tasks.push(task);
|
|
18
|
+
return { ...task };
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
update(taskId: string, updater: (task: Task) => Task): Task {
|
|
22
|
+
const index = this.tasks.findIndex((task) => task.id === taskId);
|
|
23
|
+
if (index === -1) {
|
|
24
|
+
throw new Error(`Task not found: ${taskId}`);
|
|
25
|
+
}
|
|
26
|
+
const updated = updater(this.tasks[index]);
|
|
27
|
+
this.tasks[index] = updated;
|
|
28
|
+
return { ...updated };
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
list(): Task[] {
|
|
32
|
+
return this.tasks.map((task) => ({ ...task }));
|
|
33
|
+
}
|
|
34
|
+
}
|