@hasna/todos 0.11.42 → 0.11.44
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 +17 -9
- package/dist/cli/commands/environment-snapshots.d.ts +3 -0
- package/dist/cli/commands/environment-snapshots.d.ts.map +1 -0
- package/dist/cli/commands/plan-template-commands.d.ts.map +1 -1
- package/dist/cli/index.js +1017 -137
- package/dist/cli-mcp-parity.d.ts +1 -1
- package/dist/cli-mcp-parity.d.ts.map +1 -1
- package/dist/contracts.js +44 -0
- package/dist/db/builtin-templates.d.ts +17 -0
- package/dist/db/builtin-templates.d.ts.map +1 -1
- package/dist/index.d.ts +3 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +602 -65
- package/dist/json-contracts.d.ts.map +1 -1
- package/dist/lib/environment-snapshots.d.ts +111 -0
- package/dist/lib/environment-snapshots.d.ts.map +1 -0
- package/dist/mcp/index.d.ts.map +1 -1
- package/dist/mcp/index.js +1274 -8
- package/dist/mcp/token-utils.d.ts.map +1 -1
- package/dist/mcp/tools/environment-snapshots.d.ts +8 -0
- package/dist/mcp/tools/environment-snapshots.d.ts.map +1 -0
- package/dist/mcp/tools/templates.d.ts.map +1 -1
- package/dist/mcp.js +5 -1
- package/dist/registry.js +94 -1
- package/dist/release-provenance.json +3 -3
- package/package.json +1 -1
package/dist/cli/index.js
CHANGED
|
@@ -8908,9 +8908,83 @@ var init_plans = __esm(() => {
|
|
|
8908
8908
|
// src/db/builtin-templates.ts
|
|
8909
8909
|
var exports_builtin_templates = {};
|
|
8910
8910
|
__export(exports_builtin_templates, {
|
|
8911
|
+
writeBuiltinTemplateFiles: () => writeBuiltinTemplateFiles,
|
|
8912
|
+
listBuiltinTemplates: () => listBuiltinTemplates,
|
|
8911
8913
|
initBuiltinTemplates: () => initBuiltinTemplates,
|
|
8914
|
+
getBuiltinTemplate: () => getBuiltinTemplate,
|
|
8915
|
+
exportBuiltinTemplateFiles: () => exportBuiltinTemplateFiles,
|
|
8916
|
+
exportBuiltinTemplate: () => exportBuiltinTemplate,
|
|
8917
|
+
BUILTIN_TEMPLATE_LIBRARY_VERSION: () => BUILTIN_TEMPLATE_LIBRARY_VERSION,
|
|
8918
|
+
BUILTIN_TEMPLATE_LIBRARY_SOURCE: () => BUILTIN_TEMPLATE_LIBRARY_SOURCE,
|
|
8912
8919
|
BUILTIN_TEMPLATES: () => BUILTIN_TEMPLATES
|
|
8913
8920
|
});
|
|
8921
|
+
import { mkdirSync as mkdirSync4, writeFileSync as writeFileSync2 } from "fs";
|
|
8922
|
+
import { join as join5 } from "path";
|
|
8923
|
+
function templateMetadata(template) {
|
|
8924
|
+
return {
|
|
8925
|
+
source: BUILTIN_TEMPLATE_LIBRARY_SOURCE,
|
|
8926
|
+
library_version: template.version,
|
|
8927
|
+
category: template.category,
|
|
8928
|
+
template_file: `${template.name}.json`,
|
|
8929
|
+
local_only: true,
|
|
8930
|
+
marketplace_free: true
|
|
8931
|
+
};
|
|
8932
|
+
}
|
|
8933
|
+
function listBuiltinTemplates() {
|
|
8934
|
+
return BUILTIN_TEMPLATES.map((template) => ({
|
|
8935
|
+
...template,
|
|
8936
|
+
variables: template.variables.map((variable) => ({ ...variable })),
|
|
8937
|
+
tasks: template.tasks.map((task) => ({ ...task, tags: task.tags ? [...task.tags] : undefined }))
|
|
8938
|
+
}));
|
|
8939
|
+
}
|
|
8940
|
+
function getBuiltinTemplate(name) {
|
|
8941
|
+
return listBuiltinTemplates().find((template) => template.name === name) ?? null;
|
|
8942
|
+
}
|
|
8943
|
+
function exportBuiltinTemplate(name) {
|
|
8944
|
+
const template = getBuiltinTemplate(name);
|
|
8945
|
+
if (!template)
|
|
8946
|
+
throw new Error(`Built-in template not found: ${name}`);
|
|
8947
|
+
return {
|
|
8948
|
+
name: template.name,
|
|
8949
|
+
title_pattern: `${template.name}: {${template.variables[0]?.name ?? "name"}}`,
|
|
8950
|
+
description: template.description,
|
|
8951
|
+
priority: "medium",
|
|
8952
|
+
tags: [template.category, "local-template"],
|
|
8953
|
+
variables: template.variables,
|
|
8954
|
+
project_id: null,
|
|
8955
|
+
plan_id: null,
|
|
8956
|
+
metadata: templateMetadata(template),
|
|
8957
|
+
tasks: template.tasks.map((task) => ({
|
|
8958
|
+
position: task.position,
|
|
8959
|
+
title_pattern: task.title_pattern,
|
|
8960
|
+
description: task.description ?? null,
|
|
8961
|
+
priority: task.priority ?? "medium",
|
|
8962
|
+
tags: task.tags ?? [template.category],
|
|
8963
|
+
task_type: task.task_type ?? null,
|
|
8964
|
+
condition: task.condition ?? null,
|
|
8965
|
+
include_template_id: task.include_template_id ?? null,
|
|
8966
|
+
depends_on_positions: task.depends_on_positions ?? task.depends_on ?? [],
|
|
8967
|
+
metadata: task.metadata ?? { category: template.category }
|
|
8968
|
+
}))
|
|
8969
|
+
};
|
|
8970
|
+
}
|
|
8971
|
+
function exportBuiltinTemplateFiles() {
|
|
8972
|
+
return BUILTIN_TEMPLATES.map((template) => ({
|
|
8973
|
+
filename: `${template.name}.json`,
|
|
8974
|
+
template: exportBuiltinTemplate(template.name)
|
|
8975
|
+
}));
|
|
8976
|
+
}
|
|
8977
|
+
function writeBuiltinTemplateFiles(directory) {
|
|
8978
|
+
mkdirSync4(directory, { recursive: true });
|
|
8979
|
+
const files = [];
|
|
8980
|
+
for (const entry of exportBuiltinTemplateFiles()) {
|
|
8981
|
+
const path = join5(directory, entry.filename);
|
|
8982
|
+
writeFileSync2(path, `${JSON.stringify(entry.template, null, 2)}
|
|
8983
|
+
`, "utf-8");
|
|
8984
|
+
files.push(path);
|
|
8985
|
+
}
|
|
8986
|
+
return { directory, written: files.length, files };
|
|
8987
|
+
}
|
|
8914
8988
|
function initBuiltinTemplates(db) {
|
|
8915
8989
|
const d = db || getDatabase();
|
|
8916
8990
|
const existing = listTemplates(d);
|
|
@@ -8936,7 +9010,9 @@ function initBuiltinTemplates(db) {
|
|
|
8936
9010
|
name: bt.name,
|
|
8937
9011
|
title_pattern: `${bt.name}: {${bt.variables[0]?.name || "name"}}`,
|
|
8938
9012
|
description: bt.description,
|
|
9013
|
+
tags: [bt.category, "local-template"],
|
|
8939
9014
|
variables: bt.variables,
|
|
9015
|
+
metadata: templateMetadata(bt),
|
|
8940
9016
|
tasks
|
|
8941
9017
|
}, d);
|
|
8942
9018
|
created++;
|
|
@@ -8944,14 +9020,141 @@ function initBuiltinTemplates(db) {
|
|
|
8944
9020
|
}
|
|
8945
9021
|
return { created, skipped, names };
|
|
8946
9022
|
}
|
|
8947
|
-
var BUILTIN_TEMPLATES;
|
|
9023
|
+
var BUILTIN_TEMPLATE_LIBRARY_VERSION = "2026-05-21", BUILTIN_TEMPLATE_LIBRARY_SOURCE = "bundled-local-template-library", BUILTIN_TEMPLATES;
|
|
8948
9024
|
var init_builtin_templates = __esm(() => {
|
|
8949
9025
|
init_database();
|
|
8950
9026
|
init_templates();
|
|
8951
9027
|
BUILTIN_TEMPLATES = [
|
|
9028
|
+
{
|
|
9029
|
+
name: "bug-fix",
|
|
9030
|
+
description: "Reproduce, diagnose, fix, test, and release a defect.",
|
|
9031
|
+
category: "bug-fix",
|
|
9032
|
+
version: BUILTIN_TEMPLATE_LIBRARY_VERSION,
|
|
9033
|
+
variables: [{ name: "bug", required: true, description: "Bug description" }],
|
|
9034
|
+
tasks: [
|
|
9035
|
+
{ position: 0, title_pattern: "Reproduce: {bug}", priority: "critical", tags: ["bug", "repro"] },
|
|
9036
|
+
{ position: 1, title_pattern: "Diagnose root cause of {bug}", priority: "critical", tags: ["bug", "diagnosis"], depends_on_positions: [0] },
|
|
9037
|
+
{ position: 2, title_pattern: "Write regression test for {bug}", priority: "high", tags: ["bug", "test"], depends_on_positions: [1] },
|
|
9038
|
+
{ position: 3, title_pattern: "Implement fix for {bug}", priority: "critical", tags: ["bug", "implementation"], depends_on_positions: [2] },
|
|
9039
|
+
{ position: 4, title_pattern: "Run full verification for {bug}", priority: "high", tags: ["bug", "verification"], depends_on_positions: [3] },
|
|
9040
|
+
{ position: 5, title_pattern: "Publish and smoke test fix for {bug}", priority: "high", tags: ["bug", "release"], depends_on_positions: [4] }
|
|
9041
|
+
]
|
|
9042
|
+
},
|
|
9043
|
+
{
|
|
9044
|
+
name: "feature-implementation",
|
|
9045
|
+
description: "Plan, build, test, document, and release a product feature.",
|
|
9046
|
+
category: "feature",
|
|
9047
|
+
version: BUILTIN_TEMPLATE_LIBRARY_VERSION,
|
|
9048
|
+
variables: [
|
|
9049
|
+
{ name: "feature", required: true, description: "Feature name" },
|
|
9050
|
+
{ name: "scope", required: false, default: "medium", description: "Implementation size or risk" }
|
|
9051
|
+
],
|
|
9052
|
+
tasks: [
|
|
9053
|
+
{ position: 0, title_pattern: "Define acceptance criteria for {feature}", priority: "high", tags: ["feature", "spec"] },
|
|
9054
|
+
{ position: 1, title_pattern: "Design {scope} implementation plan for {feature}", priority: "high", tags: ["feature", "design"], depends_on_positions: [0] },
|
|
9055
|
+
{ position: 2, title_pattern: "Implement {feature}", priority: "critical", tags: ["feature", "implementation"], depends_on_positions: [1] },
|
|
9056
|
+
{ position: 3, title_pattern: "Add tests for {feature}", priority: "high", tags: ["feature", "test"], depends_on_positions: [2] },
|
|
9057
|
+
{ position: 4, title_pattern: "Update docs for {feature}", priority: "medium", tags: ["feature", "docs"], depends_on_positions: [2] },
|
|
9058
|
+
{ position: 5, title_pattern: "Run release checks for {feature}", priority: "high", tags: ["feature", "verification"], depends_on_positions: [3, 4] }
|
|
9059
|
+
]
|
|
9060
|
+
},
|
|
9061
|
+
{
|
|
9062
|
+
name: "security-review",
|
|
9063
|
+
description: "Threat model, test, remediate, and report on security posture.",
|
|
9064
|
+
category: "security",
|
|
9065
|
+
version: BUILTIN_TEMPLATE_LIBRARY_VERSION,
|
|
9066
|
+
variables: [{ name: "target", required: true, description: "System, package, or change under review" }],
|
|
9067
|
+
tasks: [
|
|
9068
|
+
{ position: 0, title_pattern: "Map trust boundaries for {target}", priority: "critical", tags: ["security", "threat-model"] },
|
|
9069
|
+
{ position: 1, title_pattern: "Scan {target} for vulnerabilities and secret exposure", priority: "critical", tags: ["security", "scan"], depends_on_positions: [0] },
|
|
9070
|
+
{ position: 2, title_pattern: "Review authz, data access, and dependency risks in {target}", priority: "critical", tags: ["security", "review"], depends_on_positions: [0] },
|
|
9071
|
+
{ position: 3, title_pattern: "Fix critical security findings in {target}", priority: "critical", tags: ["security", "fix"], depends_on_positions: [1, 2] },
|
|
9072
|
+
{ position: 4, title_pattern: "Retest {target} security fixes", priority: "high", tags: ["security", "verification"], depends_on_positions: [3] },
|
|
9073
|
+
{ position: 5, title_pattern: "Write local security review report for {target}", priority: "medium", tags: ["security", "report"], depends_on_positions: [4] }
|
|
9074
|
+
]
|
|
9075
|
+
},
|
|
9076
|
+
{
|
|
9077
|
+
name: "release",
|
|
9078
|
+
description: "Prepare, verify, publish, install, and smoke test a package release.",
|
|
9079
|
+
category: "release",
|
|
9080
|
+
version: BUILTIN_TEMPLATE_LIBRARY_VERSION,
|
|
9081
|
+
variables: [
|
|
9082
|
+
{ name: "package", required: true, description: "Package name" },
|
|
9083
|
+
{ name: "version", required: false, default: "patch", description: "Release version or bump type" }
|
|
9084
|
+
],
|
|
9085
|
+
tasks: [
|
|
9086
|
+
{ position: 0, title_pattern: "Prepare {package} {version} release notes", priority: "medium", tags: ["release", "docs"] },
|
|
9087
|
+
{ position: 1, title_pattern: "Run full tests for {package}", priority: "critical", tags: ["release", "test"] },
|
|
9088
|
+
{ position: 2, title_pattern: "Run build and release verification for {package}", priority: "critical", tags: ["release", "verification"], depends_on_positions: [1] },
|
|
9089
|
+
{ position: 3, title_pattern: "Scan {package} release diff for secrets", priority: "critical", tags: ["release", "security"], depends_on_positions: [2] },
|
|
9090
|
+
{ position: 4, title_pattern: "Publish {package} {version}", priority: "high", tags: ["release", "publish"], depends_on_positions: [3] },
|
|
9091
|
+
{ position: 5, title_pattern: "Install and smoke test published {package}", priority: "high", tags: ["release", "smoke"], depends_on_positions: [4] }
|
|
9092
|
+
]
|
|
9093
|
+
},
|
|
9094
|
+
{
|
|
9095
|
+
name: "migration",
|
|
9096
|
+
description: "Plan, test, apply, and verify a schema or data migration.",
|
|
9097
|
+
category: "migration",
|
|
9098
|
+
version: BUILTIN_TEMPLATE_LIBRARY_VERSION,
|
|
9099
|
+
variables: [{ name: "migration", required: true, description: "Migration name or objective" }],
|
|
9100
|
+
tasks: [
|
|
9101
|
+
{ position: 0, title_pattern: "Design migration plan for {migration}", priority: "critical", tags: ["migration", "plan"] },
|
|
9102
|
+
{ position: 1, title_pattern: "Write rollback plan for {migration}", priority: "critical", tags: ["migration", "rollback"], depends_on_positions: [0] },
|
|
9103
|
+
{ position: 2, title_pattern: "Implement migration {migration}", priority: "critical", tags: ["migration", "implementation"], depends_on_positions: [0] },
|
|
9104
|
+
{ position: 3, title_pattern: "Test migration {migration} on fixture data", priority: "high", tags: ["migration", "test"], depends_on_positions: [2] },
|
|
9105
|
+
{ position: 4, title_pattern: "Run migration {migration} verification and drift checks", priority: "high", tags: ["migration", "verification"], depends_on_positions: [1, 3] },
|
|
9106
|
+
{ position: 5, title_pattern: "Document migration {migration} evidence", priority: "medium", tags: ["migration", "docs"], depends_on_positions: [4] }
|
|
9107
|
+
]
|
|
9108
|
+
},
|
|
9109
|
+
{
|
|
9110
|
+
name: "incident",
|
|
9111
|
+
description: "Triage, mitigate, repair, verify, and retrospect an incident.",
|
|
9112
|
+
category: "incident",
|
|
9113
|
+
version: BUILTIN_TEMPLATE_LIBRARY_VERSION,
|
|
9114
|
+
variables: [{ name: "incident", required: true, description: "Incident summary" }],
|
|
9115
|
+
tasks: [
|
|
9116
|
+
{ position: 0, title_pattern: "Triage incident: {incident}", priority: "critical", tags: ["incident", "triage"] },
|
|
9117
|
+
{ position: 1, title_pattern: "Mitigate customer impact for {incident}", priority: "critical", tags: ["incident", "mitigation"], depends_on_positions: [0] },
|
|
9118
|
+
{ position: 2, title_pattern: "Diagnose root cause for {incident}", priority: "critical", tags: ["incident", "diagnosis"], depends_on_positions: [0] },
|
|
9119
|
+
{ position: 3, title_pattern: "Implement durable repair for {incident}", priority: "critical", tags: ["incident", "repair"], depends_on_positions: [2] },
|
|
9120
|
+
{ position: 4, title_pattern: "Verify recovery from {incident}", priority: "high", tags: ["incident", "verification"], depends_on_positions: [1, 3] },
|
|
9121
|
+
{ position: 5, title_pattern: "Write retrospective for {incident}", priority: "medium", tags: ["incident", "retro"], depends_on_positions: [4] }
|
|
9122
|
+
]
|
|
9123
|
+
},
|
|
9124
|
+
{
|
|
9125
|
+
name: "docs-refresh",
|
|
9126
|
+
description: "Audit, update, validate, and publish documentation changes.",
|
|
9127
|
+
category: "docs",
|
|
9128
|
+
version: BUILTIN_TEMPLATE_LIBRARY_VERSION,
|
|
9129
|
+
variables: [{ name: "area", required: true, description: "Documentation area or product surface" }],
|
|
9130
|
+
tasks: [
|
|
9131
|
+
{ position: 0, title_pattern: "Audit current docs for {area}", priority: "medium", tags: ["docs", "audit"] },
|
|
9132
|
+
{ position: 1, title_pattern: "Update examples and commands for {area}", priority: "medium", tags: ["docs", "examples"], depends_on_positions: [0] },
|
|
9133
|
+
{ position: 2, title_pattern: "Validate docs snippets for {area}", priority: "high", tags: ["docs", "verification"], depends_on_positions: [1] },
|
|
9134
|
+
{ position: 3, title_pattern: "Refresh screenshots or generated artifacts for {area}", priority: "medium", tags: ["docs", "assets"], depends_on_positions: [1] },
|
|
9135
|
+
{ position: 4, title_pattern: "Publish docs refresh for {area}", priority: "medium", tags: ["docs", "release"], depends_on_positions: [2, 3] }
|
|
9136
|
+
]
|
|
9137
|
+
},
|
|
9138
|
+
{
|
|
9139
|
+
name: "qa",
|
|
9140
|
+
description: "Build a focused QA plan, execute checks, file defects, and sign off.",
|
|
9141
|
+
category: "qa",
|
|
9142
|
+
version: BUILTIN_TEMPLATE_LIBRARY_VERSION,
|
|
9143
|
+
variables: [{ name: "target", required: true, description: "Feature, release, or workflow under QA" }],
|
|
9144
|
+
tasks: [
|
|
9145
|
+
{ position: 0, title_pattern: "Create QA matrix for {target}", priority: "high", tags: ["qa", "plan"] },
|
|
9146
|
+
{ position: 1, title_pattern: "Run happy-path QA for {target}", priority: "high", tags: ["qa", "manual"], depends_on_positions: [0] },
|
|
9147
|
+
{ position: 2, title_pattern: "Run edge-case QA for {target}", priority: "high", tags: ["qa", "edge-cases"], depends_on_positions: [0] },
|
|
9148
|
+
{ position: 3, title_pattern: "File and dedupe QA defects for {target}", priority: "medium", tags: ["qa", "bugs"], depends_on_positions: [1, 2] },
|
|
9149
|
+
{ position: 4, title_pattern: "Verify QA fixes for {target}", priority: "high", tags: ["qa", "verification"], depends_on_positions: [3] },
|
|
9150
|
+
{ position: 5, title_pattern: "Record QA signoff for {target}", priority: "medium", tags: ["qa", "signoff"], depends_on_positions: [4] }
|
|
9151
|
+
]
|
|
9152
|
+
},
|
|
8952
9153
|
{
|
|
8953
9154
|
name: "open-source-project",
|
|
8954
9155
|
description: "Full open-source project bootstrap \u2014 scaffold to publish",
|
|
9156
|
+
category: "open-source",
|
|
9157
|
+
version: BUILTIN_TEMPLATE_LIBRARY_VERSION,
|
|
8955
9158
|
variables: [
|
|
8956
9159
|
{ name: "name", required: true, description: "Service name" },
|
|
8957
9160
|
{ name: "org", required: false, default: "hasna", description: "GitHub org" }
|
|
@@ -8971,45 +9174,6 @@ var init_builtin_templates = __esm(() => {
|
|
|
8971
9174
|
{ position: 11, title_pattern: "Publish @hasna/{name} to npm", priority: "high", depends_on_positions: [6, 7, 8] },
|
|
8972
9175
|
{ position: 12, title_pattern: "Install @hasna/{name} globally and verify", priority: "medium", depends_on_positions: [11] }
|
|
8973
9176
|
]
|
|
8974
|
-
},
|
|
8975
|
-
{
|
|
8976
|
-
name: "bug-fix",
|
|
8977
|
-
description: "Standard bug fix workflow",
|
|
8978
|
-
variables: [{ name: "bug", required: true, description: "Bug description" }],
|
|
8979
|
-
tasks: [
|
|
8980
|
-
{ position: 0, title_pattern: "Reproduce: {bug}", priority: "critical" },
|
|
8981
|
-
{ position: 1, title_pattern: "Diagnose root cause of {bug}", priority: "critical", depends_on_positions: [0] },
|
|
8982
|
-
{ position: 2, title_pattern: "Implement fix for {bug}", priority: "critical", depends_on_positions: [1] },
|
|
8983
|
-
{ position: 3, title_pattern: "Write regression test for {bug}", priority: "high", depends_on_positions: [2] },
|
|
8984
|
-
{ position: 4, title_pattern: "Publish fix and verify in production", priority: "high", depends_on_positions: [3] }
|
|
8985
|
-
]
|
|
8986
|
-
},
|
|
8987
|
-
{
|
|
8988
|
-
name: "feature",
|
|
8989
|
-
description: "Standard feature development workflow",
|
|
8990
|
-
variables: [{ name: "feature", required: true }, { name: "scope", required: false, default: "medium" }],
|
|
8991
|
-
tasks: [
|
|
8992
|
-
{ position: 0, title_pattern: "Write spec for {feature}", priority: "high" },
|
|
8993
|
-
{ position: 1, title_pattern: "Design implementation approach for {feature}", priority: "high", depends_on_positions: [0] },
|
|
8994
|
-
{ position: 2, title_pattern: "Implement {feature}", priority: "critical", depends_on_positions: [1] },
|
|
8995
|
-
{ position: 3, title_pattern: "Write tests for {feature}", priority: "high", depends_on_positions: [2] },
|
|
8996
|
-
{ position: 4, title_pattern: "Code review for {feature}", priority: "medium", depends_on_positions: [3] },
|
|
8997
|
-
{ position: 5, title_pattern: "Update docs for {feature}", priority: "medium", depends_on_positions: [2] },
|
|
8998
|
-
{ position: 6, title_pattern: "Deploy {feature}", priority: "high", depends_on_positions: [4] }
|
|
8999
|
-
]
|
|
9000
|
-
},
|
|
9001
|
-
{
|
|
9002
|
-
name: "security-audit",
|
|
9003
|
-
description: "Security audit workflow",
|
|
9004
|
-
variables: [{ name: "target", required: true }],
|
|
9005
|
-
tasks: [
|
|
9006
|
-
{ position: 0, title_pattern: "Scan {target} for vulnerabilities", priority: "critical" },
|
|
9007
|
-
{ position: 1, title_pattern: "Review {target} security findings", priority: "critical", depends_on_positions: [0] },
|
|
9008
|
-
{ position: 2, title_pattern: "Fix critical issues in {target}", priority: "critical", depends_on_positions: [1] },
|
|
9009
|
-
{ position: 3, title_pattern: "Retest {target} after fixes", priority: "high", depends_on_positions: [2] },
|
|
9010
|
-
{ position: 4, title_pattern: "Write security report for {target}", priority: "medium", depends_on_positions: [3] },
|
|
9011
|
-
{ position: 5, title_pattern: "Close audit for {target}", priority: "low", depends_on_positions: [4] }
|
|
9012
|
-
]
|
|
9013
9177
|
}
|
|
9014
9178
|
];
|
|
9015
9179
|
});
|
|
@@ -9277,7 +9441,7 @@ function registerPlanTemplateCommands(program2) {
|
|
|
9277
9441
|
console.log(` ${chalk3.dim(t.id.slice(0, 8))} ${chalk3.bold(t.name)} ${chalk3.cyan(`"${t.title_pattern}"`)} ${chalk3.yellow(t.priority)}${vars}`);
|
|
9278
9442
|
}
|
|
9279
9443
|
});
|
|
9280
|
-
program2.command("template-init").alias("templates-init").description("Initialize
|
|
9444
|
+
program2.command("template-init").alias("templates-init").description("Initialize the bundled local template library").action(async () => {
|
|
9281
9445
|
const globalOpts = program2.opts();
|
|
9282
9446
|
const { initBuiltinTemplates: initBuiltinTemplates2 } = await Promise.resolve().then(() => (init_builtin_templates(), exports_builtin_templates));
|
|
9283
9447
|
const result = initBuiltinTemplates2();
|
|
@@ -9291,6 +9455,52 @@ function registerPlanTemplateCommands(program2) {
|
|
|
9291
9455
|
console.log(chalk3.green(`Created ${result.created} template(s): ${result.names.join(", ")}. Skipped ${result.skipped} existing.`));
|
|
9292
9456
|
}
|
|
9293
9457
|
});
|
|
9458
|
+
program2.command("template-library").alias("templates-library").description("List, show, or write the bundled local template library as editable JSON files").option("--show <name>", "Show one bundled template as JSON").option("--write <dir>", "Write all bundled templates to editable JSON files").action(async (opts) => {
|
|
9459
|
+
const globalOpts = program2.opts();
|
|
9460
|
+
const {
|
|
9461
|
+
exportBuiltinTemplate: exportBuiltinTemplate2,
|
|
9462
|
+
listBuiltinTemplates: listBuiltinTemplates2,
|
|
9463
|
+
writeBuiltinTemplateFiles: writeBuiltinTemplateFiles2
|
|
9464
|
+
} = await Promise.resolve().then(() => (init_builtin_templates(), exports_builtin_templates));
|
|
9465
|
+
try {
|
|
9466
|
+
if (opts.show) {
|
|
9467
|
+
const template = exportBuiltinTemplate2(opts.show);
|
|
9468
|
+
output(template, true);
|
|
9469
|
+
return;
|
|
9470
|
+
}
|
|
9471
|
+
if (opts.write) {
|
|
9472
|
+
const result = writeBuiltinTemplateFiles2(opts.write);
|
|
9473
|
+
if (globalOpts.json) {
|
|
9474
|
+
output(result, true);
|
|
9475
|
+
return;
|
|
9476
|
+
}
|
|
9477
|
+
console.log(chalk3.green(`Wrote ${result.written} editable template file(s) to ${result.directory}`));
|
|
9478
|
+
for (const file of result.files)
|
|
9479
|
+
console.log(chalk3.dim(` ${file}`));
|
|
9480
|
+
return;
|
|
9481
|
+
}
|
|
9482
|
+
const templates = listBuiltinTemplates2().map((template) => ({
|
|
9483
|
+
name: template.name,
|
|
9484
|
+
description: template.description,
|
|
9485
|
+
category: template.category,
|
|
9486
|
+
version: template.version,
|
|
9487
|
+
variables: template.variables,
|
|
9488
|
+
task_count: template.tasks.length
|
|
9489
|
+
}));
|
|
9490
|
+
if (globalOpts.json) {
|
|
9491
|
+
output(templates, true);
|
|
9492
|
+
return;
|
|
9493
|
+
}
|
|
9494
|
+
console.log(chalk3.bold(`${templates.length} bundled local template(s):
|
|
9495
|
+
`));
|
|
9496
|
+
for (const template of templates) {
|
|
9497
|
+
console.log(` ${chalk3.bold(template.name)} ${chalk3.dim(`[${template.category}]`)} ${chalk3.yellow(`${template.task_count} tasks`)}`);
|
|
9498
|
+
console.log(chalk3.dim(` ${template.description}`));
|
|
9499
|
+
}
|
|
9500
|
+
} catch (e) {
|
|
9501
|
+
handleError(e);
|
|
9502
|
+
}
|
|
9503
|
+
});
|
|
9294
9504
|
program2.command("template-preview <id>").alias("templates-preview").description("Preview a template without creating tasks \u2014 shows resolved titles, deps, and priorities").option("--var <vars...>", "Variable substitution in key=value format (e.g. --var name=invoices)").action(async (id, opts) => {
|
|
9295
9505
|
const globalOpts = program2.opts();
|
|
9296
9506
|
const { previewTemplate: previewTemplate2 } = await Promise.resolve().then(() => (init_templates(), exports_templates));
|
|
@@ -10008,16 +10218,16 @@ var init_saved_search_views = __esm(() => {
|
|
|
10008
10218
|
});
|
|
10009
10219
|
|
|
10010
10220
|
// src/lib/claude-tasks.ts
|
|
10011
|
-
import { existsSync as existsSync5, readFileSync as readFileSync3, readdirSync as readdirSync2, writeFileSync as
|
|
10012
|
-
import { join as
|
|
10221
|
+
import { existsSync as existsSync5, readFileSync as readFileSync3, readdirSync as readdirSync2, writeFileSync as writeFileSync3 } from "fs";
|
|
10222
|
+
import { join as join6 } from "path";
|
|
10013
10223
|
function getTaskListDir(taskListId) {
|
|
10014
|
-
return
|
|
10224
|
+
return join6(HOME, ".claude", "tasks", taskListId);
|
|
10015
10225
|
}
|
|
10016
10226
|
function readClaudeTask(dir, filename) {
|
|
10017
|
-
return readJsonFile(
|
|
10227
|
+
return readJsonFile(join6(dir, filename));
|
|
10018
10228
|
}
|
|
10019
10229
|
function writeClaudeTask(dir, task) {
|
|
10020
|
-
writeJsonFile(
|
|
10230
|
+
writeJsonFile(join6(dir, `${task.id}.json`), task);
|
|
10021
10231
|
}
|
|
10022
10232
|
function toClaudeStatus(status) {
|
|
10023
10233
|
if (status === "pending" || status === "in_progress" || status === "completed") {
|
|
@@ -10029,14 +10239,14 @@ function toSqliteStatus(status) {
|
|
|
10029
10239
|
return status;
|
|
10030
10240
|
}
|
|
10031
10241
|
function readPrefixCounter(dir) {
|
|
10032
|
-
const path =
|
|
10242
|
+
const path = join6(dir, ".prefix-counter");
|
|
10033
10243
|
if (!existsSync5(path))
|
|
10034
10244
|
return 0;
|
|
10035
10245
|
const val = parseInt(readFileSync3(path, "utf-8").trim(), 10);
|
|
10036
10246
|
return isNaN(val) ? 0 : val;
|
|
10037
10247
|
}
|
|
10038
10248
|
function writePrefixCounter(dir, value) {
|
|
10039
|
-
|
|
10249
|
+
writeFileSync3(join6(dir, ".prefix-counter"), String(value));
|
|
10040
10250
|
}
|
|
10041
10251
|
function formatPrefixedSubject(title, prefix, counter) {
|
|
10042
10252
|
const padded = String(counter).padStart(5, "0");
|
|
@@ -10072,7 +10282,7 @@ function pushToClaudeTaskList(taskListId, projectId, options = {}) {
|
|
|
10072
10282
|
const existingByTodosId = new Map;
|
|
10073
10283
|
const files = listJsonFiles(dir);
|
|
10074
10284
|
for (const f of files) {
|
|
10075
|
-
const path =
|
|
10285
|
+
const path = join6(dir, f);
|
|
10076
10286
|
const ct = readClaudeTask(dir, f);
|
|
10077
10287
|
if (ct?.metadata?.["todos_id"]) {
|
|
10078
10288
|
existingByTodosId.set(ct.metadata["todos_id"], { task: ct, mtimeMs: getFileMtimeMs(path) });
|
|
@@ -10179,7 +10389,7 @@ function pullFromClaudeTaskList(taskListId, projectId, options = {}) {
|
|
|
10179
10389
|
}
|
|
10180
10390
|
for (const f of files) {
|
|
10181
10391
|
try {
|
|
10182
|
-
const filePath =
|
|
10392
|
+
const filePath = join6(dir, f);
|
|
10183
10393
|
const ct = readClaudeTask(dir, f);
|
|
10184
10394
|
if (!ct)
|
|
10185
10395
|
continue;
|
|
@@ -10253,26 +10463,26 @@ var init_claude_tasks = __esm(() => {
|
|
|
10253
10463
|
|
|
10254
10464
|
// src/lib/agent-tasks.ts
|
|
10255
10465
|
import { existsSync as existsSync6 } from "fs";
|
|
10256
|
-
import { join as
|
|
10466
|
+
import { join as join7 } from "path";
|
|
10257
10467
|
function getTodosGlobalDir2() {
|
|
10258
|
-
const newDir =
|
|
10259
|
-
const legacyDir =
|
|
10468
|
+
const newDir = join7(HOME, ".hasna", "todos");
|
|
10469
|
+
const legacyDir = join7(HOME, ".todos");
|
|
10260
10470
|
if (!existsSync6(newDir) && existsSync6(legacyDir))
|
|
10261
10471
|
return legacyDir;
|
|
10262
10472
|
return newDir;
|
|
10263
10473
|
}
|
|
10264
10474
|
function agentBaseDir(agent) {
|
|
10265
10475
|
const key = `TODOS_${agent.toUpperCase()}_TASKS_DIR`;
|
|
10266
|
-
return process.env[key] || getAgentTasksDir(agent) || process.env["TODOS_AGENT_TASKS_DIR"] ||
|
|
10476
|
+
return process.env[key] || getAgentTasksDir(agent) || process.env["TODOS_AGENT_TASKS_DIR"] || join7(getTodosGlobalDir2(), "agents");
|
|
10267
10477
|
}
|
|
10268
10478
|
function getTaskListDir2(agent, taskListId) {
|
|
10269
|
-
return
|
|
10479
|
+
return join7(agentBaseDir(agent), agent, taskListId);
|
|
10270
10480
|
}
|
|
10271
10481
|
function readAgentTask(dir, filename) {
|
|
10272
|
-
return readJsonFile(
|
|
10482
|
+
return readJsonFile(join7(dir, filename));
|
|
10273
10483
|
}
|
|
10274
10484
|
function writeAgentTask(dir, task) {
|
|
10275
|
-
writeJsonFile(
|
|
10485
|
+
writeJsonFile(join7(dir, `${task.id}.json`), task);
|
|
10276
10486
|
}
|
|
10277
10487
|
function taskToAgentTask(task, externalId, existingMeta) {
|
|
10278
10488
|
return {
|
|
@@ -10306,7 +10516,7 @@ function pushToAgentTaskList(agent, taskListId, projectId, options = {}) {
|
|
|
10306
10516
|
const existingByTodosId = new Map;
|
|
10307
10517
|
const files = listJsonFiles(dir);
|
|
10308
10518
|
for (const f of files) {
|
|
10309
|
-
const path =
|
|
10519
|
+
const path = join7(dir, f);
|
|
10310
10520
|
const at = readAgentTask(dir, f);
|
|
10311
10521
|
if (at?.metadata?.["todos_id"]) {
|
|
10312
10522
|
existingByTodosId.set(at.metadata["todos_id"], { task: at, mtimeMs: getFileMtimeMs(path) });
|
|
@@ -10399,7 +10609,7 @@ function pullFromAgentTaskList(agent, taskListId, projectId, options = {}) {
|
|
|
10399
10609
|
}
|
|
10400
10610
|
for (const f of files) {
|
|
10401
10611
|
try {
|
|
10402
|
-
const filePath =
|
|
10612
|
+
const filePath = join7(dir, f);
|
|
10403
10613
|
const at = readAgentTask(dir, f);
|
|
10404
10614
|
if (!at)
|
|
10405
10615
|
continue;
|
|
@@ -10810,7 +11020,7 @@ __export(exports_extract, {
|
|
|
10810
11020
|
EXTRACT_TAGS: () => EXTRACT_TAGS
|
|
10811
11021
|
});
|
|
10812
11022
|
import { readFileSync as readFileSync5, statSync as statSync3 } from "fs";
|
|
10813
|
-
import { relative as relative3, resolve as resolve7, join as
|
|
11023
|
+
import { relative as relative3, resolve as resolve7, join as join8 } from "path";
|
|
10814
11024
|
function tagToPriority(tag) {
|
|
10815
11025
|
switch (tag) {
|
|
10816
11026
|
case "BUG":
|
|
@@ -10882,7 +11092,7 @@ function extractTodos(options, db) {
|
|
|
10882
11092
|
const files = collectFiles(basePath, extensions);
|
|
10883
11093
|
const allComments = [];
|
|
10884
11094
|
for (const file of files) {
|
|
10885
|
-
const fullPath = statSync3(basePath).isFile() ? basePath :
|
|
11095
|
+
const fullPath = statSync3(basePath).isFile() ? basePath : join8(basePath, file);
|
|
10886
11096
|
try {
|
|
10887
11097
|
const source = readFileSync5(fullPath, "utf-8");
|
|
10888
11098
|
const relPath = statSync3(basePath).isFile() ? relative3(resolve7(basePath, ".."), fullPath) : file;
|
|
@@ -11009,8 +11219,8 @@ var init_extract = __esm(() => {
|
|
|
11009
11219
|
|
|
11010
11220
|
// src/lib/artifact-store.ts
|
|
11011
11221
|
import { createHash as createHash2 } from "crypto";
|
|
11012
|
-
import { existsSync as existsSync8, mkdirSync as
|
|
11013
|
-
import { dirname as dirname6, join as
|
|
11222
|
+
import { existsSync as existsSync8, mkdirSync as mkdirSync5, readFileSync as readFileSync6, statSync as statSync4, writeFileSync as writeFileSync4 } from "fs";
|
|
11223
|
+
import { dirname as dirname6, join as join9, resolve as resolve8 } from "path";
|
|
11014
11224
|
import { tmpdir } from "os";
|
|
11015
11225
|
function isInMemoryDb2(path) {
|
|
11016
11226
|
return path === ":memory:" || path.startsWith("file::memory:");
|
|
@@ -11022,15 +11232,15 @@ function artifactStoreRoot() {
|
|
|
11022
11232
|
return resolve8(process.env["TODOS_ARTIFACTS_DIR"]);
|
|
11023
11233
|
const dbPath = getDatabasePath();
|
|
11024
11234
|
if (isInMemoryDb2(dbPath))
|
|
11025
|
-
return
|
|
11026
|
-
return
|
|
11235
|
+
return join9(tmpdir(), "hasna-todos-artifacts");
|
|
11236
|
+
return join9(dirname6(resolve8(dbPath)), "artifacts");
|
|
11027
11237
|
}
|
|
11028
11238
|
function artifactStorePath(relativePath) {
|
|
11029
11239
|
const normalized = relativePath.replace(/\\/g, "/");
|
|
11030
11240
|
if (normalized.includes("..") || normalized.startsWith("/") || normalized.length === 0) {
|
|
11031
11241
|
throw new Error("Invalid artifact store path");
|
|
11032
11242
|
}
|
|
11033
|
-
return
|
|
11243
|
+
return join9(artifactStoreRoot(), normalized);
|
|
11034
11244
|
}
|
|
11035
11245
|
function sha256(buffer) {
|
|
11036
11246
|
return createHash2("sha256").update(buffer).digest("hex");
|
|
@@ -11088,11 +11298,11 @@ function storeArtifactContent(input) {
|
|
|
11088
11298
|
redactionStatus = "redacted";
|
|
11089
11299
|
}
|
|
11090
11300
|
const storedSha = sha256(storedBuffer);
|
|
11091
|
-
const relativePath =
|
|
11301
|
+
const relativePath = join9("sha256", storedSha.slice(0, 2), storedSha).replace(/\\/g, "/");
|
|
11092
11302
|
const destination = artifactStorePath(relativePath);
|
|
11093
11303
|
if (!existsSync8(destination)) {
|
|
11094
|
-
|
|
11095
|
-
|
|
11304
|
+
mkdirSync5(dirname6(destination), { recursive: true });
|
|
11305
|
+
writeFileSync4(destination, storedBuffer);
|
|
11096
11306
|
}
|
|
11097
11307
|
const createdAt = input.created_at || new Date().toISOString();
|
|
11098
11308
|
const retentionDays = input.retention_days ?? null;
|
|
@@ -11209,8 +11419,8 @@ function importStoredArtifactContent(content) {
|
|
|
11209
11419
|
};
|
|
11210
11420
|
}
|
|
11211
11421
|
const destination = artifactStorePath(content.relative_path);
|
|
11212
|
-
|
|
11213
|
-
|
|
11422
|
+
mkdirSync5(dirname6(destination), { recursive: true });
|
|
11423
|
+
writeFileSync4(destination, buffer);
|
|
11214
11424
|
return {
|
|
11215
11425
|
id: content.artifact_id,
|
|
11216
11426
|
path: content.relative_path,
|
|
@@ -13032,8 +13242,8 @@ function registerProjectCommands(program2) {
|
|
|
13032
13242
|
const projectId = autoProject(globalOpts);
|
|
13033
13243
|
const writeOutput = async (content) => {
|
|
13034
13244
|
if (opts.output) {
|
|
13035
|
-
const { writeFileSync:
|
|
13036
|
-
|
|
13245
|
+
const { writeFileSync: writeFileSync5 } = await import("fs");
|
|
13246
|
+
writeFileSync5(resolve9(opts.output), content.endsWith(`
|
|
13037
13247
|
`) ? content : `${content}
|
|
13038
13248
|
`);
|
|
13039
13249
|
} else {
|
|
@@ -14822,8 +15032,8 @@ var exports_doctor = {};
|
|
|
14822
15032
|
__export(exports_doctor, {
|
|
14823
15033
|
runTodosDoctor: () => runTodosDoctor
|
|
14824
15034
|
});
|
|
14825
|
-
import { chmodSync, copyFileSync, existsSync as existsSync9, mkdirSync as
|
|
14826
|
-
import { basename as basename4, dirname as dirname7, join as
|
|
15035
|
+
import { chmodSync, copyFileSync, existsSync as existsSync9, mkdirSync as mkdirSync6, statSync as statSync5 } from "fs";
|
|
15036
|
+
import { basename as basename4, dirname as dirname7, join as join10 } from "path";
|
|
14827
15037
|
function tableExists(db, table) {
|
|
14828
15038
|
return Boolean(db.query("SELECT name FROM sqlite_master WHERE type='table' AND name=?").get(table));
|
|
14829
15039
|
}
|
|
@@ -14980,13 +15190,13 @@ function createBackup(dbPath) {
|
|
|
14980
15190
|
if (!existsSync9(dbPath))
|
|
14981
15191
|
return;
|
|
14982
15192
|
const stamp = now().replace(/[:.]/g, "-");
|
|
14983
|
-
const backupDir =
|
|
15193
|
+
const backupDir = join10(dirname7(dbPath), `${basename4(dbPath)}.backup-${stamp}`);
|
|
14984
15194
|
const files = [];
|
|
14985
|
-
|
|
15195
|
+
mkdirSync6(backupDir, { recursive: true });
|
|
14986
15196
|
for (const source of [dbPath, `${dbPath}-wal`, `${dbPath}-shm`]) {
|
|
14987
15197
|
if (!existsSync9(source))
|
|
14988
15198
|
continue;
|
|
14989
|
-
const target =
|
|
15199
|
+
const target = join10(backupDir, basename4(source));
|
|
14990
15200
|
copyFileSync(source, target);
|
|
14991
15201
|
files.push(target);
|
|
14992
15202
|
}
|
|
@@ -15212,7 +15422,7 @@ var init_doctor = __esm(() => {
|
|
|
15212
15422
|
});
|
|
15213
15423
|
|
|
15214
15424
|
// src/server/routes.ts
|
|
15215
|
-
import { join as
|
|
15425
|
+
import { join as join11, resolve as resolve11, sep } from "path";
|
|
15216
15426
|
function parseFieldsParam(url) {
|
|
15217
15427
|
const fieldsParam = url.searchParams.get("fields");
|
|
15218
15428
|
return fieldsParam ? fieldsParam.split(",").map((f) => f.trim()).filter(Boolean) : undefined;
|
|
@@ -15912,7 +16122,7 @@ function handleStaticFiles(path, method, ctx, json2, serveStaticFile2) {
|
|
|
15912
16122
|
if (!ctx.dashboardExists || method !== "GET" && method !== "HEAD")
|
|
15913
16123
|
return null;
|
|
15914
16124
|
if (path !== "/") {
|
|
15915
|
-
const filePath =
|
|
16125
|
+
const filePath = join11(ctx.dashboardDir, path);
|
|
15916
16126
|
const resolvedFile = resolve11(filePath);
|
|
15917
16127
|
const resolvedBase = resolve11(ctx.dashboardDir);
|
|
15918
16128
|
if (!resolvedFile.startsWith(resolvedBase + sep) && resolvedFile !== resolvedBase) {
|
|
@@ -15922,7 +16132,7 @@ function handleStaticFiles(path, method, ctx, json2, serveStaticFile2) {
|
|
|
15922
16132
|
if (res2)
|
|
15923
16133
|
return res2;
|
|
15924
16134
|
}
|
|
15925
|
-
const indexPath =
|
|
16135
|
+
const indexPath = join11(ctx.dashboardDir, "index.html");
|
|
15926
16136
|
const res = serveStaticFile2(indexPath);
|
|
15927
16137
|
if (res)
|
|
15928
16138
|
return res;
|
|
@@ -15952,26 +16162,26 @@ __export(exports_serve, {
|
|
|
15952
16162
|
MIME_TYPES: () => MIME_TYPES
|
|
15953
16163
|
});
|
|
15954
16164
|
import { existsSync as existsSync10 } from "fs";
|
|
15955
|
-
import { join as
|
|
16165
|
+
import { join as join12, dirname as dirname8, extname } from "path";
|
|
15956
16166
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
15957
16167
|
function resolveDashboardDir() {
|
|
15958
16168
|
const candidates = [];
|
|
15959
16169
|
try {
|
|
15960
16170
|
const scriptDir = dirname8(fileURLToPath2(import.meta.url));
|
|
15961
|
-
candidates.push(
|
|
15962
|
-
candidates.push(
|
|
16171
|
+
candidates.push(join12(scriptDir, "..", "dashboard", "dist"));
|
|
16172
|
+
candidates.push(join12(scriptDir, "..", "..", "dashboard", "dist"));
|
|
15963
16173
|
} catch {}
|
|
15964
16174
|
if (process.argv[1]) {
|
|
15965
16175
|
const mainDir = dirname8(process.argv[1]);
|
|
15966
|
-
candidates.push(
|
|
15967
|
-
candidates.push(
|
|
16176
|
+
candidates.push(join12(mainDir, "..", "dashboard", "dist"));
|
|
16177
|
+
candidates.push(join12(mainDir, "..", "..", "dashboard", "dist"));
|
|
15968
16178
|
}
|
|
15969
|
-
candidates.push(
|
|
16179
|
+
candidates.push(join12(process.cwd(), "dashboard", "dist"));
|
|
15970
16180
|
for (const candidate of candidates) {
|
|
15971
16181
|
if (existsSync10(candidate))
|
|
15972
16182
|
return candidate;
|
|
15973
16183
|
}
|
|
15974
|
-
return
|
|
16184
|
+
return join12(process.cwd(), "dashboard", "dist");
|
|
15975
16185
|
}
|
|
15976
16186
|
function getProvidedApiKey(req) {
|
|
15977
16187
|
const headerKey = req.headers.get("x-api-key");
|
|
@@ -17747,14 +17957,14 @@ __export(exports_config_serve_commands, {
|
|
|
17747
17957
|
registerConfigServeCommands: () => registerConfigServeCommands
|
|
17748
17958
|
});
|
|
17749
17959
|
import chalk6 from "chalk";
|
|
17750
|
-
import { existsSync as existsSync11, mkdirSync as
|
|
17751
|
-
import { dirname as dirname9, join as
|
|
17960
|
+
import { existsSync as existsSync11, mkdirSync as mkdirSync7, readFileSync as readFileSync7, writeFileSync as writeFileSync5 } from "fs";
|
|
17961
|
+
import { dirname as dirname9, join as join13 } from "path";
|
|
17752
17962
|
function registerConfigServeCommands(program2) {
|
|
17753
17963
|
program2.command("config").description("View or update configuration").option("--get <key>", "Get a config value").option("--set <key=value>", "Set a config value (e.g. completion_guard.enabled=true)").action((opts) => {
|
|
17754
17964
|
const globalOpts = program2.opts();
|
|
17755
17965
|
const home = process.env["HOME"] || "~";
|
|
17756
|
-
const newPath =
|
|
17757
|
-
const legacyPath =
|
|
17966
|
+
const newPath = join13(home, ".hasna", "todos", "config.json");
|
|
17967
|
+
const legacyPath = join13(home, ".todos", "config.json");
|
|
17758
17968
|
const configPath = !existsSync11(newPath) && existsSync11(legacyPath) ? legacyPath : newPath;
|
|
17759
17969
|
if (opts.get) {
|
|
17760
17970
|
const config2 = loadConfig();
|
|
@@ -17793,8 +18003,8 @@ function registerConfigServeCommands(program2) {
|
|
|
17793
18003
|
obj[keys[keys.length - 1]] = parsedValue;
|
|
17794
18004
|
const dir = dirname9(configPath);
|
|
17795
18005
|
if (!existsSync11(dir))
|
|
17796
|
-
|
|
17797
|
-
|
|
18006
|
+
mkdirSync7(dir, { recursive: true });
|
|
18007
|
+
writeFileSync5(configPath, JSON.stringify(config2, null, 2));
|
|
17798
18008
|
if (globalOpts.json) {
|
|
17799
18009
|
output({ key, value: parsedValue }, true);
|
|
17800
18010
|
} else {
|
|
@@ -20605,8 +20815,8 @@ Repairs`));
|
|
|
20605
20815
|
const db = getDatabase();
|
|
20606
20816
|
const row = db.query("SELECT COUNT(*) as count FROM tasks").get();
|
|
20607
20817
|
const { statSync: statSync6 } = await import("fs");
|
|
20608
|
-
const { join:
|
|
20609
|
-
const dbPath = process.env["TODOS_DB_PATH"] ||
|
|
20818
|
+
const { join: join14 } = await import("path");
|
|
20819
|
+
const dbPath = process.env["TODOS_DB_PATH"] || join14(process.env["HOME"] || "~", ".todos", "todos.db");
|
|
20610
20820
|
let size = "unknown";
|
|
20611
20821
|
try {
|
|
20612
20822
|
size = `${(statSync6(dbPath).size / 1024 / 1024).toFixed(1)} MB`;
|
|
@@ -26403,6 +26613,8 @@ var init_token_utils = __esm(() => {
|
|
|
26403
26613
|
"check_file_lock",
|
|
26404
26614
|
"create_comment",
|
|
26405
26615
|
"create_handoff",
|
|
26616
|
+
"capture_environment_snapshot",
|
|
26617
|
+
"compare_environment_snapshots",
|
|
26406
26618
|
"create_inbox_item",
|
|
26407
26619
|
"delete_comment",
|
|
26408
26620
|
"detect_file_relationships",
|
|
@@ -26525,10 +26737,12 @@ var init_token_utils = __esm(() => {
|
|
|
26525
26737
|
"export_template",
|
|
26526
26738
|
"import_template",
|
|
26527
26739
|
"init_templates",
|
|
26740
|
+
"list_template_library",
|
|
26528
26741
|
"list_templates",
|
|
26529
26742
|
"preview_template",
|
|
26530
26743
|
"template_history",
|
|
26531
|
-
"update_template"
|
|
26744
|
+
"update_template",
|
|
26745
|
+
"write_template_library"
|
|
26532
26746
|
],
|
|
26533
26747
|
webhooks: ["create_webhook", "delete_webhook", "list_webhooks"],
|
|
26534
26748
|
machines: [
|
|
@@ -32670,6 +32884,587 @@ var init_agents2 = __esm(() => {
|
|
|
32670
32884
|
init_database();
|
|
32671
32885
|
});
|
|
32672
32886
|
|
|
32887
|
+
// src/mcp/tools/templates.ts
|
|
32888
|
+
function registerTemplateTools(server, { shouldRegisterTool, resolveId, formatError }) {
|
|
32889
|
+
if (shouldRegisterTool("create_template")) {
|
|
32890
|
+
server.tool("create_template", "Create a reusable task template. Optionally include a tasks array to define a multi-task template with dependencies and variable placeholders ({name} syntax). Use variables to define typed variable definitions with defaults and required flags.", {
|
|
32891
|
+
name: exports_external.string(),
|
|
32892
|
+
title_pattern: exports_external.string(),
|
|
32893
|
+
description: exports_external.string().optional(),
|
|
32894
|
+
priority: exports_external.enum(["low", "medium", "high", "critical"]).optional(),
|
|
32895
|
+
tags: exports_external.array(exports_external.string()).optional(),
|
|
32896
|
+
project_id: exports_external.string().optional(),
|
|
32897
|
+
plan_id: exports_external.string().optional(),
|
|
32898
|
+
variables: exports_external.array(exports_external.object({
|
|
32899
|
+
name: exports_external.string().describe("Variable name (used as {name} in patterns)"),
|
|
32900
|
+
required: exports_external.boolean().describe("Whether this variable must be provided"),
|
|
32901
|
+
default: exports_external.string().optional().describe("Default value if not provided"),
|
|
32902
|
+
description: exports_external.string().optional().describe("Help text for the variable")
|
|
32903
|
+
})).optional().describe("Typed variable definitions with defaults and required flags"),
|
|
32904
|
+
tasks: exports_external.array(exports_external.object({
|
|
32905
|
+
title_pattern: exports_external.string().describe("Title pattern with optional {variable} placeholders"),
|
|
32906
|
+
description: exports_external.string().optional(),
|
|
32907
|
+
priority: exports_external.enum(["low", "medium", "high", "critical"]).optional(),
|
|
32908
|
+
tags: exports_external.array(exports_external.string()).optional(),
|
|
32909
|
+
task_type: exports_external.string().optional(),
|
|
32910
|
+
depends_on: exports_external.array(exports_external.number()).optional().describe("Position indices (0-based) of tasks this task depends on"),
|
|
32911
|
+
metadata: exports_external.record(exports_external.unknown()).optional()
|
|
32912
|
+
})).optional().describe("Multi-task template: ordered list of tasks to create together with dependencies")
|
|
32913
|
+
}, async (params) => {
|
|
32914
|
+
try {
|
|
32915
|
+
const { createTemplate: createTemplate2, getTemplateWithTasks: getTemplateWithTasks2 } = await Promise.resolve().then(() => (init_templates(), exports_templates));
|
|
32916
|
+
const t = createTemplate2(params);
|
|
32917
|
+
const withTasks = getTemplateWithTasks2(t.id);
|
|
32918
|
+
const taskCount = withTasks?.tasks.length ?? 0;
|
|
32919
|
+
const taskInfo = taskCount > 0 ? ` | ${taskCount} task(s)` : "";
|
|
32920
|
+
return { content: [{ type: "text", text: `Template created: ${t.id.slice(0, 8)} | ${t.name} | "${t.title_pattern}"${taskInfo}` }] };
|
|
32921
|
+
} catch (e) {
|
|
32922
|
+
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
32923
|
+
}
|
|
32924
|
+
});
|
|
32925
|
+
}
|
|
32926
|
+
if (shouldRegisterTool("list_templates")) {
|
|
32927
|
+
server.tool("list_templates", "List all task templates", {}, async () => {
|
|
32928
|
+
try {
|
|
32929
|
+
const { listTemplates: listTemplates2 } = await Promise.resolve().then(() => (init_templates(), exports_templates));
|
|
32930
|
+
const templates = listTemplates2();
|
|
32931
|
+
if (templates.length === 0)
|
|
32932
|
+
return { content: [{ type: "text", text: "No templates." }] };
|
|
32933
|
+
const text = templates.map((t) => {
|
|
32934
|
+
const vars = t.variables.length > 0 ? ` | vars: ${t.variables.map((v) => `${v.name}${v.required ? "*" : ""}${v.default ? `=${v.default}` : ""}`).join(", ")}` : "";
|
|
32935
|
+
return `${t.id.slice(0, 8)} | ${t.name} | "${t.title_pattern}" | ${t.priority}${vars}`;
|
|
32936
|
+
}).join(`
|
|
32937
|
+
`);
|
|
32938
|
+
return { content: [{ type: "text", text: `${templates.length} template(s):
|
|
32939
|
+
${text}` }] };
|
|
32940
|
+
} catch (e) {
|
|
32941
|
+
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
32942
|
+
}
|
|
32943
|
+
});
|
|
32944
|
+
}
|
|
32945
|
+
if (shouldRegisterTool("create_task_from_template")) {
|
|
32946
|
+
server.tool("create_task_from_template", "Create task(s) from a template. For multi-task templates, creates all tasks with dependencies wired. Supports {variable} substitution in titles/descriptions.", {
|
|
32947
|
+
template_id: exports_external.string(),
|
|
32948
|
+
title: exports_external.string().optional().describe("Override title (single-task templates only)"),
|
|
32949
|
+
description: exports_external.string().optional().describe("Override description (single-task templates only)"),
|
|
32950
|
+
priority: exports_external.enum(["low", "medium", "high", "critical"]).optional(),
|
|
32951
|
+
assigned_to: exports_external.string().optional(),
|
|
32952
|
+
project_id: exports_external.string().optional(),
|
|
32953
|
+
task_list_id: exports_external.string().optional(),
|
|
32954
|
+
variables: exports_external.record(exports_external.string()).optional().describe("Variable substitution map for {name} placeholders in multi-task templates")
|
|
32955
|
+
}, async (params) => {
|
|
32956
|
+
try {
|
|
32957
|
+
const { taskFromTemplate: taskFromTemplate2, getTemplateWithTasks: getTemplateWithTasks2, tasksFromTemplate: tasksFromTemplate2 } = await Promise.resolve().then(() => (init_templates(), exports_templates));
|
|
32958
|
+
const resolvedTemplateId = resolveId(params.template_id, "task_templates");
|
|
32959
|
+
const resolvedProjectId = params.project_id ? resolveId(params.project_id, "projects") : undefined;
|
|
32960
|
+
const resolvedTaskListId = params.task_list_id ? resolveId(params.task_list_id, "task_lists") : undefined;
|
|
32961
|
+
const templateWithTasks = getTemplateWithTasks2(resolvedTemplateId);
|
|
32962
|
+
if (templateWithTasks && templateWithTasks.tasks.length > 0) {
|
|
32963
|
+
const effectiveProjectId = resolvedProjectId || templateWithTasks.project_id || undefined;
|
|
32964
|
+
const tasks = tasksFromTemplate2(resolvedTemplateId, effectiveProjectId, params.variables, resolvedTaskListId);
|
|
32965
|
+
const text = tasks.map((t) => `${t.id.slice(0, 8)} | ${t.priority} | ${t.title}`).join(`
|
|
32966
|
+
`);
|
|
32967
|
+
return { content: [{ type: "text", text: `${tasks.length} task(s) created from template:
|
|
32968
|
+
${text}` }] };
|
|
32969
|
+
}
|
|
32970
|
+
const input = taskFromTemplate2(resolvedTemplateId, {
|
|
32971
|
+
title: params.title,
|
|
32972
|
+
description: params.description,
|
|
32973
|
+
priority: params.priority,
|
|
32974
|
+
assigned_to: params.assigned_to,
|
|
32975
|
+
project_id: resolvedProjectId,
|
|
32976
|
+
task_list_id: resolvedTaskListId
|
|
32977
|
+
});
|
|
32978
|
+
const task = createTask(input);
|
|
32979
|
+
return { content: [{ type: "text", text: `Task created from template:
|
|
32980
|
+
${task.id.slice(0, 8)} | ${task.priority} | ${task.title}` }] };
|
|
32981
|
+
} catch (e) {
|
|
32982
|
+
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
32983
|
+
}
|
|
32984
|
+
});
|
|
32985
|
+
}
|
|
32986
|
+
if (shouldRegisterTool("delete_template")) {
|
|
32987
|
+
server.tool("delete_template", "Delete a task template by ID.", { id: exports_external.string() }, async ({ id }) => {
|
|
32988
|
+
try {
|
|
32989
|
+
const { deleteTemplate: deleteTemplate2 } = await Promise.resolve().then(() => (init_templates(), exports_templates));
|
|
32990
|
+
const resolvedId = resolveId(id, "task_templates");
|
|
32991
|
+
const deleted = deleteTemplate2(resolvedId);
|
|
32992
|
+
return { content: [{ type: "text", text: deleted ? "Template deleted." : "Template not found." }] };
|
|
32993
|
+
} catch (e) {
|
|
32994
|
+
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
32995
|
+
}
|
|
32996
|
+
});
|
|
32997
|
+
}
|
|
32998
|
+
if (shouldRegisterTool("update_template")) {
|
|
32999
|
+
server.tool("update_template", "Update a task template's name, title pattern, description, priority, tags, or other fields.", {
|
|
33000
|
+
id: exports_external.string(),
|
|
33001
|
+
name: exports_external.string().optional(),
|
|
33002
|
+
title_pattern: exports_external.string().optional(),
|
|
33003
|
+
description: exports_external.string().optional(),
|
|
33004
|
+
priority: exports_external.enum(["low", "medium", "high", "critical"]).optional(),
|
|
33005
|
+
tags: exports_external.array(exports_external.string()).optional(),
|
|
33006
|
+
project_id: exports_external.string().optional(),
|
|
33007
|
+
plan_id: exports_external.string().optional()
|
|
33008
|
+
}, async ({ id, ...updates }) => {
|
|
33009
|
+
try {
|
|
33010
|
+
const { updateTemplate: updateTemplate2 } = await Promise.resolve().then(() => (init_templates(), exports_templates));
|
|
33011
|
+
const resolvedId = resolveId(id, "task_templates");
|
|
33012
|
+
const t = updateTemplate2(resolvedId, updates);
|
|
33013
|
+
if (!t)
|
|
33014
|
+
return { content: [{ type: "text", text: `Template not found: ${id}` }], isError: true };
|
|
33015
|
+
return { content: [{ type: "text", text: `Template updated: ${t.id.slice(0, 8)} | ${t.name} | "${t.title_pattern}" | ${t.priority}` }] };
|
|
33016
|
+
} catch (e) {
|
|
33017
|
+
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
33018
|
+
}
|
|
33019
|
+
});
|
|
33020
|
+
}
|
|
33021
|
+
if (shouldRegisterTool("init_templates")) {
|
|
33022
|
+
server.tool("init_templates", "Initialize the bundled local template library. Skips templates that already exist by name.", {}, async () => {
|
|
33023
|
+
try {
|
|
33024
|
+
const { initBuiltinTemplates: initBuiltinTemplates2 } = await Promise.resolve().then(() => (init_builtin_templates(), exports_builtin_templates));
|
|
33025
|
+
const result = initBuiltinTemplates2();
|
|
33026
|
+
if (result.created === 0)
|
|
33027
|
+
return { content: [{ type: "text", text: `All ${result.skipped} built-in template(s) already exist.` }] };
|
|
33028
|
+
return { content: [{ type: "text", text: `Created ${result.created} template(s): ${result.names.join(", ")}. Skipped ${result.skipped} existing.` }] };
|
|
33029
|
+
} catch (e) {
|
|
33030
|
+
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
33031
|
+
}
|
|
33032
|
+
});
|
|
33033
|
+
}
|
|
33034
|
+
if (shouldRegisterTool("list_template_library")) {
|
|
33035
|
+
server.tool("list_template_library", "List the bundled marketplace-free local template library without mutating the database.", {}, async () => {
|
|
33036
|
+
try {
|
|
33037
|
+
const { listBuiltinTemplates: listBuiltinTemplates2 } = await Promise.resolve().then(() => (init_builtin_templates(), exports_builtin_templates));
|
|
33038
|
+
const templates = listBuiltinTemplates2().map((template) => ({
|
|
33039
|
+
name: template.name,
|
|
33040
|
+
description: template.description,
|
|
33041
|
+
category: template.category,
|
|
33042
|
+
version: template.version,
|
|
33043
|
+
variables: template.variables,
|
|
33044
|
+
task_count: template.tasks.length
|
|
33045
|
+
}));
|
|
33046
|
+
return { content: [{ type: "text", text: JSON.stringify(templates, null, 2) }] };
|
|
33047
|
+
} catch (e) {
|
|
33048
|
+
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
33049
|
+
}
|
|
33050
|
+
});
|
|
33051
|
+
}
|
|
33052
|
+
if (shouldRegisterTool("write_template_library")) {
|
|
33053
|
+
server.tool("write_template_library", "Write the bundled local template library to editable JSON files for review or import.", { directory: exports_external.string() }, async ({ directory }) => {
|
|
33054
|
+
try {
|
|
33055
|
+
const { writeBuiltinTemplateFiles: writeBuiltinTemplateFiles2 } = await Promise.resolve().then(() => (init_builtin_templates(), exports_builtin_templates));
|
|
33056
|
+
return { content: [{ type: "text", text: JSON.stringify(writeBuiltinTemplateFiles2(directory), null, 2) }] };
|
|
33057
|
+
} catch (e) {
|
|
33058
|
+
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
33059
|
+
}
|
|
33060
|
+
});
|
|
33061
|
+
}
|
|
33062
|
+
if (shouldRegisterTool("preview_template")) {
|
|
33063
|
+
server.tool("preview_template", "Preview a template without creating tasks. Shows resolved titles (variables substituted), dependencies, and priorities.", {
|
|
33064
|
+
template_id: exports_external.string(),
|
|
33065
|
+
variables: exports_external.record(exports_external.string()).optional().describe("Variable substitution map for {name} placeholders")
|
|
33066
|
+
}, async (params) => {
|
|
33067
|
+
try {
|
|
33068
|
+
const { previewTemplate: previewTemplate2 } = await Promise.resolve().then(() => (init_templates(), exports_templates));
|
|
33069
|
+
const resolvedId = resolveId(params.template_id, "task_templates");
|
|
33070
|
+
const preview = previewTemplate2(resolvedId, params.variables);
|
|
33071
|
+
const lines = preview.tasks.map((t) => {
|
|
33072
|
+
const deps = t.depends_on_positions.length > 0 ? ` (after: ${t.depends_on_positions.join(", ")})` : "";
|
|
33073
|
+
return ` [${t.position}] ${t.priority} | ${t.title}${deps}`;
|
|
33074
|
+
});
|
|
33075
|
+
const varsInfo = preview.variables.length > 0 ? `
|
|
33076
|
+
Variables: ${preview.variables.map((v) => `${v.name}${v.required ? "*" : ""}${v.default ? `=${v.default}` : ""}`).join(", ")}` : "";
|
|
33077
|
+
const resolvedVars = Object.keys(preview.resolved_variables).length > 0 ? `
|
|
33078
|
+
Resolved: ${Object.entries(preview.resolved_variables).map(([k, v]) => `${k}=${v}`).join(", ")}` : "";
|
|
33079
|
+
return { content: [{ type: "text", text: `Preview: ${preview.template_name} (${preview.tasks.length} tasks)${varsInfo}${resolvedVars}
|
|
33080
|
+
${lines.join(`
|
|
33081
|
+
`)}` }] };
|
|
33082
|
+
} catch (e) {
|
|
33083
|
+
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
33084
|
+
}
|
|
33085
|
+
});
|
|
33086
|
+
}
|
|
33087
|
+
if (shouldRegisterTool("export_template")) {
|
|
33088
|
+
server.tool("export_template", "Export a template as a full JSON object (template + tasks + variables). Useful for sharing or backup.", { template_id: exports_external.string() }, async ({ template_id }) => {
|
|
33089
|
+
try {
|
|
33090
|
+
const { exportTemplate: exportTemplate2 } = await Promise.resolve().then(() => (init_templates(), exports_templates));
|
|
33091
|
+
const resolvedId = resolveId(template_id, "task_templates");
|
|
33092
|
+
const json2 = exportTemplate2(resolvedId);
|
|
33093
|
+
return { content: [{ type: "text", text: JSON.stringify(json2, null, 2) }] };
|
|
33094
|
+
} catch (e) {
|
|
33095
|
+
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
33096
|
+
}
|
|
33097
|
+
});
|
|
33098
|
+
}
|
|
33099
|
+
if (shouldRegisterTool("import_template")) {
|
|
33100
|
+
server.tool("import_template", "Import a template from a JSON string (as returned by export_template). Creates new template with new IDs.", { json: exports_external.string().describe("JSON string of the template export") }, async ({ json: json2 }) => {
|
|
33101
|
+
try {
|
|
33102
|
+
const { importTemplate: importTemplate2 } = await Promise.resolve().then(() => (init_templates(), exports_templates));
|
|
33103
|
+
const parsed = JSON.parse(json2);
|
|
33104
|
+
const t = importTemplate2(parsed);
|
|
33105
|
+
return { content: [{ type: "text", text: `Template imported: ${t.id.slice(0, 8)} | ${t.name} | "${t.title_pattern}"` }] };
|
|
33106
|
+
} catch (e) {
|
|
33107
|
+
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
33108
|
+
}
|
|
33109
|
+
});
|
|
33110
|
+
}
|
|
33111
|
+
if (shouldRegisterTool("template_history")) {
|
|
33112
|
+
server.tool("template_history", "Show version history of a template. Each update creates a snapshot of the previous state.", { template_id: exports_external.string() }, async ({ template_id }) => {
|
|
33113
|
+
try {
|
|
33114
|
+
const { listTemplateVersions: listTemplateVersions2, getTemplate: getTemplate2 } = await Promise.resolve().then(() => (init_templates(), exports_templates));
|
|
33115
|
+
const resolvedId = resolveId(template_id, "task_templates");
|
|
33116
|
+
const template = getTemplate2(resolvedId);
|
|
33117
|
+
if (!template)
|
|
33118
|
+
return { content: [{ type: "text", text: `Template not found: ${template_id}` }], isError: true };
|
|
33119
|
+
const versions = listTemplateVersions2(resolvedId);
|
|
33120
|
+
if (versions.length === 0)
|
|
33121
|
+
return { content: [{ type: "text", text: `${template.name} v${template.version} \u2014 no previous versions.` }] };
|
|
33122
|
+
const lines = versions.map((v) => {
|
|
33123
|
+
const snap = JSON.parse(v.snapshot);
|
|
33124
|
+
return `v${v.version} | ${v.created_at} | ${snap.name} | "${snap.title_pattern}"`;
|
|
33125
|
+
});
|
|
33126
|
+
return { content: [{ type: "text", text: `${template.name} \u2014 current: v${template.version}
|
|
33127
|
+
${lines.join(`
|
|
33128
|
+
`)}` }] };
|
|
33129
|
+
} catch (e) {
|
|
33130
|
+
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
33131
|
+
}
|
|
33132
|
+
});
|
|
33133
|
+
}
|
|
33134
|
+
}
|
|
33135
|
+
var init_templates2 = __esm(() => {
|
|
33136
|
+
init_zod();
|
|
33137
|
+
init_tasks();
|
|
33138
|
+
});
|
|
33139
|
+
|
|
33140
|
+
// src/lib/environment-snapshots.ts
|
|
33141
|
+
var exports_environment_snapshots = {};
|
|
33142
|
+
__export(exports_environment_snapshots, {
|
|
33143
|
+
writeEnvironmentSnapshot: () => writeEnvironmentSnapshot,
|
|
33144
|
+
recordEnvironmentSnapshot: () => recordEnvironmentSnapshot,
|
|
33145
|
+
readEnvironmentSnapshot: () => readEnvironmentSnapshot,
|
|
33146
|
+
compareEnvironmentSnapshots: () => compareEnvironmentSnapshots,
|
|
33147
|
+
compareEnvironmentSnapshotFiles: () => compareEnvironmentSnapshotFiles,
|
|
33148
|
+
captureEnvironmentSnapshot: () => captureEnvironmentSnapshot
|
|
33149
|
+
});
|
|
33150
|
+
import { createHash as createHash6 } from "crypto";
|
|
33151
|
+
import { existsSync as existsSync13, readFileSync as readFileSync9, statSync as statSync6 } from "fs";
|
|
33152
|
+
import { hostname, platform, arch } from "os";
|
|
33153
|
+
import { dirname as dirname10, join as join14, resolve as resolve12 } from "path";
|
|
33154
|
+
import { tmpdir as tmpdir2 } from "os";
|
|
33155
|
+
function sha2563(value) {
|
|
33156
|
+
return createHash6("sha256").update(value).digest("hex");
|
|
33157
|
+
}
|
|
33158
|
+
function fileRecord(root, relativePath) {
|
|
33159
|
+
const path = join14(root, relativePath);
|
|
33160
|
+
if (!existsSync13(path))
|
|
33161
|
+
return null;
|
|
33162
|
+
const stat = statSync6(path);
|
|
33163
|
+
if (!stat.isFile())
|
|
33164
|
+
return null;
|
|
33165
|
+
const content = readFileSync9(path);
|
|
33166
|
+
return { path: relativePath, sha256: sha2563(content), size_bytes: content.length };
|
|
33167
|
+
}
|
|
33168
|
+
function manifestRecord(root, relativePath) {
|
|
33169
|
+
const base = fileRecord(root, relativePath);
|
|
33170
|
+
if (!base)
|
|
33171
|
+
return null;
|
|
33172
|
+
const parsed = readJsonFile(join14(root, relativePath));
|
|
33173
|
+
if (!parsed)
|
|
33174
|
+
return { ...base, redacted: {} };
|
|
33175
|
+
const redacted = redactValue({
|
|
33176
|
+
name: parsed["name"] ?? null,
|
|
33177
|
+
version: parsed["version"] ?? null,
|
|
33178
|
+
packageManager: parsed["packageManager"] ?? null,
|
|
33179
|
+
scripts: parsed["scripts"] ?? {},
|
|
33180
|
+
dependencies: parsed["dependencies"] ?? {},
|
|
33181
|
+
devDependencies: parsed["devDependencies"] ?? {},
|
|
33182
|
+
peerDependencies: parsed["peerDependencies"] ?? {},
|
|
33183
|
+
optionalDependencies: parsed["optionalDependencies"] ?? {}
|
|
33184
|
+
});
|
|
33185
|
+
return { ...base, redacted };
|
|
33186
|
+
}
|
|
33187
|
+
function runLocalCommand(root, args) {
|
|
33188
|
+
const result = Bun.spawnSync({
|
|
33189
|
+
cmd: args,
|
|
33190
|
+
cwd: root,
|
|
33191
|
+
stdout: "pipe",
|
|
33192
|
+
stderr: "pipe",
|
|
33193
|
+
env: { PATH: process.env["PATH"] || "" }
|
|
33194
|
+
});
|
|
33195
|
+
return {
|
|
33196
|
+
exitCode: result.exitCode,
|
|
33197
|
+
stdout: redactEvidenceText(result.stdout.toString("utf8").trim()),
|
|
33198
|
+
stderr: redactEvidenceText(result.stderr.toString("utf8").trim())
|
|
33199
|
+
};
|
|
33200
|
+
}
|
|
33201
|
+
function summarizeGitStatus(lines) {
|
|
33202
|
+
const summary = { added: 0, modified: 0, deleted: 0, renamed: 0, untracked: 0 };
|
|
33203
|
+
for (const line of lines) {
|
|
33204
|
+
if (line.startsWith("??"))
|
|
33205
|
+
summary.untracked += 1;
|
|
33206
|
+
else if (line.includes("R"))
|
|
33207
|
+
summary.renamed += 1;
|
|
33208
|
+
else if (line.includes("D"))
|
|
33209
|
+
summary.deleted += 1;
|
|
33210
|
+
else if (line.includes("A"))
|
|
33211
|
+
summary.added += 1;
|
|
33212
|
+
else if (line.includes("M"))
|
|
33213
|
+
summary.modified += 1;
|
|
33214
|
+
}
|
|
33215
|
+
return summary;
|
|
33216
|
+
}
|
|
33217
|
+
function captureGit(root, warnings) {
|
|
33218
|
+
const inside = runLocalCommand(root, ["git", "rev-parse", "--is-inside-work-tree"]);
|
|
33219
|
+
if (inside.exitCode !== 0 || inside.stdout !== "true") {
|
|
33220
|
+
return { present: false, branch: null, commit: null, is_dirty: false, status_porcelain: [], status_summary: summarizeGitStatus([]) };
|
|
33221
|
+
}
|
|
33222
|
+
const branch = runLocalCommand(root, ["git", "branch", "--show-current"]);
|
|
33223
|
+
const commit = runLocalCommand(root, ["git", "rev-parse", "HEAD"]);
|
|
33224
|
+
const status = runLocalCommand(root, ["git", "status", "--porcelain=v1"]);
|
|
33225
|
+
if (commit.exitCode !== 0)
|
|
33226
|
+
warnings.push(`git commit unavailable: ${commit.stderr || commit.stdout || "unknown error"}`);
|
|
33227
|
+
if (status.exitCode !== 0)
|
|
33228
|
+
warnings.push(`git status unavailable: ${status.stderr || status.stdout || "unknown error"}`);
|
|
33229
|
+
const lines = status.stdout ? status.stdout.split(/\r?\n/).filter(Boolean) : [];
|
|
33230
|
+
return {
|
|
33231
|
+
present: true,
|
|
33232
|
+
branch: branch.stdout || null,
|
|
33233
|
+
commit: commit.stdout || null,
|
|
33234
|
+
is_dirty: lines.length > 0,
|
|
33235
|
+
status_porcelain: lines,
|
|
33236
|
+
status_summary: summarizeGitStatus(lines)
|
|
33237
|
+
};
|
|
33238
|
+
}
|
|
33239
|
+
function packageManager(env, lockfiles) {
|
|
33240
|
+
const userAgent = (env["npm_config_user_agent"] || "").toLowerCase();
|
|
33241
|
+
if (userAgent.includes("bun"))
|
|
33242
|
+
return "bun";
|
|
33243
|
+
if (lockfiles.some((file) => file.path.startsWith("bun.lock")))
|
|
33244
|
+
return "bun";
|
|
33245
|
+
if (userAgent.includes("npm") || lockfiles.some((file) => file.path.includes("package-lock")))
|
|
33246
|
+
return "npm";
|
|
33247
|
+
return "unknown";
|
|
33248
|
+
}
|
|
33249
|
+
function isSecretEnvKey(key) {
|
|
33250
|
+
return /api[_-]?key|token|secret|password|credential|private|session|cookie/i.test(key);
|
|
33251
|
+
}
|
|
33252
|
+
function commandEnv(env, includeValues) {
|
|
33253
|
+
const keys = Object.keys(env).sort();
|
|
33254
|
+
const interesting = keys.filter((key) => isSecretEnvKey(key) || ["CI", "NODE_ENV", "BUN_ENV", "SHELL", "TERM", "PATH", "PWD", "USER", "npm_config_user_agent"].includes(key) || key.startsWith("TODOS_") || key.startsWith("BUN_"));
|
|
33255
|
+
const redactedKeys = interesting.filter(isSecretEnvKey);
|
|
33256
|
+
const values = includeValues ? Object.fromEntries(interesting.map((key) => [key, isSecretEnvKey(key) ? "[REDACTED]" : redactEvidenceText(String(env[key] ?? ""))])) : null;
|
|
33257
|
+
return {
|
|
33258
|
+
command: null,
|
|
33259
|
+
env_keys: interesting,
|
|
33260
|
+
env: values,
|
|
33261
|
+
redacted_keys: redactedKeys
|
|
33262
|
+
};
|
|
33263
|
+
}
|
|
33264
|
+
function defaultSnapshotDir() {
|
|
33265
|
+
const dbPath = getDatabasePath();
|
|
33266
|
+
if (dbPath === ":memory:" || dbPath.startsWith("file::memory:"))
|
|
33267
|
+
return join14(tmpdir2(), "hasna-todos", "environment-snapshots");
|
|
33268
|
+
return join14(dirname10(resolve12(dbPath)), "environment-snapshots");
|
|
33269
|
+
}
|
|
33270
|
+
function snapshotWithId(snapshot) {
|
|
33271
|
+
const digest = sha2563(JSON.stringify(snapshot)).slice(0, 24);
|
|
33272
|
+
return { id: `env_${digest}`, ...snapshot };
|
|
33273
|
+
}
|
|
33274
|
+
function captureEnvironmentSnapshot(input = {}) {
|
|
33275
|
+
const root = resolve12(input.root || process.cwd());
|
|
33276
|
+
const env = input.env || process.env;
|
|
33277
|
+
const warnings = [];
|
|
33278
|
+
const manifests = MANIFEST_FILES.map((file) => manifestRecord(root, file)).filter((file) => Boolean(file));
|
|
33279
|
+
const lockfiles = LOCKFILES.map((file) => fileRecord(root, file)).filter((file) => Boolean(file));
|
|
33280
|
+
const configHashes = CONFIG_FILES.map((file) => fileRecord(root, file)).filter((file) => Boolean(file));
|
|
33281
|
+
const commandMetadata = commandEnv(env, Boolean(input.include_env_values));
|
|
33282
|
+
commandMetadata.command = input.command ? redactEvidenceText(input.command) : null;
|
|
33283
|
+
if (manifests.length === 0)
|
|
33284
|
+
warnings.push("no package manifest found");
|
|
33285
|
+
if (lockfiles.length === 0)
|
|
33286
|
+
warnings.push("no package lockfile found");
|
|
33287
|
+
return snapshotWithId({
|
|
33288
|
+
schema_version: 1,
|
|
33289
|
+
captured_at: input.now ? new Date(input.now).toISOString() : new Date().toISOString(),
|
|
33290
|
+
root,
|
|
33291
|
+
machine: { hostname: hostname(), platform: platform(), arch: arch() },
|
|
33292
|
+
target: {
|
|
33293
|
+
task_id: input.task_id ?? null,
|
|
33294
|
+
run_id: input.run_id ?? null,
|
|
33295
|
+
agent_id: input.agent_id ?? null
|
|
33296
|
+
},
|
|
33297
|
+
runtime: {
|
|
33298
|
+
bun: Bun.version || null,
|
|
33299
|
+
node: process.version,
|
|
33300
|
+
executable: process.execPath
|
|
33301
|
+
},
|
|
33302
|
+
package_manager: {
|
|
33303
|
+
manager: packageManager(env, lockfiles),
|
|
33304
|
+
user_agent: env["npm_config_user_agent"] ? redactEvidenceText(env["npm_config_user_agent"]) : null,
|
|
33305
|
+
manifests,
|
|
33306
|
+
lockfiles
|
|
33307
|
+
},
|
|
33308
|
+
git: captureGit(root, warnings),
|
|
33309
|
+
config_hashes: configHashes,
|
|
33310
|
+
command_env: commandMetadata,
|
|
33311
|
+
warnings
|
|
33312
|
+
});
|
|
33313
|
+
}
|
|
33314
|
+
function writeEnvironmentSnapshot(snapshot, outputPath) {
|
|
33315
|
+
const path = outputPath ? resolve12(outputPath) : join14(defaultSnapshotDir(), `${snapshot.id}.json`);
|
|
33316
|
+
ensureDir2(dirname10(path));
|
|
33317
|
+
writeJsonFile(path, snapshot);
|
|
33318
|
+
return path;
|
|
33319
|
+
}
|
|
33320
|
+
function readEnvironmentSnapshot(path) {
|
|
33321
|
+
const snapshot = readJsonFile(resolve12(path));
|
|
33322
|
+
if (!snapshot || snapshot.schema_version !== 1 || typeof snapshot.id !== "string") {
|
|
33323
|
+
throw new Error(`Invalid environment snapshot: ${path}`);
|
|
33324
|
+
}
|
|
33325
|
+
return snapshot;
|
|
33326
|
+
}
|
|
33327
|
+
function recordEnvironmentSnapshot(input = {}, db) {
|
|
33328
|
+
let taskId = input.task_id;
|
|
33329
|
+
let runId = input.run_id;
|
|
33330
|
+
const needsDatabase = Boolean(taskId || runId);
|
|
33331
|
+
const d = needsDatabase ? db || getDatabase() : null;
|
|
33332
|
+
if (runId) {
|
|
33333
|
+
runId = resolveTaskRunId(runId, d);
|
|
33334
|
+
const run = getTaskRun(runId, d);
|
|
33335
|
+
if (!run)
|
|
33336
|
+
throw new Error(`Run not found: ${input.run_id}`);
|
|
33337
|
+
taskId = taskId || run.task_id;
|
|
33338
|
+
}
|
|
33339
|
+
if (taskId && d) {
|
|
33340
|
+
taskId = resolvePartialId(d, "tasks", taskId) || taskId;
|
|
33341
|
+
if (!getTask(taskId, d))
|
|
33342
|
+
throw new Error(`Task not found: ${taskId}`);
|
|
33343
|
+
}
|
|
33344
|
+
const snapshot = captureEnvironmentSnapshot({ ...input, task_id: taskId, run_id: runId });
|
|
33345
|
+
const outputPath = writeEnvironmentSnapshot(snapshot, input.output_path);
|
|
33346
|
+
let taskVerificationId = null;
|
|
33347
|
+
let runArtifactId = null;
|
|
33348
|
+
if (runId) {
|
|
33349
|
+
const artifact = addTaskRunArtifact({
|
|
33350
|
+
run_id: runId,
|
|
33351
|
+
path: outputPath,
|
|
33352
|
+
artifact_type: "environment_snapshot",
|
|
33353
|
+
description: "Reproducible local environment snapshot",
|
|
33354
|
+
metadata: { environment_snapshot_id: snapshot.id, schema_version: snapshot.schema_version },
|
|
33355
|
+
store_content: input.store_content ?? true,
|
|
33356
|
+
agent_id: input.agent_id
|
|
33357
|
+
}, d);
|
|
33358
|
+
runArtifactId = artifact.id;
|
|
33359
|
+
} else if (taskId) {
|
|
33360
|
+
const verification = addTaskVerification({
|
|
33361
|
+
task_id: taskId,
|
|
33362
|
+
command: input.command || "capture environment snapshot",
|
|
33363
|
+
status: "unknown",
|
|
33364
|
+
output_summary: `environment snapshot ${snapshot.id}`,
|
|
33365
|
+
artifact_path: outputPath,
|
|
33366
|
+
agent_id: input.agent_id,
|
|
33367
|
+
run_at: snapshot.captured_at
|
|
33368
|
+
}, d);
|
|
33369
|
+
taskVerificationId = verification.id;
|
|
33370
|
+
}
|
|
33371
|
+
return { snapshot, output_path: outputPath, task_verification_id: taskVerificationId, run_artifact_id: runArtifactId };
|
|
33372
|
+
}
|
|
33373
|
+
function keyed(files) {
|
|
33374
|
+
return new Map(files.map((file) => [file.path, file]));
|
|
33375
|
+
}
|
|
33376
|
+
function diffFiles(left, right) {
|
|
33377
|
+
const leftMap = keyed(left);
|
|
33378
|
+
const rightMap = keyed(right);
|
|
33379
|
+
const paths = [...new Set([...leftMap.keys(), ...rightMap.keys()])].sort((a, b) => a.localeCompare(b));
|
|
33380
|
+
return paths.map((path) => ({ path, left_sha256: leftMap.get(path)?.sha256 ?? null, right_sha256: rightMap.get(path)?.sha256 ?? null })).filter((entry) => entry.left_sha256 !== entry.right_sha256);
|
|
33381
|
+
}
|
|
33382
|
+
function compareEnvironmentSnapshots(left, right) {
|
|
33383
|
+
const warnings = [];
|
|
33384
|
+
if (left.schema_version !== right.schema_version)
|
|
33385
|
+
warnings.push("snapshot schema versions differ");
|
|
33386
|
+
return {
|
|
33387
|
+
schema_version: 1,
|
|
33388
|
+
left_id: left.id,
|
|
33389
|
+
right_id: right.id,
|
|
33390
|
+
same_root: left.root === right.root,
|
|
33391
|
+
same_machine: left.machine.hostname === right.machine.hostname && left.machine.platform === right.machine.platform && left.machine.arch === right.machine.arch,
|
|
33392
|
+
same_runtime: left.runtime.bun === right.runtime.bun && left.runtime.node === right.runtime.node,
|
|
33393
|
+
same_git_commit: left.git.commit === right.git.commit,
|
|
33394
|
+
dirty_state_changed: left.git.is_dirty !== right.git.is_dirty,
|
|
33395
|
+
changed_config_hashes: diffFiles(left.config_hashes, right.config_hashes),
|
|
33396
|
+
changed_lockfiles: diffFiles(left.package_manager.lockfiles, right.package_manager.lockfiles),
|
|
33397
|
+
changed_manifests: diffFiles(left.package_manager.manifests, right.package_manager.manifests),
|
|
33398
|
+
warnings
|
|
33399
|
+
};
|
|
33400
|
+
}
|
|
33401
|
+
function compareEnvironmentSnapshotFiles(leftPath, rightPath) {
|
|
33402
|
+
return compareEnvironmentSnapshots(readEnvironmentSnapshot(leftPath), readEnvironmentSnapshot(rightPath));
|
|
33403
|
+
}
|
|
33404
|
+
var MANIFEST_FILES, LOCKFILES, CONFIG_FILES;
|
|
33405
|
+
var init_environment_snapshots = __esm(() => {
|
|
33406
|
+
init_task_runs();
|
|
33407
|
+
init_task_commits();
|
|
33408
|
+
init_database();
|
|
33409
|
+
init_tasks();
|
|
33410
|
+
init_sync_utils();
|
|
33411
|
+
MANIFEST_FILES = ["package.json", "dashboard/package.json", "sdk/package.json"];
|
|
33412
|
+
LOCKFILES = ["bun.lock", "bun.lockb", "package-lock.json", "npm-shrinkwrap.json"];
|
|
33413
|
+
CONFIG_FILES = [
|
|
33414
|
+
"AGENTS.md",
|
|
33415
|
+
"CLAUDE.md",
|
|
33416
|
+
"README.md",
|
|
33417
|
+
"SECURITY.md",
|
|
33418
|
+
"bunfig.toml",
|
|
33419
|
+
"tsconfig.json",
|
|
33420
|
+
"components.json",
|
|
33421
|
+
"next.config.js",
|
|
33422
|
+
"next.config.mjs",
|
|
33423
|
+
"next.config.ts",
|
|
33424
|
+
"vite.config.ts",
|
|
33425
|
+
"dashboard/vite.config.ts"
|
|
33426
|
+
];
|
|
33427
|
+
});
|
|
33428
|
+
|
|
33429
|
+
// src/mcp/tools/environment-snapshots.ts
|
|
33430
|
+
function registerEnvironmentSnapshotTools(server, { shouldRegisterTool, formatError }) {
|
|
33431
|
+
if (shouldRegisterTool("capture_environment_snapshot")) {
|
|
33432
|
+
server.tool("capture_environment_snapshot", "Capture a local reproducible environment snapshot with Bun/node versions, package-manager state, git status, config hashes, command metadata, and redacted manifests. Optionally attach it to a local task or run.", {
|
|
33433
|
+
root: exports_external.string().optional(),
|
|
33434
|
+
task_id: exports_external.string().optional(),
|
|
33435
|
+
run_id: exports_external.string().optional(),
|
|
33436
|
+
agent_id: exports_external.string().optional(),
|
|
33437
|
+
command: exports_external.string().optional(),
|
|
33438
|
+
output_path: exports_external.string().optional(),
|
|
33439
|
+
include_env_values: exports_external.boolean().optional()
|
|
33440
|
+
}, async (params) => {
|
|
33441
|
+
try {
|
|
33442
|
+
const { recordEnvironmentSnapshot: recordEnvironmentSnapshot2 } = await Promise.resolve().then(() => (init_environment_snapshots(), exports_environment_snapshots));
|
|
33443
|
+
const result = recordEnvironmentSnapshot2(params);
|
|
33444
|
+
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
|
33445
|
+
} catch (e) {
|
|
33446
|
+
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
33447
|
+
}
|
|
33448
|
+
});
|
|
33449
|
+
}
|
|
33450
|
+
if (shouldRegisterTool("compare_environment_snapshots")) {
|
|
33451
|
+
server.tool("compare_environment_snapshots", "Compare two local environment snapshot JSON files and report runtime, git, manifest, lockfile, and config hash drift.", {
|
|
33452
|
+
left_path: exports_external.string(),
|
|
33453
|
+
right_path: exports_external.string()
|
|
33454
|
+
}, async ({ left_path, right_path }) => {
|
|
33455
|
+
try {
|
|
33456
|
+
const { compareEnvironmentSnapshotFiles: compareEnvironmentSnapshotFiles2 } = await Promise.resolve().then(() => (init_environment_snapshots(), exports_environment_snapshots));
|
|
33457
|
+
return { content: [{ type: "text", text: JSON.stringify(compareEnvironmentSnapshotFiles2(left_path, right_path), null, 2) }] };
|
|
33458
|
+
} catch (e) {
|
|
33459
|
+
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
33460
|
+
}
|
|
33461
|
+
});
|
|
33462
|
+
}
|
|
33463
|
+
}
|
|
33464
|
+
var init_environment_snapshots2 = __esm(() => {
|
|
33465
|
+
init_zod();
|
|
33466
|
+
});
|
|
33467
|
+
|
|
32673
33468
|
// src/mcp/index.ts
|
|
32674
33469
|
var exports_mcp = {};
|
|
32675
33470
|
__export(exports_mcp, {
|
|
@@ -32838,6 +33633,8 @@ var init_mcp = __esm(() => {
|
|
|
32838
33633
|
init_code_tools();
|
|
32839
33634
|
init_machines2();
|
|
32840
33635
|
init_agents2();
|
|
33636
|
+
init_templates2();
|
|
33637
|
+
init_environment_snapshots2();
|
|
32841
33638
|
init_package_version();
|
|
32842
33639
|
init_token_utils();
|
|
32843
33640
|
if (hasVersionFlag()) {
|
|
@@ -32868,6 +33665,8 @@ var init_mcp = __esm(() => {
|
|
|
32868
33665
|
registerTaskRelTools(server, toolContext);
|
|
32869
33666
|
registerCodeTools(server, toolContext);
|
|
32870
33667
|
registerAgentTools(server, { ...toolContext, agentFocusMap });
|
|
33668
|
+
registerTemplateTools(server, toolContext);
|
|
33669
|
+
registerEnvironmentSnapshotTools(server, toolContext);
|
|
32871
33670
|
registerMachineTools(server, { shouldRegisterTool, formatError });
|
|
32872
33671
|
registerDispatchTools(server, { shouldRegisterTool, resolveId, formatError });
|
|
32873
33672
|
main().catch(async (err) => {
|
|
@@ -32887,45 +33686,45 @@ __export(exports_mcp_hooks_commands, {
|
|
|
32887
33686
|
});
|
|
32888
33687
|
import chalk8 from "chalk";
|
|
32889
33688
|
import { execSync as execSync3 } from "child_process";
|
|
32890
|
-
import { existsSync as
|
|
32891
|
-
import { dirname as
|
|
33689
|
+
import { existsSync as existsSync14, readFileSync as readFileSync10, writeFileSync as writeFileSync6, mkdirSync as mkdirSync8, chmodSync as chmodSync2 } from "fs";
|
|
33690
|
+
import { dirname as dirname11, join as join15 } from "path";
|
|
32892
33691
|
function getMcpBinaryPath() {
|
|
32893
33692
|
try {
|
|
32894
33693
|
const p = execSync3("which todos-mcp", { encoding: "utf-8" }).trim();
|
|
32895
33694
|
if (p)
|
|
32896
33695
|
return p;
|
|
32897
33696
|
} catch {}
|
|
32898
|
-
const bunBin =
|
|
32899
|
-
if (
|
|
33697
|
+
const bunBin = join15(HOME2, ".bun", "bin", "todos-mcp");
|
|
33698
|
+
if (existsSync14(bunBin))
|
|
32900
33699
|
return bunBin;
|
|
32901
33700
|
return "todos-mcp";
|
|
32902
33701
|
}
|
|
32903
33702
|
function readJsonFile2(path) {
|
|
32904
|
-
if (!
|
|
33703
|
+
if (!existsSync14(path))
|
|
32905
33704
|
return {};
|
|
32906
33705
|
try {
|
|
32907
|
-
return JSON.parse(
|
|
33706
|
+
return JSON.parse(readFileSync10(path, "utf-8"));
|
|
32908
33707
|
} catch {
|
|
32909
33708
|
return {};
|
|
32910
33709
|
}
|
|
32911
33710
|
}
|
|
32912
33711
|
function writeJsonFile2(path, data) {
|
|
32913
|
-
const dir =
|
|
32914
|
-
if (!
|
|
32915
|
-
|
|
32916
|
-
|
|
33712
|
+
const dir = dirname11(path);
|
|
33713
|
+
if (!existsSync14(dir))
|
|
33714
|
+
mkdirSync8(dir, { recursive: true });
|
|
33715
|
+
writeFileSync6(path, JSON.stringify(data, null, 2) + `
|
|
32917
33716
|
`);
|
|
32918
33717
|
}
|
|
32919
33718
|
function readTomlFile(path) {
|
|
32920
|
-
if (!
|
|
33719
|
+
if (!existsSync14(path))
|
|
32921
33720
|
return "";
|
|
32922
|
-
return
|
|
33721
|
+
return readFileSync10(path, "utf-8");
|
|
32923
33722
|
}
|
|
32924
33723
|
function writeTomlFile(path, content) {
|
|
32925
|
-
const dir =
|
|
32926
|
-
if (!
|
|
32927
|
-
|
|
32928
|
-
|
|
33724
|
+
const dir = dirname11(path);
|
|
33725
|
+
if (!existsSync14(dir))
|
|
33726
|
+
mkdirSync8(dir, { recursive: true });
|
|
33727
|
+
writeFileSync6(path, content);
|
|
32929
33728
|
}
|
|
32930
33729
|
function removeTomlBlock(content, blockName) {
|
|
32931
33730
|
const lines = content.split(`
|
|
@@ -32989,7 +33788,7 @@ function unregisterClaude(_global) {
|
|
|
32989
33788
|
}
|
|
32990
33789
|
}
|
|
32991
33790
|
function registerCodex(binPath) {
|
|
32992
|
-
const configPath =
|
|
33791
|
+
const configPath = join15(HOME2, ".codex", "config.toml");
|
|
32993
33792
|
let content = readTomlFile(configPath);
|
|
32994
33793
|
content = removeTomlBlock(content, "mcp_servers.todos");
|
|
32995
33794
|
const block = `
|
|
@@ -33003,7 +33802,7 @@ args = []
|
|
|
33003
33802
|
console.log(chalk8.green(`Codex CLI: registered in ${configPath}`));
|
|
33004
33803
|
}
|
|
33005
33804
|
function unregisterCodex() {
|
|
33006
|
-
const configPath =
|
|
33805
|
+
const configPath = join15(HOME2, ".codex", "config.toml");
|
|
33007
33806
|
let content = readTomlFile(configPath);
|
|
33008
33807
|
if (!content.includes("[mcp_servers.todos]")) {
|
|
33009
33808
|
console.log(chalk8.dim(`Codex CLI: todos not found in ${configPath}`));
|
|
@@ -33015,7 +33814,7 @@ function unregisterCodex() {
|
|
|
33015
33814
|
console.log(chalk8.green(`Codex CLI: unregistered from ${configPath}`));
|
|
33016
33815
|
}
|
|
33017
33816
|
function registerGemini(binPath) {
|
|
33018
|
-
const configPath =
|
|
33817
|
+
const configPath = join15(HOME2, ".gemini", "settings.json");
|
|
33019
33818
|
const config = readJsonFile2(configPath);
|
|
33020
33819
|
if (!config["mcpServers"]) {
|
|
33021
33820
|
config["mcpServers"] = {};
|
|
@@ -33029,7 +33828,7 @@ function registerGemini(binPath) {
|
|
|
33029
33828
|
console.log(chalk8.green(`Gemini CLI: registered in ${configPath}`));
|
|
33030
33829
|
}
|
|
33031
33830
|
function unregisterGemini() {
|
|
33032
|
-
const configPath =
|
|
33831
|
+
const configPath = join15(HOME2, ".gemini", "settings.json");
|
|
33033
33832
|
const config = readJsonFile2(configPath);
|
|
33034
33833
|
const servers = config["mcpServers"];
|
|
33035
33834
|
if (!servers || !("todos" in servers)) {
|
|
@@ -33086,9 +33885,9 @@ function registerMcpHooksCommands(program2) {
|
|
|
33086
33885
|
if (p)
|
|
33087
33886
|
todosBin = p;
|
|
33088
33887
|
} catch {}
|
|
33089
|
-
const hooksDir =
|
|
33090
|
-
if (!
|
|
33091
|
-
|
|
33888
|
+
const hooksDir = join15(process.cwd(), ".claude", "hooks");
|
|
33889
|
+
if (!existsSync14(hooksDir))
|
|
33890
|
+
mkdirSync8(hooksDir, { recursive: true });
|
|
33092
33891
|
const hookScript = `#!/usr/bin/env bash
|
|
33093
33892
|
# Auto-generated by: todos hooks install
|
|
33094
33893
|
# Syncs todos with Claude Code task list on tool use events.
|
|
@@ -33112,11 +33911,11 @@ esac
|
|
|
33112
33911
|
|
|
33113
33912
|
exit 0
|
|
33114
33913
|
`;
|
|
33115
|
-
const hookPath =
|
|
33116
|
-
|
|
33914
|
+
const hookPath = join15(hooksDir, "todos-sync.sh");
|
|
33915
|
+
writeFileSync6(hookPath, hookScript);
|
|
33117
33916
|
execSync3(`chmod +x "${hookPath}"`);
|
|
33118
33917
|
console.log(chalk8.green(`Hook script created: ${hookPath}`));
|
|
33119
|
-
const settingsPath =
|
|
33918
|
+
const settingsPath = join15(process.cwd(), ".claude", "settings.json");
|
|
33120
33919
|
const settings = readJsonFile2(settingsPath);
|
|
33121
33920
|
if (!settings["hooks"]) {
|
|
33122
33921
|
settings["hooks"] = {};
|
|
@@ -33859,18 +34658,18 @@ Artifacts:`));
|
|
|
33859
34658
|
const gitDir = execSync3("git rev-parse --git-dir", { encoding: "utf-8" }).trim();
|
|
33860
34659
|
const hookPath = `${gitDir}/hooks/post-commit`;
|
|
33861
34660
|
const marker = "# todos-auto-link";
|
|
33862
|
-
if (
|
|
33863
|
-
const existing =
|
|
34661
|
+
if (existsSync14(hookPath)) {
|
|
34662
|
+
const existing = readFileSync10(hookPath, "utf-8");
|
|
33864
34663
|
if (existing.includes(marker)) {
|
|
33865
34664
|
console.log(chalk8.yellow("Hook already installed."));
|
|
33866
34665
|
return;
|
|
33867
34666
|
}
|
|
33868
|
-
|
|
34667
|
+
writeFileSync6(hookPath, existing + `
|
|
33869
34668
|
${marker}
|
|
33870
34669
|
$(dirname "$0")/../../scripts/post-commit-hook.sh
|
|
33871
34670
|
`);
|
|
33872
34671
|
} else {
|
|
33873
|
-
|
|
34672
|
+
writeFileSync6(hookPath, `#!/usr/bin/env bash
|
|
33874
34673
|
${marker}
|
|
33875
34674
|
$(dirname "$0")/../../scripts/post-commit-hook.sh
|
|
33876
34675
|
`);
|
|
@@ -33887,11 +34686,11 @@ $(dirname "$0")/../../scripts/post-commit-hook.sh
|
|
|
33887
34686
|
const gitDir = execSync3("git rev-parse --git-dir", { encoding: "utf-8" }).trim();
|
|
33888
34687
|
const hookPath = `${gitDir}/hooks/post-commit`;
|
|
33889
34688
|
const marker = "# todos-auto-link";
|
|
33890
|
-
if (!
|
|
34689
|
+
if (!existsSync14(hookPath)) {
|
|
33891
34690
|
console.log(chalk8.dim("No post-commit hook found."));
|
|
33892
34691
|
return;
|
|
33893
34692
|
}
|
|
33894
|
-
const content =
|
|
34693
|
+
const content = readFileSync10(hookPath, "utf-8");
|
|
33895
34694
|
if (!content.includes(marker)) {
|
|
33896
34695
|
console.log(chalk8.dim("Hook not managed by todos."));
|
|
33897
34696
|
return;
|
|
@@ -33902,7 +34701,7 @@ $(dirname "$0")/../../scripts/post-commit-hook.sh
|
|
|
33902
34701
|
if (cleaned === "#!/usr/bin/env bash" || cleaned === "") {
|
|
33903
34702
|
(await import("fs")).unlinkSync(hookPath);
|
|
33904
34703
|
} else {
|
|
33905
|
-
|
|
34704
|
+
writeFileSync6(hookPath, cleaned + `
|
|
33906
34705
|
`);
|
|
33907
34706
|
}
|
|
33908
34707
|
console.log(chalk8.green("Post-commit hook removed."));
|
|
@@ -34067,9 +34866,9 @@ __export(exports_machines2, {
|
|
|
34067
34866
|
});
|
|
34068
34867
|
import chalk10 from "chalk";
|
|
34069
34868
|
import { execSync as execSync4 } from "child_process";
|
|
34070
|
-
import { writeFileSync as
|
|
34071
|
-
import { tmpdir as
|
|
34072
|
-
import { join as
|
|
34869
|
+
import { writeFileSync as writeFileSync7 } from "fs";
|
|
34870
|
+
import { tmpdir as tmpdir3 } from "os";
|
|
34871
|
+
import { join as join16 } from "path";
|
|
34073
34872
|
function getOrCreateLocalMachineName() {
|
|
34074
34873
|
return process.env["TODOS_MACHINE_NAME"] || __require("os").hostname() || "unknown";
|
|
34075
34874
|
}
|
|
@@ -34263,8 +35062,8 @@ Warning: No primary machine set.`));
|
|
|
34263
35062
|
if (opts.push) {
|
|
34264
35063
|
try {
|
|
34265
35064
|
const localTasks = listTasks3();
|
|
34266
|
-
const tmpFile =
|
|
34267
|
-
|
|
35065
|
+
const tmpFile = join16(tmpdir3(), `todos-export-${uuid()}.json`);
|
|
35066
|
+
writeFileSync7(tmpFile, JSON.stringify(localTasks, null, 2));
|
|
34268
35067
|
execSync4(`scp ${tmpFile} ${ssh}:/tmp/todos-import.json`, { timeout: 15000 });
|
|
34269
35068
|
const importCmd = `ssh ${ssh} 'node -e "const fs=require(\\'fs\\');const tasks=JSON.parse(fs.readFileSync(\\'/tmp/todos-import.json\\',\\'utf-8\\'));console.log(JSON.stringify(tasks.length))"'`;
|
|
34270
35069
|
const count = execSync4(importCmd, { encoding: "utf-8", timeout: 1e4 }).trim();
|
|
@@ -34413,6 +35212,84 @@ var init_api_key_commands = __esm(() => {
|
|
|
34413
35212
|
init_helpers();
|
|
34414
35213
|
});
|
|
34415
35214
|
|
|
35215
|
+
// src/cli/commands/environment-snapshots.ts
|
|
35216
|
+
var exports_environment_snapshots2 = {};
|
|
35217
|
+
__export(exports_environment_snapshots2, {
|
|
35218
|
+
registerEnvironmentSnapshotCommands: () => registerEnvironmentSnapshotCommands
|
|
35219
|
+
});
|
|
35220
|
+
import chalk12 from "chalk";
|
|
35221
|
+
function printJson(value) {
|
|
35222
|
+
console.log(JSON.stringify(value, null, 2));
|
|
35223
|
+
}
|
|
35224
|
+
function registerEnvironmentSnapshotCommands(program2) {
|
|
35225
|
+
const envCmd = program2.command("env-snapshot").alias("environment-snapshot").description("Capture and compare local reproducible environment snapshots");
|
|
35226
|
+
envCmd.command("capture").description("Capture runtime, package-manager, git, config hash, and redacted environment metadata").option("--root <path>", "Project root to inspect").option("--task <id>", "Attach snapshot evidence to a task").option("--run <id>", "Attach snapshot artifact to a task run").option("--agent <name>", "Agent name for attached evidence").option("--command <command>", "Command or verification step this snapshot explains").option("--output <path>", "Write snapshot JSON to a specific path").option("--include-env-values", "Include nonsecret environment values; secret-like keys are still redacted").action((opts) => {
|
|
35227
|
+
const globalOpts = program2.opts();
|
|
35228
|
+
try {
|
|
35229
|
+
const result = recordEnvironmentSnapshot({
|
|
35230
|
+
root: opts.root,
|
|
35231
|
+
task_id: opts.task,
|
|
35232
|
+
run_id: opts.run,
|
|
35233
|
+
agent_id: opts.agent || globalOpts.agent,
|
|
35234
|
+
command: opts.command,
|
|
35235
|
+
output_path: opts.output,
|
|
35236
|
+
include_env_values: Boolean(opts.includeEnvValues)
|
|
35237
|
+
});
|
|
35238
|
+
if (globalOpts.json) {
|
|
35239
|
+
printJson(result);
|
|
35240
|
+
return;
|
|
35241
|
+
}
|
|
35242
|
+
console.log(chalk12.green("Captured") + ` ${result.snapshot.id}`);
|
|
35243
|
+
console.log(`path: ${result.output_path}`);
|
|
35244
|
+
console.log(`root: ${result.snapshot.root}`);
|
|
35245
|
+
console.log(`git: ${result.snapshot.git.commit || "none"}${result.snapshot.git.is_dirty ? " dirty" : ""}`);
|
|
35246
|
+
console.log(`runtime: bun ${result.snapshot.runtime.bun || "unknown"} / node ${result.snapshot.runtime.node}`);
|
|
35247
|
+
if (result.run_artifact_id)
|
|
35248
|
+
console.log(`run artifact: ${result.run_artifact_id}`);
|
|
35249
|
+
if (result.task_verification_id)
|
|
35250
|
+
console.log(`task verification: ${result.task_verification_id}`);
|
|
35251
|
+
for (const warning of result.snapshot.warnings)
|
|
35252
|
+
console.log(chalk12.yellow(`warning: ${warning}`));
|
|
35253
|
+
} catch (error) {
|
|
35254
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
35255
|
+
if (globalOpts.json)
|
|
35256
|
+
printJson({ error: message });
|
|
35257
|
+
else
|
|
35258
|
+
console.error(chalk12.red(`Error: ${message}`));
|
|
35259
|
+
process.exit(1);
|
|
35260
|
+
}
|
|
35261
|
+
});
|
|
35262
|
+
envCmd.command("compare").description("Compare two environment snapshot JSON files").argument("<left>", "Left snapshot JSON path").argument("<right>", "Right snapshot JSON path").action((left, right) => {
|
|
35263
|
+
const globalOpts = program2.opts();
|
|
35264
|
+
try {
|
|
35265
|
+
const comparison = compareEnvironmentSnapshotFiles(left, right);
|
|
35266
|
+
if (globalOpts.json) {
|
|
35267
|
+
printJson(comparison);
|
|
35268
|
+
return;
|
|
35269
|
+
}
|
|
35270
|
+
console.log(`left: ${comparison.left_id}`);
|
|
35271
|
+
console.log(`right: ${comparison.right_id}`);
|
|
35272
|
+
console.log(`same root: ${comparison.same_root}`);
|
|
35273
|
+
console.log(`same machine: ${comparison.same_machine}`);
|
|
35274
|
+
console.log(`same runtime: ${comparison.same_runtime}`);
|
|
35275
|
+
console.log(`same git commit: ${comparison.same_git_commit}`);
|
|
35276
|
+
console.log(`dirty state changed: ${comparison.dirty_state_changed}`);
|
|
35277
|
+
const changed = comparison.changed_config_hashes.length + comparison.changed_lockfiles.length + comparison.changed_manifests.length;
|
|
35278
|
+
console.log(`changed files: ${changed}`);
|
|
35279
|
+
} catch (error) {
|
|
35280
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
35281
|
+
if (globalOpts.json)
|
|
35282
|
+
printJson({ error: message });
|
|
35283
|
+
else
|
|
35284
|
+
console.error(chalk12.red(`Error: ${message}`));
|
|
35285
|
+
process.exit(1);
|
|
35286
|
+
}
|
|
35287
|
+
});
|
|
35288
|
+
}
|
|
35289
|
+
var init_environment_snapshots3 = __esm(() => {
|
|
35290
|
+
init_environment_snapshots();
|
|
35291
|
+
});
|
|
35292
|
+
|
|
34416
35293
|
// src/cli/index.tsx
|
|
34417
35294
|
init_esm();
|
|
34418
35295
|
init_package_version();
|
|
@@ -34428,7 +35305,8 @@ var [
|
|
|
34428
35305
|
{ registerMcpHooksCommands: registerMcpHooksCommands2 },
|
|
34429
35306
|
{ registerDispatchCommands: registerDispatchCommands2 },
|
|
34430
35307
|
{ registerMachineCommands: registerMachineCommands2 },
|
|
34431
|
-
{ registerApiKeyCommands: registerApiKeyCommands2 }
|
|
35308
|
+
{ registerApiKeyCommands: registerApiKeyCommands2 },
|
|
35309
|
+
{ registerEnvironmentSnapshotCommands: registerEnvironmentSnapshotCommands2 }
|
|
34432
35310
|
] = await Promise.all([
|
|
34433
35311
|
Promise.resolve().then(() => (init_task_commands(), exports_task_commands)),
|
|
34434
35312
|
Promise.resolve().then(() => (init_plan_template_commands(), exports_plan_template_commands)),
|
|
@@ -34439,7 +35317,8 @@ var [
|
|
|
34439
35317
|
Promise.resolve().then(() => (init_mcp_hooks_commands(), exports_mcp_hooks_commands)),
|
|
34440
35318
|
Promise.resolve().then(() => (init_dispatch3(), exports_dispatch2)),
|
|
34441
35319
|
Promise.resolve().then(() => (init_machines3(), exports_machines2)),
|
|
34442
|
-
Promise.resolve().then(() => (init_api_key_commands(), exports_api_key_commands))
|
|
35320
|
+
Promise.resolve().then(() => (init_api_key_commands(), exports_api_key_commands)),
|
|
35321
|
+
Promise.resolve().then(() => (init_environment_snapshots3(), exports_environment_snapshots2))
|
|
34443
35322
|
]);
|
|
34444
35323
|
registerTaskCommands2(program2);
|
|
34445
35324
|
registerPlanTemplateCommands2(program2);
|
|
@@ -34451,4 +35330,5 @@ registerMcpHooksCommands2(program2);
|
|
|
34451
35330
|
registerDispatchCommands2(program2);
|
|
34452
35331
|
registerMachineCommands2(program2);
|
|
34453
35332
|
registerApiKeyCommands2(program2);
|
|
35333
|
+
registerEnvironmentSnapshotCommands2(program2);
|
|
34454
35334
|
program2.parse();
|