esedre 1.1.0 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +14 -0
- package/README.md +3 -3
- package/dist/esedre.mjs +299 -239
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,20 @@ All notable changes to Esedre are documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [1.2.0] - 2026-10-09
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
- **MCP Ticket Listing Implementation Plan Hydration (`includePlan` and `hasPlan`) (Ticket #58)**:
|
|
12
|
+
- Extended `esedre_list_tickets` MCP tool to return `hasPlan: boolean` on all listed tickets, indicating whether an implementation plan exists on disk.
|
|
13
|
+
- Added optional `includePlan: boolean` parameter to `esedre_list_tickets` to hydrate full `planMarkdown` directly in ticket listings, eliminating redundant sequential `esedre_get_plan` tool calls when agents review multiple active tickets.
|
|
14
|
+
- Added unit test suite in `tests/mcpServer.test.ts` verifying `hasPlan` and `includePlan` behavior across multiple tickets.
|
|
15
|
+
- **Comment File Input Option for CLI & MCP Tool (Ticket #62)**:
|
|
16
|
+
- Added `--file <path>` option to `ese comment` CLI command, reading verification notes directly from UTF-8 markdown or text files on disk.
|
|
17
|
+
- Bypasses Windows `cmd.exe` `%VAR%` parameter expansion and stripping, preserving percent signs (`100%`) and literal characters verbatim.
|
|
18
|
+
- Added priority precedence for `--file` over inline `--text` and positional arguments, with existence, regular file, and non-empty content validation.
|
|
19
|
+
- Added parity `file?: string` property to `esedre_add_comment` MCP tool, with automatic local file resolution and relaxed `required: ['ticketId']` schema constraint.
|
|
20
|
+
- Added comprehensive automated test suite in `tests/cliComment.test.ts` covering error handling, priority precedence, percent preservation, and MCP execution.
|
|
21
|
+
|
|
8
22
|
## [1.1.0] - 2026-10-02
|
|
9
23
|
|
|
10
24
|
### Added
|
package/README.md
CHANGED
|
@@ -122,7 +122,7 @@ Core day-to-day workflow commands for scoping, viewing, planning, and verifying
|
|
|
122
122
|
| `link` | `ese link <sourceId> <relation> <targetId> [--author "..."]` | Establish a bi-directional link between two tickets. Relations: `relates-to`, `blocks`, `parent-of`, `duplicates`. |
|
|
123
123
|
| `unlink` | `ese unlink <sourceId> <targetId> [--relation <relation>]` | Remove a bi-directional link between two tickets. |
|
|
124
124
|
| `milestone` | `ese milestone [list\|get\|create\|update\|delete] [args...]` | Manage project milestones and umbrella feature flags with deliverables tracking. |
|
|
125
|
-
| `comment` | `ese comment <id> ["<text>"] [--text "..."] [--author "..."]` | Append a developer or LLM agent note to ticket history. |
|
|
125
|
+
| `comment` | `ese comment <id> ["<text>"] [--file <path>] [--text "..."] [--author "..."]` | Append a developer or LLM agent note to ticket history. |
|
|
126
126
|
| `snapshot`, `refresh` | `ese snapshot [--project <code>] [--json]` | Generate or refresh projection `.esedre/snapshot.json` for zero-latency agent context. |
|
|
127
127
|
| `projects` | `ese projects [--json]` | List registered projects within authorized scope. |
|
|
128
128
|
|
|
@@ -199,7 +199,7 @@ To connect Esedre to **Google Antigravity**, **Claude Code**, **Cursor**, or any
|
|
|
199
199
|
```
|
|
200
200
|
|
|
201
201
|
### Registered Tools
|
|
202
|
-
- `esedre_list_tickets`: List tickets with optional project, status, type, priority, milestone, isBlocked, linkedTo, or
|
|
202
|
+
- `esedre_list_tickets`: List tickets with optional project, status, type, priority, milestone, isBlocked, linkedTo, search, or includePlan filter.
|
|
203
203
|
- `esedre_get_ticket`: Retrieve full specification, summary, comments, revision, links, blocker status, and content hash (`sha1`).
|
|
204
204
|
- `esedre_get_plan` & `esedre_save_plan`: Inspect and update implementation plans with optimistic concurrency (`lastHash`).
|
|
205
205
|
- `esedre_create_ticket`: Mint new roadmap tickets with project code validation (up to 8 chars), optional priority, optional milestone, and specification detail markdown.
|
|
@@ -210,7 +210,7 @@ To connect Esedre to **Google Antigravity**, **Claude Code**, **Cursor**, or any
|
|
|
210
210
|
- `esedre_get_milestone`: Retrieve milestone specifications, umbrella feature flag, and member tickets.
|
|
211
211
|
- `esedre_create_milestone`: Create a project milestone with optional umbrella feature flag and target date.
|
|
212
212
|
- `esedre_update_milestone`: Update milestone status, title, description, or umbrella feature flag.
|
|
213
|
-
- `esedre_add_comment`: Append developer or LLM agent verification notes.
|
|
213
|
+
- `esedre_add_comment`: Append developer or LLM agent verification notes with optional file path or inline text.
|
|
214
214
|
|
|
215
215
|
### Cross-Project Linked Issues & Dependencies
|
|
216
216
|
|
package/dist/esedre.mjs
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
// bin/esedre.ts
|
|
4
|
-
import
|
|
5
|
-
import
|
|
4
|
+
import fs11 from "node:fs";
|
|
5
|
+
import path11 from "node:path";
|
|
6
6
|
|
|
7
7
|
// src/storage/filesystem.ts
|
|
8
8
|
import fs3 from "node:fs";
|
|
@@ -10,7 +10,7 @@ import path3 from "node:path";
|
|
|
10
10
|
import os2 from "node:os";
|
|
11
11
|
|
|
12
12
|
// src/types.ts
|
|
13
|
-
var CURRENT_ESEDRE_VERSION = "1.
|
|
13
|
+
var CURRENT_ESEDRE_VERSION = "1.2.0";
|
|
14
14
|
var INVERSE_RELATIONS = {
|
|
15
15
|
"relates-to": "relates-to",
|
|
16
16
|
"blocks": "blocked-by",
|
|
@@ -3579,6 +3579,8 @@ var SecurityFilter = class {
|
|
|
3579
3579
|
};
|
|
3580
3580
|
|
|
3581
3581
|
// src/mcp/server.ts
|
|
3582
|
+
import fs4 from "node:fs";
|
|
3583
|
+
import path4 from "node:path";
|
|
3582
3584
|
import readline from "node:readline";
|
|
3583
3585
|
var EsedreMcpServer = class {
|
|
3584
3586
|
storage;
|
|
@@ -3800,7 +3802,8 @@ Status: ${ticket.meta.status}`;
|
|
|
3800
3802
|
milestone: { type: "string", description: "Filter tickets by milestone name or ID" },
|
|
3801
3803
|
search: { type: "string", description: "Search keywords in title or ID" },
|
|
3802
3804
|
isBlocked: { type: "boolean", description: "Filter tickets blocked by uncompleted tickets" },
|
|
3803
|
-
linkedTo: { type: "string", description: "Filter tickets linked to a specific ticket ID or key" }
|
|
3805
|
+
linkedTo: { type: "string", description: "Filter tickets linked to a specific ticket ID or key" },
|
|
3806
|
+
includePlan: { type: "boolean", description: "When true, includes implementation plan markdown (planMarkdown) for each ticket" }
|
|
3804
3807
|
}
|
|
3805
3808
|
}
|
|
3806
3809
|
},
|
|
@@ -3892,9 +3895,10 @@ Status: ${ticket.meta.status}`;
|
|
|
3892
3895
|
properties: {
|
|
3893
3896
|
ticketId: { type: ["integer", "string"], description: "Numeric ticket ID or compound key" },
|
|
3894
3897
|
text: { type: "string", description: "Comment message" },
|
|
3898
|
+
file: { type: "string", description: "Path to local file containing comment message markdown" },
|
|
3895
3899
|
author: { type: "string", description: "Author name (e.g. Antigravity, Developer)" }
|
|
3896
3900
|
},
|
|
3897
|
-
required: ["ticketId"
|
|
3901
|
+
required: ["ticketId"]
|
|
3898
3902
|
}
|
|
3899
3903
|
},
|
|
3900
3904
|
{
|
|
@@ -3991,6 +3995,7 @@ Status: ${ticket.meta.status}`;
|
|
|
3991
3995
|
switch (canonical) {
|
|
3992
3996
|
case "list_tickets": {
|
|
3993
3997
|
const type = args.type || args.category;
|
|
3998
|
+
const includePlan = Boolean(args.includePlan);
|
|
3994
3999
|
const tickets = await this.storage.listTickets({
|
|
3995
4000
|
project: args.project,
|
|
3996
4001
|
status: args.status,
|
|
@@ -4002,23 +4007,39 @@ Status: ${ticket.meta.status}`;
|
|
|
4002
4007
|
isBlocked: args.isBlocked !== void 0 ? Boolean(args.isBlocked) : void 0,
|
|
4003
4008
|
linkedTo: args.linkedTo
|
|
4004
4009
|
});
|
|
4005
|
-
|
|
4006
|
-
|
|
4007
|
-
|
|
4008
|
-
|
|
4009
|
-
|
|
4010
|
-
|
|
4011
|
-
|
|
4012
|
-
|
|
4013
|
-
|
|
4014
|
-
|
|
4015
|
-
|
|
4016
|
-
|
|
4017
|
-
|
|
4018
|
-
|
|
4019
|
-
|
|
4020
|
-
|
|
4021
|
-
|
|
4010
|
+
const results = await Promise.all(
|
|
4011
|
+
tickets.map(async (t) => {
|
|
4012
|
+
let planMarkdown = t.planMarkdown;
|
|
4013
|
+
if (includePlan && planMarkdown === void 0) {
|
|
4014
|
+
const lookupKey = t.projectDescriptor?.code ? `${t.projectDescriptor.code}-${t.meta.id}` : t.meta.id;
|
|
4015
|
+
planMarkdown = await this.storage.getPlan(lookupKey) ?? null;
|
|
4016
|
+
}
|
|
4017
|
+
const hasPlan = Boolean(
|
|
4018
|
+
t.planMarkdown && t.planMarkdown.trim().length > 0 || planMarkdown && planMarkdown.trim().length > 0
|
|
4019
|
+
);
|
|
4020
|
+
return {
|
|
4021
|
+
id: t.meta.id,
|
|
4022
|
+
title: t.meta.title,
|
|
4023
|
+
type: t.meta.type || t.meta.category,
|
|
4024
|
+
category: t.meta.type || t.meta.category,
|
|
4025
|
+
status: t.meta.status,
|
|
4026
|
+
priority: t.meta.priority,
|
|
4027
|
+
complexity: t.meta.complexity,
|
|
4028
|
+
effort: t.meta.estimatedEffort,
|
|
4029
|
+
milestone: t.meta.milestone,
|
|
4030
|
+
featureFlag: t.meta.featureFlag,
|
|
4031
|
+
inheritedFeatureFlag: t.meta.inheritedFeatureFlag,
|
|
4032
|
+
project: t.projectDescriptor?.code || t.meta.project || "UNASSIGNED",
|
|
4033
|
+
summary: t.detail?.summary,
|
|
4034
|
+
hasPlan,
|
|
4035
|
+
...includePlan ? { planMarkdown: planMarkdown || null } : {},
|
|
4036
|
+
sha1: t.sha1 || t.meta.sha1,
|
|
4037
|
+
isBlocked: t.isBlocked,
|
|
4038
|
+
links: t.links
|
|
4039
|
+
};
|
|
4040
|
+
})
|
|
4041
|
+
);
|
|
4042
|
+
return results;
|
|
4022
4043
|
}
|
|
4023
4044
|
case "get_ticket": {
|
|
4024
4045
|
const id = this.parseTicketId(args.ticketId);
|
|
@@ -4077,8 +4098,26 @@ Status: ${ticket.meta.status}`;
|
|
|
4077
4098
|
}
|
|
4078
4099
|
case "add_comment": {
|
|
4079
4100
|
const id = this.parseTicketId(args.ticketId);
|
|
4101
|
+
let text = args.text;
|
|
4102
|
+
if (!text && args.file) {
|
|
4103
|
+
const resolvedPath = path4.resolve(process.cwd(), args.file);
|
|
4104
|
+
if (!fs4.existsSync(resolvedPath)) {
|
|
4105
|
+
throw new Error(`File not found: ${args.file}`);
|
|
4106
|
+
}
|
|
4107
|
+
if (!fs4.statSync(resolvedPath).isFile()) {
|
|
4108
|
+
throw new Error(`Path is not a file: ${args.file}`);
|
|
4109
|
+
}
|
|
4110
|
+
const fileContent = fs4.readFileSync(resolvedPath, "utf-8");
|
|
4111
|
+
if (!fileContent.trim()) {
|
|
4112
|
+
throw new Error(`Comment file is empty: ${args.file}`);
|
|
4113
|
+
}
|
|
4114
|
+
text = fileContent;
|
|
4115
|
+
}
|
|
4116
|
+
if (!text || !text.trim()) {
|
|
4117
|
+
throw new Error("Comment text or file is required");
|
|
4118
|
+
}
|
|
4080
4119
|
const comment = await this.storage.addComment(id, {
|
|
4081
|
-
text
|
|
4120
|
+
text,
|
|
4082
4121
|
author: args.author || "Agent"
|
|
4083
4122
|
});
|
|
4084
4123
|
return comment;
|
|
@@ -4147,19 +4186,19 @@ Status: ${ticket.meta.status}`;
|
|
|
4147
4186
|
};
|
|
4148
4187
|
|
|
4149
4188
|
// src/utils/manifestDetector.ts
|
|
4150
|
-
import
|
|
4151
|
-
import
|
|
4189
|
+
import fs5 from "node:fs";
|
|
4190
|
+
import path5 from "node:path";
|
|
4152
4191
|
function sanitizeCode(input) {
|
|
4153
4192
|
const clean = input.replace(/[^a-zA-Z0-9]/g, "");
|
|
4154
4193
|
return clean.slice(0, MAX_PROJECT_CODE_LENGTH) || "APP";
|
|
4155
4194
|
}
|
|
4156
4195
|
function detectProjectManifest(targetDir) {
|
|
4157
|
-
const dirBase =
|
|
4196
|
+
const dirBase = path5.basename(targetDir);
|
|
4158
4197
|
for (const settingsFile of ["settings.gradle.kts", "settings.gradle"]) {
|
|
4159
|
-
const fullPath =
|
|
4160
|
-
if (
|
|
4198
|
+
const fullPath = path5.join(targetDir, settingsFile);
|
|
4199
|
+
if (fs5.existsSync(fullPath)) {
|
|
4161
4200
|
try {
|
|
4162
|
-
const content =
|
|
4201
|
+
const content = fs5.readFileSync(fullPath, "utf-8");
|
|
4163
4202
|
const match = content.match(/rootProject\.name\s*=\s*["']([^"']+)["']/);
|
|
4164
4203
|
if (match && match[1].trim()) {
|
|
4165
4204
|
const rawName = match[1].trim();
|
|
@@ -4175,15 +4214,15 @@ function detectProjectManifest(targetDir) {
|
|
|
4175
4214
|
}
|
|
4176
4215
|
}
|
|
4177
4216
|
for (const buildFile of [
|
|
4178
|
-
|
|
4217
|
+
path5.join("app", "build.gradle.kts"),
|
|
4179
4218
|
"build.gradle.kts",
|
|
4180
|
-
|
|
4219
|
+
path5.join("app", "build.gradle"),
|
|
4181
4220
|
"build.gradle"
|
|
4182
4221
|
]) {
|
|
4183
|
-
const fullPath =
|
|
4184
|
-
if (
|
|
4222
|
+
const fullPath = path5.join(targetDir, buildFile);
|
|
4223
|
+
if (fs5.existsSync(fullPath)) {
|
|
4185
4224
|
try {
|
|
4186
|
-
const content =
|
|
4225
|
+
const content = fs5.readFileSync(fullPath, "utf-8");
|
|
4187
4226
|
const match = content.match(/(?:namespace|applicationId)\s*=\s*["']([^"']+)["']/);
|
|
4188
4227
|
if (match && match[1].trim()) {
|
|
4189
4228
|
const ns = match[1].trim();
|
|
@@ -4200,10 +4239,10 @@ function detectProjectManifest(targetDir) {
|
|
|
4200
4239
|
}
|
|
4201
4240
|
}
|
|
4202
4241
|
}
|
|
4203
|
-
const pomPath =
|
|
4204
|
-
if (
|
|
4242
|
+
const pomPath = path5.join(targetDir, "pom.xml");
|
|
4243
|
+
if (fs5.existsSync(pomPath)) {
|
|
4205
4244
|
try {
|
|
4206
|
-
const content =
|
|
4245
|
+
const content = fs5.readFileSync(pomPath, "utf-8");
|
|
4207
4246
|
const stripped = content.replace(/<!--[\s\S]*?-->/g, "").replace(/<parent>[\s\S]*?<\/parent>/g, "");
|
|
4208
4247
|
const nameMatch = stripped.match(/<name>\s*([^<]+)\s*<\/name>/);
|
|
4209
4248
|
const artifactMatch = stripped.match(/<artifactId>\s*([^<]+)\s*<\/artifactId>/) || content.match(/<artifactId>\s*([^<]+)\s*<\/artifactId>/);
|
|
@@ -4222,10 +4261,10 @@ function detectProjectManifest(targetDir) {
|
|
|
4222
4261
|
} catch {
|
|
4223
4262
|
}
|
|
4224
4263
|
}
|
|
4225
|
-
const pkgPath =
|
|
4226
|
-
if (
|
|
4264
|
+
const pkgPath = path5.join(targetDir, "package.json");
|
|
4265
|
+
if (fs5.existsSync(pkgPath)) {
|
|
4227
4266
|
try {
|
|
4228
|
-
const pkg = JSON.parse(
|
|
4267
|
+
const pkg = JSON.parse(fs5.readFileSync(pkgPath, "utf-8"));
|
|
4229
4268
|
if (pkg.name && typeof pkg.name === "string") {
|
|
4230
4269
|
const baseName = pkg.name.replace(/^@[\w-]+\//, "");
|
|
4231
4270
|
return {
|
|
@@ -4238,10 +4277,10 @@ function detectProjectManifest(targetDir) {
|
|
|
4238
4277
|
} catch {
|
|
4239
4278
|
}
|
|
4240
4279
|
}
|
|
4241
|
-
const cargoPath =
|
|
4242
|
-
if (
|
|
4280
|
+
const cargoPath = path5.join(targetDir, "Cargo.toml");
|
|
4281
|
+
if (fs5.existsSync(cargoPath)) {
|
|
4243
4282
|
try {
|
|
4244
|
-
const content =
|
|
4283
|
+
const content = fs5.readFileSync(cargoPath, "utf-8");
|
|
4245
4284
|
const match = content.match(/\[package\][\s\S]*?name\s*=\s*["']([^"']+)["']/);
|
|
4246
4285
|
if (match && match[1].trim()) {
|
|
4247
4286
|
const rawName = match[1].trim();
|
|
@@ -4255,10 +4294,10 @@ function detectProjectManifest(targetDir) {
|
|
|
4255
4294
|
} catch {
|
|
4256
4295
|
}
|
|
4257
4296
|
}
|
|
4258
|
-
const pyprojectPath =
|
|
4259
|
-
if (
|
|
4297
|
+
const pyprojectPath = path5.join(targetDir, "pyproject.toml");
|
|
4298
|
+
if (fs5.existsSync(pyprojectPath)) {
|
|
4260
4299
|
try {
|
|
4261
|
-
const content =
|
|
4300
|
+
const content = fs5.readFileSync(pyprojectPath, "utf-8");
|
|
4262
4301
|
const match = content.match(/name\s*=\s*["']([^"']+)["']/);
|
|
4263
4302
|
if (match && match[1].trim()) {
|
|
4264
4303
|
const rawName = match[1].trim();
|
|
@@ -4272,10 +4311,10 @@ function detectProjectManifest(targetDir) {
|
|
|
4272
4311
|
} catch {
|
|
4273
4312
|
}
|
|
4274
4313
|
}
|
|
4275
|
-
const setupCfgPath =
|
|
4276
|
-
if (
|
|
4314
|
+
const setupCfgPath = path5.join(targetDir, "setup.cfg");
|
|
4315
|
+
if (fs5.existsSync(setupCfgPath)) {
|
|
4277
4316
|
try {
|
|
4278
|
-
const content =
|
|
4317
|
+
const content = fs5.readFileSync(setupCfgPath, "utf-8");
|
|
4279
4318
|
const match = content.match(/^name\s*=\s*([^\r\n]+)/m);
|
|
4280
4319
|
if (match && match[1].trim()) {
|
|
4281
4320
|
const rawName = match[1].trim();
|
|
@@ -4289,10 +4328,10 @@ function detectProjectManifest(targetDir) {
|
|
|
4289
4328
|
} catch {
|
|
4290
4329
|
}
|
|
4291
4330
|
}
|
|
4292
|
-
const setupPyPath =
|
|
4293
|
-
if (
|
|
4331
|
+
const setupPyPath = path5.join(targetDir, "setup.py");
|
|
4332
|
+
if (fs5.existsSync(setupPyPath)) {
|
|
4294
4333
|
try {
|
|
4295
|
-
const content =
|
|
4334
|
+
const content = fs5.readFileSync(setupPyPath, "utf-8");
|
|
4296
4335
|
const match = content.match(/name\s*=\s*["']([^"']+)["']/);
|
|
4297
4336
|
if (match && match[1].trim()) {
|
|
4298
4337
|
const rawName = match[1].trim();
|
|
@@ -4306,10 +4345,10 @@ function detectProjectManifest(targetDir) {
|
|
|
4306
4345
|
} catch {
|
|
4307
4346
|
}
|
|
4308
4347
|
}
|
|
4309
|
-
const goModPath =
|
|
4310
|
-
if (
|
|
4348
|
+
const goModPath = path5.join(targetDir, "go.mod");
|
|
4349
|
+
if (fs5.existsSync(goModPath)) {
|
|
4311
4350
|
try {
|
|
4312
|
-
const content =
|
|
4351
|
+
const content = fs5.readFileSync(goModPath, "utf-8");
|
|
4313
4352
|
const match = content.match(/^module\s+([^\r\n\s]+)/m);
|
|
4314
4353
|
if (match && match[1].trim()) {
|
|
4315
4354
|
const modPath = match[1].trim();
|
|
@@ -4333,48 +4372,48 @@ function detectProjectManifest(targetDir) {
|
|
|
4333
4372
|
|
|
4334
4373
|
// src/upgrade.ts
|
|
4335
4374
|
import crypto2 from "node:crypto";
|
|
4336
|
-
import
|
|
4337
|
-
import
|
|
4375
|
+
import fs7 from "node:fs";
|
|
4376
|
+
import path7 from "node:path";
|
|
4338
4377
|
|
|
4339
4378
|
// src/server/daemon.ts
|
|
4340
|
-
import
|
|
4341
|
-
import
|
|
4379
|
+
import fs6 from "node:fs";
|
|
4380
|
+
import path6 from "node:path";
|
|
4342
4381
|
import os3 from "node:os";
|
|
4343
4382
|
import http from "node:http";
|
|
4344
4383
|
import { spawn, spawnSync } from "node:child_process";
|
|
4345
4384
|
import { fileURLToPath } from "node:url";
|
|
4346
4385
|
var __filename = fileURLToPath(import.meta.url);
|
|
4347
|
-
var __dirname =
|
|
4386
|
+
var __dirname = path6.dirname(__filename);
|
|
4348
4387
|
function getGlobalEsedreDir() {
|
|
4349
4388
|
if (process.env.ESEDRE_GLOBAL_DIR) {
|
|
4350
4389
|
return process.env.ESEDRE_GLOBAL_DIR;
|
|
4351
4390
|
}
|
|
4352
|
-
return
|
|
4391
|
+
return path6.join(os3.homedir(), ".esedre");
|
|
4353
4392
|
}
|
|
4354
4393
|
function ensureGlobalEsedreStore() {
|
|
4355
4394
|
const globalDir = getGlobalEsedreDir();
|
|
4356
|
-
const runDir =
|
|
4357
|
-
const logsDir =
|
|
4395
|
+
const runDir = path6.join(globalDir, "run");
|
|
4396
|
+
const logsDir = path6.join(globalDir, "logs");
|
|
4358
4397
|
try {
|
|
4359
|
-
|
|
4360
|
-
|
|
4398
|
+
fs6.mkdirSync(runDir, { recursive: true });
|
|
4399
|
+
fs6.mkdirSync(logsDir, { recursive: true });
|
|
4361
4400
|
try {
|
|
4362
|
-
const runEntries =
|
|
4401
|
+
const runEntries = fs6.readdirSync(runDir);
|
|
4363
4402
|
for (const entry of runEntries) {
|
|
4364
4403
|
if (entry.startsWith(".probe-")) {
|
|
4365
|
-
|
|
4404
|
+
fs6.unlinkSync(path6.join(runDir, entry));
|
|
4366
4405
|
}
|
|
4367
4406
|
}
|
|
4368
4407
|
} catch {
|
|
4369
4408
|
}
|
|
4370
|
-
const testProbe =
|
|
4409
|
+
const testProbe = path6.join(runDir, `.probe-${Date.now()}-${Math.random().toString(36).slice(2, 7)}`);
|
|
4371
4410
|
try {
|
|
4372
|
-
|
|
4411
|
+
fs6.writeFileSync(testProbe, "ok", "utf-8");
|
|
4373
4412
|
return { ok: true, path: globalDir };
|
|
4374
4413
|
} finally {
|
|
4375
|
-
if (
|
|
4414
|
+
if (fs6.existsSync(testProbe)) {
|
|
4376
4415
|
try {
|
|
4377
|
-
|
|
4416
|
+
fs6.unlinkSync(testProbe);
|
|
4378
4417
|
} catch {
|
|
4379
4418
|
}
|
|
4380
4419
|
}
|
|
@@ -4389,10 +4428,10 @@ function resolveStoreDir(workspaceRoot) {
|
|
|
4389
4428
|
return globalStore.path;
|
|
4390
4429
|
}
|
|
4391
4430
|
if (workspaceRoot) {
|
|
4392
|
-
const localStore =
|
|
4431
|
+
const localStore = path6.join(workspaceRoot, ".esedre");
|
|
4393
4432
|
try {
|
|
4394
|
-
|
|
4395
|
-
|
|
4433
|
+
fs6.mkdirSync(path6.join(localStore, "run"), { recursive: true });
|
|
4434
|
+
fs6.mkdirSync(path6.join(localStore, "logs"), { recursive: true });
|
|
4396
4435
|
return localStore;
|
|
4397
4436
|
} catch {
|
|
4398
4437
|
}
|
|
@@ -4401,32 +4440,32 @@ function resolveStoreDir(workspaceRoot) {
|
|
|
4401
4440
|
}
|
|
4402
4441
|
function getDaemonStateFile(port, workspaceRoot) {
|
|
4403
4442
|
const store = resolveStoreDir(workspaceRoot);
|
|
4404
|
-
return
|
|
4443
|
+
return path6.join(store, "run", `daemon-${port}.json`);
|
|
4405
4444
|
}
|
|
4406
4445
|
function getDaemonLogFile(port, workspaceRoot) {
|
|
4407
4446
|
const store = resolveStoreDir(workspaceRoot);
|
|
4408
|
-
return
|
|
4447
|
+
return path6.join(store, "logs", `daemon-${port}.log`);
|
|
4409
4448
|
}
|
|
4410
4449
|
function getDaemonState(port, workspaceRoot) {
|
|
4411
4450
|
const stateFile = getDaemonStateFile(port, workspaceRoot);
|
|
4412
|
-
if (!
|
|
4451
|
+
if (!fs6.existsSync(stateFile)) {
|
|
4413
4452
|
return null;
|
|
4414
4453
|
}
|
|
4415
4454
|
try {
|
|
4416
|
-
const raw =
|
|
4455
|
+
const raw = fs6.readFileSync(stateFile, "utf-8").trim();
|
|
4417
4456
|
if (!raw) {
|
|
4418
|
-
|
|
4457
|
+
fs6.unlinkSync(stateFile);
|
|
4419
4458
|
return null;
|
|
4420
4459
|
}
|
|
4421
4460
|
const state = JSON.parse(raw);
|
|
4422
4461
|
if (!state || typeof state.pid !== "number" || typeof state.port !== "number") {
|
|
4423
|
-
|
|
4462
|
+
fs6.unlinkSync(stateFile);
|
|
4424
4463
|
return null;
|
|
4425
4464
|
}
|
|
4426
4465
|
return state;
|
|
4427
4466
|
} catch {
|
|
4428
4467
|
try {
|
|
4429
|
-
|
|
4468
|
+
fs6.unlinkSync(stateFile);
|
|
4430
4469
|
} catch {
|
|
4431
4470
|
}
|
|
4432
4471
|
return null;
|
|
@@ -4497,7 +4536,7 @@ async function startDaemon(options = {}) {
|
|
|
4497
4536
|
}
|
|
4498
4537
|
}
|
|
4499
4538
|
try {
|
|
4500
|
-
|
|
4539
|
+
fs6.unlinkSync(stateFile);
|
|
4501
4540
|
} catch {
|
|
4502
4541
|
}
|
|
4503
4542
|
}
|
|
@@ -4516,40 +4555,40 @@ async function startDaemon(options = {}) {
|
|
|
4516
4555
|
};
|
|
4517
4556
|
}
|
|
4518
4557
|
resolveStoreDir(workspaceRoot);
|
|
4519
|
-
|
|
4520
|
-
|
|
4558
|
+
fs6.mkdirSync(path6.dirname(logFile), { recursive: true });
|
|
4559
|
+
fs6.mkdirSync(path6.dirname(stateFile), { recursive: true });
|
|
4521
4560
|
const MAX_LOG_SIZE_BYTES = 10 * 1024 * 1024;
|
|
4522
4561
|
try {
|
|
4523
|
-
if (
|
|
4524
|
-
const stats =
|
|
4562
|
+
if (fs6.existsSync(logFile)) {
|
|
4563
|
+
const stats = fs6.statSync(logFile);
|
|
4525
4564
|
if (stats.size > MAX_LOG_SIZE_BYTES) {
|
|
4526
4565
|
const oldLogFile = `${logFile}.old`;
|
|
4527
4566
|
try {
|
|
4528
|
-
if (
|
|
4529
|
-
|
|
4567
|
+
if (fs6.existsSync(oldLogFile)) fs6.unlinkSync(oldLogFile);
|
|
4568
|
+
fs6.renameSync(logFile, oldLogFile);
|
|
4530
4569
|
} catch {
|
|
4531
|
-
|
|
4570
|
+
fs6.truncateSync(logFile, 0);
|
|
4532
4571
|
}
|
|
4533
4572
|
}
|
|
4534
4573
|
}
|
|
4535
4574
|
} catch {
|
|
4536
4575
|
}
|
|
4537
|
-
const logFd =
|
|
4538
|
-
const isEsedreArgv1 = process.argv[1] && /esedre/i.test(
|
|
4576
|
+
const logFd = fs6.openSync(logFile, "a");
|
|
4577
|
+
const isEsedreArgv1 = process.argv[1] && /esedre/i.test(path6.basename(process.argv[1]));
|
|
4539
4578
|
const cliCandidates = [
|
|
4540
|
-
isEsedreArgv1 ?
|
|
4541
|
-
|
|
4542
|
-
|
|
4543
|
-
|
|
4544
|
-
|
|
4545
|
-
|
|
4546
|
-
|
|
4547
|
-
|
|
4548
|
-
|
|
4549
|
-
|
|
4579
|
+
isEsedreArgv1 ? path6.resolve(process.argv[1]) : void 0,
|
|
4580
|
+
path6.resolve(workspaceRoot, "dist", "esedre.mjs"),
|
|
4581
|
+
path6.resolve(workspaceRoot, "../esedre/dist", "esedre.mjs"),
|
|
4582
|
+
path6.resolve(__dirname, "esedre.mjs"),
|
|
4583
|
+
path6.resolve(__dirname, "..", "dist", "esedre.mjs"),
|
|
4584
|
+
path6.resolve(__dirname, "..", "..", "dist", "esedre.mjs"),
|
|
4585
|
+
path6.resolve(__dirname, "..", "..", "bin", "esedre.js"),
|
|
4586
|
+
path6.resolve(__dirname, "..", "..", "bin", "esedre.ts"),
|
|
4587
|
+
path6.resolve(__dirname, "..", "esedre.mjs"),
|
|
4588
|
+
path6.resolve(__dirname, "../../../esedre/dist", "esedre.mjs")
|
|
4550
4589
|
].filter(Boolean);
|
|
4551
|
-
const cliPath = cliCandidates.find((c) =>
|
|
4552
|
-
if (!
|
|
4590
|
+
const cliPath = cliCandidates.find((c) => fs6.existsSync(c) && (c.endsWith(".mjs") || c.endsWith(".js") || c.endsWith(".ts"))) || cliCandidates[0];
|
|
4591
|
+
if (!fs6.existsSync(cliPath)) {
|
|
4553
4592
|
throw new Error(`Could not locate Esedre executable. Searched candidates: ${cliCandidates.join(", ")}`);
|
|
4554
4593
|
}
|
|
4555
4594
|
const child = spawn(process.execPath, [cliPath, "start", "--foreground", "--port", String(port)], {
|
|
@@ -4561,7 +4600,7 @@ async function startDaemon(options = {}) {
|
|
|
4561
4600
|
});
|
|
4562
4601
|
child.unref();
|
|
4563
4602
|
try {
|
|
4564
|
-
|
|
4603
|
+
fs6.closeSync(logFd);
|
|
4565
4604
|
} catch {
|
|
4566
4605
|
}
|
|
4567
4606
|
const pid = child.pid;
|
|
@@ -4602,13 +4641,13 @@ async function startDaemon(options = {}) {
|
|
|
4602
4641
|
logFile
|
|
4603
4642
|
};
|
|
4604
4643
|
const tmpState = `${stateFile}.tmp.${Date.now()}`;
|
|
4605
|
-
|
|
4644
|
+
fs6.writeFileSync(tmpState, JSON.stringify(state, null, 2) + "\n", "utf-8");
|
|
4606
4645
|
try {
|
|
4607
|
-
|
|
4646
|
+
fs6.renameSync(tmpState, stateFile);
|
|
4608
4647
|
} catch {
|
|
4609
|
-
|
|
4648
|
+
fs6.writeFileSync(stateFile, JSON.stringify(state, null, 2) + "\n", "utf-8");
|
|
4610
4649
|
try {
|
|
4611
|
-
|
|
4650
|
+
fs6.unlinkSync(tmpState);
|
|
4612
4651
|
} catch {
|
|
4613
4652
|
}
|
|
4614
4653
|
}
|
|
@@ -4671,8 +4710,8 @@ async function stopDaemon(options = {}) {
|
|
|
4671
4710
|
}
|
|
4672
4711
|
}
|
|
4673
4712
|
try {
|
|
4674
|
-
if (
|
|
4675
|
-
|
|
4713
|
+
if (fs6.existsSync(stateFile)) {
|
|
4714
|
+
fs6.unlinkSync(stateFile);
|
|
4676
4715
|
}
|
|
4677
4716
|
} catch {
|
|
4678
4717
|
}
|
|
@@ -4709,7 +4748,7 @@ async function getDaemonStatus(options = {}) {
|
|
|
4709
4748
|
}
|
|
4710
4749
|
if (state && !isProcessAlive(state.pid)) {
|
|
4711
4750
|
try {
|
|
4712
|
-
|
|
4751
|
+
fs6.unlinkSync(stateFile);
|
|
4713
4752
|
} catch {
|
|
4714
4753
|
}
|
|
4715
4754
|
}
|
|
@@ -4727,26 +4766,26 @@ function printDaemonLogs(options = {}) {
|
|
|
4727
4766
|
const port = resolvePorts(void 0, { port: options.port }).gateway;
|
|
4728
4767
|
const workspaceRoot = options.workspaceRoot || process.cwd();
|
|
4729
4768
|
const logFile = getDaemonLogFile(port, workspaceRoot);
|
|
4730
|
-
if (!
|
|
4769
|
+
if (!fs6.existsSync(logFile)) {
|
|
4731
4770
|
console.log(`\x1B[33mNo logs found at: ${logFile}\x1B[0m`);
|
|
4732
4771
|
return;
|
|
4733
4772
|
}
|
|
4734
4773
|
const count = options.lines || 40;
|
|
4735
4774
|
let content = "";
|
|
4736
4775
|
try {
|
|
4737
|
-
const stats =
|
|
4776
|
+
const stats = fs6.statSync(logFile);
|
|
4738
4777
|
const maxReadBytes = Math.min(stats.size, Math.max(256 * 1024, count * 2048));
|
|
4739
4778
|
if (stats.size <= maxReadBytes) {
|
|
4740
|
-
content =
|
|
4779
|
+
content = fs6.readFileSync(logFile, "utf-8");
|
|
4741
4780
|
} else {
|
|
4742
|
-
const fd =
|
|
4781
|
+
const fd = fs6.openSync(logFile, "r");
|
|
4743
4782
|
const buffer = Buffer.alloc(maxReadBytes);
|
|
4744
|
-
|
|
4745
|
-
|
|
4783
|
+
fs6.readSync(fd, buffer, 0, maxReadBytes, stats.size - maxReadBytes);
|
|
4784
|
+
fs6.closeSync(fd);
|
|
4746
4785
|
content = buffer.toString("utf-8");
|
|
4747
4786
|
}
|
|
4748
4787
|
} catch {
|
|
4749
|
-
content =
|
|
4788
|
+
content = fs6.readFileSync(logFile, "utf-8");
|
|
4750
4789
|
}
|
|
4751
4790
|
const allLines = content.split(/\r?\n/);
|
|
4752
4791
|
const sanitizedLines = allLines.slice(-count).map((line) => {
|
|
@@ -4961,13 +5000,13 @@ When Esedre MCP is active in your agent session (\`.agents/mcp_config.json\`), u
|
|
|
4961
5000
|
|
|
4962
5001
|
| Tool | Purpose | Key Arguments |
|
|
4963
5002
|
|---|---|---|
|
|
4964
|
-
| \`esedre_list_tickets\` | List roadmap tickets with filters | \`project\` (e.g. \`Profe\`, \`Esedre\`, \`Alce\`), \`status\`, \`type\`, \`search\` |
|
|
5003
|
+
| \`esedre_list_tickets\` | List roadmap tickets with filters | \`project\` (e.g. \`Profe\`, \`Esedre\`, \`Alce\`), \`status\`, \`type\`, \`priority\`, \`milestone\`, \`isBlocked\`, \`linkedTo\`, \`search\`, \`includePlan\` |
|
|
4965
5004
|
| \`esedre_get_ticket\` | Full specification, summary, comments, revision & sha1 | \`ticketId\` (numeric e.g. \`96\` or compound e.g. \`Profe-96\`) |
|
|
4966
5005
|
| \`esedre_get_plan\` | Active implementation plan markdown | \`ticketId\` (numeric or compound) |
|
|
4967
5006
|
| \`esedre_save_plan\` | Save implementation plan markdown with OCC | \`ticketId\`, \`planMarkdown\`, \`lastHash\` |
|
|
4968
5007
|
| \`esedre_create_ticket\` | Mint a new ticket with auto sequential ID | \`title\` (max 48 chars), \`type\`, \`project\`, \`effort\`, \`summary\`, \`detail\` |
|
|
4969
5008
|
| \`esedre_update_ticket\` | Update ticket attributes with OCC | \`ticketId\`, \`status\`, \`type\`, \`title\`, \`complexity\`, \`effort\`, \`inDevelopment\`, \`featureFlag\`, \`lastHash\` |
|
|
4970
|
-
| \`esedre_add_comment\` | Append developer or agent comment | \`ticketId\`, \`text\`, \`author\` |
|
|
5009
|
+
| \`esedre_add_comment\` | Append developer or agent comment | \`ticketId\`, \`text\`, \`file\`, \`author\` |
|
|
4971
5010
|
|
|
4972
5011
|
## 2. Core CLI Commands (Fallback via \`run_command\`)
|
|
4973
5012
|
|
|
@@ -5005,6 +5044,7 @@ Both \`esedre\` and \`ese\` work interchangeably:
|
|
|
5005
5044
|
### Append Comments or Notes
|
|
5006
5045
|
\`\`\`bash
|
|
5007
5046
|
.esedre/ese comment <ticketId> "Verified implementation." --author "Agent"
|
|
5047
|
+
.esedre/ese comment <ticketId> --file <commentFilePath> --author "Agent"
|
|
5008
5048
|
.esedre/ese comment <ticketId> --text "Verified implementation." --author "Agent"
|
|
5009
5049
|
\`\`\`
|
|
5010
5050
|
|
|
@@ -5060,8 +5100,8 @@ If the host application declares any or all of the 12 core design tokens on \`:r
|
|
|
5060
5100
|
- The URL hash is preserved across hard page refreshes (F5) without falling back to \`#/chat\` or other views.
|
|
5061
5101
|
`;
|
|
5062
5102
|
function plantWrappers(targetDir) {
|
|
5063
|
-
const esedreDir =
|
|
5064
|
-
|
|
5103
|
+
const esedreDir = path7.join(targetDir, ".esedre");
|
|
5104
|
+
fs7.mkdirSync(esedreDir, { recursive: true });
|
|
5065
5105
|
const wrappers = [
|
|
5066
5106
|
{ name: "esedre.cmd", content: WRAPPER_CMD, exec: false },
|
|
5067
5107
|
{ name: "esedre.ps1", content: WRAPPER_PS1, exec: false },
|
|
@@ -5071,24 +5111,24 @@ function plantWrappers(targetDir) {
|
|
|
5071
5111
|
{ name: "ese", content: ESE_WRAPPER_BASH, exec: true }
|
|
5072
5112
|
];
|
|
5073
5113
|
for (const w of wrappers) {
|
|
5074
|
-
const fullPath =
|
|
5075
|
-
|
|
5114
|
+
const fullPath = path7.join(esedreDir, w.name);
|
|
5115
|
+
fs7.writeFileSync(fullPath, w.content, "utf-8");
|
|
5076
5116
|
if (w.exec) {
|
|
5077
5117
|
try {
|
|
5078
|
-
|
|
5118
|
+
fs7.chmodSync(fullPath, 493);
|
|
5079
5119
|
} catch {
|
|
5080
5120
|
}
|
|
5081
5121
|
}
|
|
5082
5122
|
}
|
|
5083
5123
|
}
|
|
5084
5124
|
function setupMcpConfig(targetDir) {
|
|
5085
|
-
const agentsDir =
|
|
5086
|
-
|
|
5087
|
-
const mcpConfigPath =
|
|
5125
|
+
const agentsDir = path7.join(targetDir, ".agents");
|
|
5126
|
+
fs7.mkdirSync(agentsDir, { recursive: true });
|
|
5127
|
+
const mcpConfigPath = path7.join(agentsDir, "mcp_config.json");
|
|
5088
5128
|
let configObj = { mcpServers: {} };
|
|
5089
|
-
if (
|
|
5129
|
+
if (fs7.existsSync(mcpConfigPath)) {
|
|
5090
5130
|
try {
|
|
5091
|
-
configObj = JSON.parse(
|
|
5131
|
+
configObj = JSON.parse(fs7.readFileSync(mcpConfigPath, "utf-8"));
|
|
5092
5132
|
if (!configObj.mcpServers) configObj.mcpServers = {};
|
|
5093
5133
|
} catch {
|
|
5094
5134
|
}
|
|
@@ -5105,28 +5145,28 @@ function setupMcpConfig(targetDir) {
|
|
|
5105
5145
|
};
|
|
5106
5146
|
}
|
|
5107
5147
|
try {
|
|
5108
|
-
|
|
5148
|
+
fs7.writeFileSync(mcpConfigPath, JSON.stringify(configObj, null, 2) + "\n", "utf-8");
|
|
5109
5149
|
return true;
|
|
5110
5150
|
} catch {
|
|
5111
5151
|
return false;
|
|
5112
5152
|
}
|
|
5113
5153
|
}
|
|
5114
5154
|
function setupSkill(targetDir, options = {}) {
|
|
5115
|
-
const skillDir =
|
|
5116
|
-
|
|
5117
|
-
const skillFile =
|
|
5118
|
-
if (!
|
|
5119
|
-
|
|
5155
|
+
const skillDir = path7.join(targetDir, ".agents", "skills", "esedre");
|
|
5156
|
+
fs7.mkdirSync(skillDir, { recursive: true });
|
|
5157
|
+
const skillFile = path7.join(skillDir, "SKILL.md");
|
|
5158
|
+
if (!fs7.existsSync(skillFile)) {
|
|
5159
|
+
fs7.writeFileSync(skillFile, ESEDRE_SKILL_TEMPLATE, "utf-8");
|
|
5120
5160
|
return { status: "MISSING", updated: true };
|
|
5121
5161
|
}
|
|
5122
|
-
const currentContent =
|
|
5162
|
+
const currentContent = fs7.readFileSync(skillFile, "utf-8");
|
|
5123
5163
|
const status = classifyContent(currentContent, ESEDRE_SKILL_TEMPLATE, HISTORIC_SKILL_HASHES);
|
|
5124
5164
|
if (status === "LATEST" && !options.force) {
|
|
5125
5165
|
return { status, updated: false };
|
|
5126
5166
|
}
|
|
5127
5167
|
const hasBannedOrDeprecatedTerms = currentContent.includes("companion agent") || currentContent.includes("companion LLM") || currentContent.includes("tools/esedre.mjs");
|
|
5128
5168
|
if (options.force || status === "HISTORIC_DEFAULT" || status === "MISSING" || hasBannedOrDeprecatedTerms) {
|
|
5129
|
-
|
|
5169
|
+
fs7.writeFileSync(skillFile, ESEDRE_SKILL_TEMPLATE, "utf-8");
|
|
5130
5170
|
return { status: "LATEST", updated: true };
|
|
5131
5171
|
}
|
|
5132
5172
|
return { status, updated: false };
|
|
@@ -5140,8 +5180,8 @@ var VITE_CONFIG_FILES = [
|
|
|
5140
5180
|
];
|
|
5141
5181
|
function findViteConfig(targetDir) {
|
|
5142
5182
|
for (const name of VITE_CONFIG_FILES) {
|
|
5143
|
-
const fullPath =
|
|
5144
|
-
if (
|
|
5183
|
+
const fullPath = path7.join(targetDir, name);
|
|
5184
|
+
if (fs7.existsSync(fullPath)) {
|
|
5145
5185
|
return fullPath;
|
|
5146
5186
|
}
|
|
5147
5187
|
}
|
|
@@ -5205,10 +5245,10 @@ function configureViteProxy(targetDir, port = DEFAULT_ESEDRE_PORT, setupProxy) {
|
|
|
5205
5245
|
recommendedProxySnippet: snippet
|
|
5206
5246
|
};
|
|
5207
5247
|
}
|
|
5208
|
-
const relativeName =
|
|
5248
|
+
const relativeName = path7.basename(configPath);
|
|
5209
5249
|
let content = "";
|
|
5210
5250
|
try {
|
|
5211
|
-
content =
|
|
5251
|
+
content = fs7.readFileSync(configPath, "utf-8");
|
|
5212
5252
|
} catch {
|
|
5213
5253
|
return {
|
|
5214
5254
|
status: "MANUAL_REQUIRED",
|
|
@@ -5239,7 +5279,7 @@ function configureViteProxy(targetDir, port = DEFAULT_ESEDRE_PORT, setupProxy) {
|
|
|
5239
5279
|
};
|
|
5240
5280
|
}
|
|
5241
5281
|
try {
|
|
5242
|
-
|
|
5282
|
+
fs7.writeFileSync(configPath, injected, "utf-8");
|
|
5243
5283
|
return {
|
|
5244
5284
|
status: "CONFIGURED",
|
|
5245
5285
|
configFile: relativeName,
|
|
@@ -5254,15 +5294,15 @@ function configureViteProxy(targetDir, port = DEFAULT_ESEDRE_PORT, setupProxy) {
|
|
|
5254
5294
|
}
|
|
5255
5295
|
}
|
|
5256
5296
|
function configureWorkspace(targetDir, options = {}) {
|
|
5257
|
-
const esedreDir =
|
|
5258
|
-
if (!
|
|
5259
|
-
|
|
5297
|
+
const esedreDir = path7.join(targetDir, ".esedre");
|
|
5298
|
+
if (!fs7.existsSync(esedreDir)) {
|
|
5299
|
+
fs7.mkdirSync(esedreDir, { recursive: true });
|
|
5260
5300
|
}
|
|
5261
|
-
const esedreJsonPath =
|
|
5301
|
+
const esedreJsonPath = path7.join(esedreDir, "esedre.json");
|
|
5262
5302
|
let existingRaw = null;
|
|
5263
|
-
if (
|
|
5303
|
+
if (fs7.existsSync(esedreJsonPath)) {
|
|
5264
5304
|
try {
|
|
5265
|
-
existingRaw = JSON.parse(
|
|
5305
|
+
existingRaw = JSON.parse(fs7.readFileSync(esedreJsonPath, "utf-8"));
|
|
5266
5306
|
} catch {
|
|
5267
5307
|
}
|
|
5268
5308
|
}
|
|
@@ -5298,16 +5338,16 @@ function configureWorkspace(targetDir, options = {}) {
|
|
|
5298
5338
|
port: options.port || void 0
|
|
5299
5339
|
};
|
|
5300
5340
|
}
|
|
5301
|
-
|
|
5341
|
+
fs7.writeFileSync(esedreJsonPath, JSON.stringify(config, null, 2) + "\n", "utf-8");
|
|
5302
5342
|
let projectRegistered = false;
|
|
5303
5343
|
if (projectCode) {
|
|
5304
|
-
const hasExistingTicketsDir =
|
|
5305
|
-
const ticketsDir =
|
|
5306
|
-
if (!hasExistingTicketsDir && !
|
|
5307
|
-
|
|
5344
|
+
const hasExistingTicketsDir = fs7.existsSync(path7.join(targetDir, "tickets")) || fs7.existsSync(path7.join(targetDir, "src", "data", "planning", "tickets"));
|
|
5345
|
+
const ticketsDir = path7.join(esedreDir, "tickets");
|
|
5346
|
+
if (!hasExistingTicketsDir && !fs7.existsSync(ticketsDir)) {
|
|
5347
|
+
fs7.mkdirSync(ticketsDir, { recursive: true });
|
|
5308
5348
|
}
|
|
5309
|
-
const inRepoPJson =
|
|
5310
|
-
if (!
|
|
5349
|
+
const inRepoPJson = path7.join(esedreDir, "project.json");
|
|
5350
|
+
if (!fs7.existsSync(inRepoPJson)) {
|
|
5311
5351
|
const projDesc = {
|
|
5312
5352
|
id: 1,
|
|
5313
5353
|
code: projectCode.toUpperCase(),
|
|
@@ -5319,14 +5359,14 @@ function configureWorkspace(targetDir, options = {}) {
|
|
|
5319
5359
|
border: "border-cyan-500/40"
|
|
5320
5360
|
}
|
|
5321
5361
|
};
|
|
5322
|
-
|
|
5362
|
+
fs7.writeFileSync(inRepoPJson, JSON.stringify(projDesc, null, 2) + "\n", "utf-8");
|
|
5323
5363
|
projectRegistered = true;
|
|
5324
5364
|
} else if (projectName) {
|
|
5325
5365
|
try {
|
|
5326
|
-
const existing = JSON.parse(
|
|
5366
|
+
const existing = JSON.parse(fs7.readFileSync(inRepoPJson, "utf-8"));
|
|
5327
5367
|
if (existing.name !== projectName) {
|
|
5328
5368
|
existing.name = projectName;
|
|
5329
|
-
|
|
5369
|
+
fs7.writeFileSync(inRepoPJson, JSON.stringify(existing, null, 2) + "\n", "utf-8");
|
|
5330
5370
|
}
|
|
5331
5371
|
} catch {
|
|
5332
5372
|
}
|
|
@@ -5376,26 +5416,26 @@ var ESEDRE_HUB_AGENTS_TEMPLATE = `# Agent Guidelines: esedre-data
|
|
|
5376
5416
|
When inspecting or managing tickets for any project (such as \`Profe\`, \`Esedre\`, or \`Alce\`), navigate to that project's workspace and utilize its local \`.esedre/snapshot.json\`, Esedre MCP tools, or \`.esedre/ese\` CLI wrapper.
|
|
5377
5417
|
`;
|
|
5378
5418
|
function initHub(targetDir) {
|
|
5379
|
-
const resolved =
|
|
5380
|
-
if (!
|
|
5381
|
-
|
|
5419
|
+
const resolved = path7.resolve(targetDir);
|
|
5420
|
+
if (!fs7.existsSync(resolved)) {
|
|
5421
|
+
fs7.mkdirSync(resolved, { recursive: true });
|
|
5382
5422
|
}
|
|
5383
|
-
const projectsDir =
|
|
5423
|
+
const projectsDir = path7.join(resolved, "projects");
|
|
5384
5424
|
let projectsDirCreated = false;
|
|
5385
|
-
if (!
|
|
5386
|
-
|
|
5425
|
+
if (!fs7.existsSync(projectsDir)) {
|
|
5426
|
+
fs7.mkdirSync(projectsDir, { recursive: true });
|
|
5387
5427
|
projectsDirCreated = true;
|
|
5388
5428
|
}
|
|
5389
|
-
const projectsJsonPath =
|
|
5429
|
+
const projectsJsonPath = path7.join(resolved, "projects.json");
|
|
5390
5430
|
let projectsJsonCreated = false;
|
|
5391
|
-
if (!
|
|
5392
|
-
|
|
5431
|
+
if (!fs7.existsSync(projectsJsonPath)) {
|
|
5432
|
+
fs7.writeFileSync(projectsJsonPath, "[]\n", "utf-8");
|
|
5393
5433
|
projectsJsonCreated = true;
|
|
5394
5434
|
}
|
|
5395
|
-
const agentsMdPath =
|
|
5435
|
+
const agentsMdPath = path7.join(resolved, "AGENTS.md");
|
|
5396
5436
|
let agentsMdCreated = false;
|
|
5397
|
-
if (!
|
|
5398
|
-
|
|
5437
|
+
if (!fs7.existsSync(agentsMdPath)) {
|
|
5438
|
+
fs7.writeFileSync(agentsMdPath, ESEDRE_HUB_AGENTS_TEMPLATE, "utf-8");
|
|
5399
5439
|
agentsMdCreated = true;
|
|
5400
5440
|
}
|
|
5401
5441
|
return {
|
|
@@ -5424,14 +5464,14 @@ function findOutdatedWrappers(currentWorkspaceRoot, globalConfig) {
|
|
|
5424
5464
|
const outdated = [];
|
|
5425
5465
|
const checkedDirs = /* @__PURE__ */ new Set();
|
|
5426
5466
|
const checkDir = (dir, name) => {
|
|
5427
|
-
const resolved =
|
|
5467
|
+
const resolved = path7.resolve(expandHome(dir));
|
|
5428
5468
|
if (checkedDirs.has(resolved.toLowerCase())) return;
|
|
5429
5469
|
checkedDirs.add(resolved.toLowerCase());
|
|
5430
|
-
if (!
|
|
5431
|
-
const cmdPath =
|
|
5432
|
-
if (
|
|
5470
|
+
if (!fs7.existsSync(resolved)) return;
|
|
5471
|
+
const cmdPath = path7.join(resolved, ".esedre", "esedre.cmd");
|
|
5472
|
+
if (fs7.existsSync(cmdPath)) {
|
|
5433
5473
|
try {
|
|
5434
|
-
const content =
|
|
5474
|
+
const content = fs7.readFileSync(cmdPath, "utf-8");
|
|
5435
5475
|
if (isWrapperOutdated(content)) {
|
|
5436
5476
|
outdated.push({ name, dir: resolved, wrapperPath: cmdPath });
|
|
5437
5477
|
}
|
|
@@ -5440,7 +5480,7 @@ function findOutdatedWrappers(currentWorkspaceRoot, globalConfig) {
|
|
|
5440
5480
|
}
|
|
5441
5481
|
};
|
|
5442
5482
|
if (currentWorkspaceRoot) {
|
|
5443
|
-
checkDir(currentWorkspaceRoot,
|
|
5483
|
+
checkDir(currentWorkspaceRoot, path7.basename(currentWorkspaceRoot));
|
|
5444
5484
|
}
|
|
5445
5485
|
if (globalConfig?.projects) {
|
|
5446
5486
|
for (const [code, p] of Object.entries(globalConfig.projects)) {
|
|
@@ -5452,20 +5492,20 @@ function findOutdatedWrappers(currentWorkspaceRoot, globalConfig) {
|
|
|
5452
5492
|
async function upgradeAllWorkspaces(options = {}) {
|
|
5453
5493
|
const globalCfg = options.globalConfig !== void 0 ? options.globalConfig : readGlobalConfig();
|
|
5454
5494
|
const workspaceMap = /* @__PURE__ */ new Map();
|
|
5455
|
-
if (options.currentWorkspaceRoot &&
|
|
5456
|
-
const hasEsedre =
|
|
5495
|
+
if (options.currentWorkspaceRoot && fs7.existsSync(options.currentWorkspaceRoot)) {
|
|
5496
|
+
const hasEsedre = fs7.existsSync(path7.join(options.currentWorkspaceRoot, ".esedre")) || fs7.existsSync(path7.join(options.currentWorkspaceRoot, "esedre.json"));
|
|
5457
5497
|
if (hasEsedre) {
|
|
5458
5498
|
const disc = findEsedreConfig(options.currentWorkspaceRoot, { fallbackToGlobal: true });
|
|
5459
5499
|
workspaceMap.set(
|
|
5460
|
-
|
|
5461
|
-
disc.config?.projectCode ||
|
|
5500
|
+
path7.resolve(options.currentWorkspaceRoot),
|
|
5501
|
+
disc.config?.projectCode || path7.basename(options.currentWorkspaceRoot)
|
|
5462
5502
|
);
|
|
5463
5503
|
}
|
|
5464
5504
|
}
|
|
5465
5505
|
if (globalCfg?.projects) {
|
|
5466
5506
|
for (const [code, p] of Object.entries(globalCfg.projects)) {
|
|
5467
|
-
const resolved =
|
|
5468
|
-
if (
|
|
5507
|
+
const resolved = path7.resolve(expandHome(p));
|
|
5508
|
+
if (fs7.existsSync(resolved)) {
|
|
5469
5509
|
if (!workspaceMap.has(resolved)) {
|
|
5470
5510
|
workspaceMap.set(resolved, code);
|
|
5471
5511
|
}
|
|
@@ -5516,15 +5556,15 @@ async function upgradeAllWorkspaces(options = {}) {
|
|
|
5516
5556
|
}
|
|
5517
5557
|
|
|
5518
5558
|
// src/server/gateway.ts
|
|
5519
|
-
import
|
|
5559
|
+
import fs10 from "node:fs";
|
|
5520
5560
|
import http4 from "node:http";
|
|
5521
|
-
import
|
|
5561
|
+
import path10 from "node:path";
|
|
5522
5562
|
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
5523
5563
|
|
|
5524
5564
|
// src/server/uiServer.ts
|
|
5525
5565
|
import http2 from "node:http";
|
|
5526
|
-
import
|
|
5527
|
-
import
|
|
5566
|
+
import fs8 from "node:fs";
|
|
5567
|
+
import path8 from "node:path";
|
|
5528
5568
|
var MIME_TYPES = {
|
|
5529
5569
|
".html": "text/html; charset=utf-8",
|
|
5530
5570
|
".js": "application/javascript; charset=utf-8",
|
|
@@ -5554,27 +5594,27 @@ function startUiServer(port, webDir) {
|
|
|
5554
5594
|
if (!reqPath || reqPath === "/") {
|
|
5555
5595
|
reqPath = "/index.html";
|
|
5556
5596
|
}
|
|
5557
|
-
const safePath =
|
|
5558
|
-
let filePath =
|
|
5559
|
-
const resolvedFilePath =
|
|
5560
|
-
const resolvedWebDir =
|
|
5597
|
+
const safePath = path8.normalize(reqPath).replace(/^(\.\.[/\\])+/, "");
|
|
5598
|
+
let filePath = path8.join(webDir, safePath);
|
|
5599
|
+
const resolvedFilePath = path8.resolve(filePath);
|
|
5600
|
+
const resolvedWebDir = path8.resolve(webDir);
|
|
5561
5601
|
if (!resolvedFilePath.startsWith(resolvedWebDir)) {
|
|
5562
5602
|
res.writeHead(403, { "Content-Type": "text/plain" });
|
|
5563
5603
|
res.end("Access Denied: Path escapes web root.");
|
|
5564
5604
|
return;
|
|
5565
5605
|
}
|
|
5566
|
-
if (!
|
|
5567
|
-
filePath =
|
|
5606
|
+
if (!fs8.existsSync(filePath) || fs8.statSync(filePath).isDirectory()) {
|
|
5607
|
+
filePath = path8.join(webDir, "index.html");
|
|
5568
5608
|
}
|
|
5569
|
-
if (!
|
|
5609
|
+
if (!fs8.existsSync(filePath)) {
|
|
5570
5610
|
res.writeHead(404, { "Content-Type": "text/plain" });
|
|
5571
5611
|
res.end("Esedre UI: index.html not found. Run npm run build first.");
|
|
5572
5612
|
return;
|
|
5573
5613
|
}
|
|
5574
|
-
const ext =
|
|
5614
|
+
const ext = path8.extname(filePath).toLowerCase();
|
|
5575
5615
|
const contentType = MIME_TYPES[ext] || "application/octet-stream";
|
|
5576
5616
|
try {
|
|
5577
|
-
const content =
|
|
5617
|
+
const content = fs8.readFileSync(filePath);
|
|
5578
5618
|
res.writeHead(200, { "Content-Type": contentType });
|
|
5579
5619
|
res.end(content);
|
|
5580
5620
|
} catch (err) {
|
|
@@ -5596,8 +5636,8 @@ function startUiServer(port, webDir) {
|
|
|
5596
5636
|
|
|
5597
5637
|
// src/server/apiServer.ts
|
|
5598
5638
|
import http3 from "node:http";
|
|
5599
|
-
import
|
|
5600
|
-
import
|
|
5639
|
+
import fs9 from "node:fs";
|
|
5640
|
+
import path9 from "node:path";
|
|
5601
5641
|
var MAX_JSON_BODY_BYTES = 10 * 1024 * 1024;
|
|
5602
5642
|
function readJsonBody(req) {
|
|
5603
5643
|
return new Promise((resolve, reject) => {
|
|
@@ -5893,16 +5933,16 @@ function createApiHandler(storage, workspaceRoot) {
|
|
|
5893
5933
|
return true;
|
|
5894
5934
|
}
|
|
5895
5935
|
const filePath = reqStorage.getAttachmentPath ? reqStorage.getAttachmentPath(ticketId, file) : null;
|
|
5896
|
-
if (!filePath || !
|
|
5936
|
+
if (!filePath || !fs9.existsSync(filePath)) {
|
|
5897
5937
|
sendJson(res, 404, { error: "Attachment not found." });
|
|
5898
5938
|
return true;
|
|
5899
5939
|
}
|
|
5900
|
-
const stat =
|
|
5940
|
+
const stat = fs9.statSync(filePath);
|
|
5901
5941
|
if (!stat.isFile()) {
|
|
5902
5942
|
sendJson(res, 404, { error: "Attachment not found." });
|
|
5903
5943
|
return true;
|
|
5904
5944
|
}
|
|
5905
|
-
const ext =
|
|
5945
|
+
const ext = path9.extname(filePath).toLowerCase();
|
|
5906
5946
|
const mimeTypes = {
|
|
5907
5947
|
".png": "image/png",
|
|
5908
5948
|
".jpg": "image/jpeg",
|
|
@@ -5920,7 +5960,7 @@ function createApiHandler(storage, workspaceRoot) {
|
|
|
5920
5960
|
"Content-Length": stat.size,
|
|
5921
5961
|
"Cache-Control": "public, max-age=86400"
|
|
5922
5962
|
});
|
|
5923
|
-
|
|
5963
|
+
fs9.createReadStream(filePath).pipe(res);
|
|
5924
5964
|
return true;
|
|
5925
5965
|
}
|
|
5926
5966
|
if (pathname === "/api/planning/plan-history") {
|
|
@@ -6211,35 +6251,35 @@ function startApiServer(port, storage, workspaceRoot) {
|
|
|
6211
6251
|
|
|
6212
6252
|
// src/server/gateway.ts
|
|
6213
6253
|
var __filename2 = fileURLToPath2(import.meta.url);
|
|
6214
|
-
var __dirname2 =
|
|
6254
|
+
var __dirname2 = path10.dirname(__filename2);
|
|
6215
6255
|
function resolveWebDir(explicitWebDir, workspaceRoot) {
|
|
6216
|
-
if (explicitWebDir &&
|
|
6256
|
+
if (explicitWebDir && fs10.existsSync(path10.join(explicitWebDir, "index.html"))) {
|
|
6217
6257
|
return explicitWebDir;
|
|
6218
6258
|
}
|
|
6219
6259
|
const candidates = [
|
|
6220
6260
|
// 1. Packaged layout right next to executable (e.g. tools/web)
|
|
6221
|
-
|
|
6261
|
+
path10.resolve(__dirname2, "web"),
|
|
6222
6262
|
// 2. From workspace root: <workspaceRoot>/dist/web
|
|
6223
|
-
workspaceRoot ?
|
|
6263
|
+
workspaceRoot ? path10.resolve(workspaceRoot, "dist", "web") : "",
|
|
6224
6264
|
// 3. In-repo legacy subfolder: <workspaceRoot>/esedre/dist/web
|
|
6225
|
-
workspaceRoot ?
|
|
6265
|
+
workspaceRoot ? path10.resolve(workspaceRoot, "esedre", "dist", "web") : "",
|
|
6226
6266
|
// 4. Sibling standalone repository from workspace root: <workspaceRoot>/../esedre/dist/web
|
|
6227
|
-
workspaceRoot ?
|
|
6267
|
+
workspaceRoot ? path10.resolve(workspaceRoot, "..", "esedre", "dist", "web") : "",
|
|
6228
6268
|
// 5. Packaged layout inside dist/web
|
|
6229
|
-
|
|
6269
|
+
path10.resolve(__dirname2, "dist", "web"),
|
|
6230
6270
|
// 6. Sibling standalone repository from __dirname: <__dirname>/../../esedre/dist/web
|
|
6231
|
-
|
|
6271
|
+
path10.resolve(__dirname2, "..", "..", "esedre", "dist", "web"),
|
|
6232
6272
|
// 7. Source tree layout: esedre/src/server/ -> esedre/dist/web
|
|
6233
|
-
|
|
6273
|
+
path10.resolve(__dirname2, "..", "..", "dist", "web"),
|
|
6234
6274
|
// 8. Legacy layout from __dirname
|
|
6235
|
-
|
|
6275
|
+
path10.resolve(__dirname2, "..", "esedre", "dist", "web")
|
|
6236
6276
|
].filter(Boolean);
|
|
6237
6277
|
for (const candidate of candidates) {
|
|
6238
|
-
if (
|
|
6278
|
+
if (fs10.existsSync(path10.join(candidate, "index.html"))) {
|
|
6239
6279
|
return candidate;
|
|
6240
6280
|
}
|
|
6241
6281
|
}
|
|
6242
|
-
return candidates[0] ||
|
|
6282
|
+
return candidates[0] || path10.resolve(__dirname2, "web");
|
|
6243
6283
|
}
|
|
6244
6284
|
function proxyRequest(req, res, targetPort, rewritePath) {
|
|
6245
6285
|
const targetPath = rewritePath ? rewritePath(req.url || "/") : req.url || "/";
|
|
@@ -6362,7 +6402,7 @@ ${colors.bold}ROADMAP COMMANDS (Pair Programming & LLM Agents):${colors.reset}
|
|
|
6362
6402
|
${colors.bold}update${colors.reset} <id> [-s|--status <status>] [-t|--type <type>] [-P|--priority <prio>] [--title "..."] [--complexity <c>] [--effort "<e>"] [--detail "<md>"] [--file <path>] [--in-dev] [--flag <name>] [--last-hash <sha1>] [--force] [--json]
|
|
6363
6403
|
Mutate ticket status, type, priority, title, complexity, effort, active state, or feature flag with OCC protection.
|
|
6364
6404
|
|
|
6365
|
-
${colors.bold}comment${colors.reset} <id> ["<text>"] [--text "..."] [--author "..."] [--json]
|
|
6405
|
+
${colors.bold}comment${colors.reset} <id> ["<text>"] [--file <path>] [--text "..."] [--author "..."] [--json]
|
|
6366
6406
|
Append a research finding, test verification, or note to ticket history.
|
|
6367
6407
|
|
|
6368
6408
|
${colors.bold}link${colors.reset} <sourceId> <relation> <targetId> [--author "..."] [--json]
|
|
@@ -6439,7 +6479,7 @@ ${colors.bold}OPTIONS:${colors.reset}
|
|
|
6439
6479
|
--blocked Filter tickets that are blocked by uncompleted tickets.
|
|
6440
6480
|
--linked-to <key> Filter tickets that are linked to a specific ticket.
|
|
6441
6481
|
--detail "<md>" Specification markdown for ticket detail during create or update.
|
|
6442
|
-
--file <path> Path to markdown file for detail (create, update)
|
|
6482
|
+
--file <path> Path to markdown file for detail (create, update), implementation plan (plan), or comment (comment).
|
|
6443
6483
|
--json Output raw machine-readable JSON (strongly recommended for autonomous LLM coding agents).
|
|
6444
6484
|
--last-hash <hash> Optimistic concurrency control: last known sha1 hash of the ticket from 'get'.
|
|
6445
6485
|
--force Bypass optimistic concurrency last-hash conflict checks on writes.
|
|
@@ -6512,7 +6552,7 @@ function printDuplicateProjectWarnings(storage, isJson) {
|
|
|
6512
6552
|
const warnings = storage.getDuplicateProjectWarnings ? storage.getDuplicateProjectWarnings() : [];
|
|
6513
6553
|
for (const warn of warnings) {
|
|
6514
6554
|
console.error(
|
|
6515
|
-
`${colors.yellow}\u26A0\uFE0F Warning: Duplicate project code '${warn.code}' detected across multiple data hubs (${
|
|
6555
|
+
`${colors.yellow}\u26A0\uFE0F Warning: Duplicate project code '${warn.code}' detected across multiple data hubs (${path11.basename(warn.firstHub)} and ${path11.basename(warn.duplicateHub)}). Duplicate project codes across hubs are not supported; only '${path11.basename(warn.firstHub)}' is active.${colors.reset}`
|
|
6516
6556
|
);
|
|
6517
6557
|
}
|
|
6518
6558
|
}
|
|
@@ -6555,7 +6595,7 @@ async function main() {
|
|
|
6555
6595
|
switch (command) {
|
|
6556
6596
|
case "init": {
|
|
6557
6597
|
const rawTarget = positionals[0];
|
|
6558
|
-
const targetDir = rawTarget ?
|
|
6598
|
+
const targetDir = rawTarget ? path11.resolve(rawTarget) : discovered.workspaceRoot;
|
|
6559
6599
|
const rawHubFlag = flags["hub"];
|
|
6560
6600
|
const hubArg = typeof rawHubFlag === "string" ? rawHubFlag : void 0;
|
|
6561
6601
|
const isHubRequested = Boolean(rawHubFlag);
|
|
@@ -6634,7 +6674,7 @@ ${colors.bold}Next Steps:${colors.reset}`);
|
|
|
6634
6674
|
console.error(`${colors.red}Error: ${nameVal.error}${colors.reset}`);
|
|
6635
6675
|
process.exit(1);
|
|
6636
6676
|
}
|
|
6637
|
-
const isTargetHub =
|
|
6677
|
+
const isTargetHub = fs11.existsSync(path11.join(targetDir, "projects.json")) || fs11.existsSync(path11.join(targetDir, "projects")) && !fs11.existsSync(path11.join(targetDir, ".esedre", "esedre.json"));
|
|
6638
6678
|
const isWorkspaceless = isHubRequested || isTargetHub || Boolean(discovered.config?.projectCode) && !rawTarget && discovered.config?.projectCode?.toLowerCase() !== projectCode.toLowerCase();
|
|
6639
6679
|
let projectNewlyRegistered = false;
|
|
6640
6680
|
let registeredProj;
|
|
@@ -6705,7 +6745,7 @@ ${colors.bold}Next Steps:${colors.reset}`);
|
|
|
6705
6745
|
const viteConfigPath = findViteConfig(targetDir);
|
|
6706
6746
|
if (viteConfigPath && setupProxy === void 0 && !isJson) {
|
|
6707
6747
|
try {
|
|
6708
|
-
const rawContent =
|
|
6748
|
+
const rawContent = fs11.readFileSync(viteConfigPath, "utf-8");
|
|
6709
6749
|
const alreadyConfigured = rawContent.includes("'/esedre'") || rawContent.includes('"/esedre"');
|
|
6710
6750
|
if (!alreadyConfigured) {
|
|
6711
6751
|
if (isYes) {
|
|
@@ -6715,7 +6755,7 @@ ${colors.bold}Next Steps:${colors.reset}`);
|
|
|
6715
6755
|
const rl = readline2.createInterface({ input: process.stdin, output: process.stdout });
|
|
6716
6756
|
try {
|
|
6717
6757
|
const answer = await rl.question(
|
|
6718
|
-
`${colors.cyan}?${colors.reset} Detected ${colors.bold}${
|
|
6758
|
+
`${colors.cyan}?${colors.reset} Detected ${colors.bold}${path11.basename(viteConfigPath)}${colors.reset}. Would you like to configure a reverse proxy for ${colors.bold}/esedre${colors.reset} targeting http://127.0.0.1:${port}? [Y/n] `
|
|
6719
6759
|
);
|
|
6720
6760
|
const trimmed = answer.trim().toLowerCase();
|
|
6721
6761
|
setupProxy = trimmed === "" || trimmed === "y" || trimmed === "yes";
|
|
@@ -6731,10 +6771,10 @@ ${colors.bold}Next Steps:${colors.reset}`);
|
|
|
6731
6771
|
let hubRelPath = void 0;
|
|
6732
6772
|
if (rawHub) {
|
|
6733
6773
|
const singleHub = Array.isArray(rawHub) ? rawHub[0] : rawHub;
|
|
6734
|
-
const resolvedHub =
|
|
6735
|
-
if (
|
|
6736
|
-
const rel =
|
|
6737
|
-
const isSubdir = !rel.startsWith("..") && !
|
|
6774
|
+
const resolvedHub = path11.resolve(expandHome(singleHub));
|
|
6775
|
+
if (fs11.existsSync(resolvedHub)) {
|
|
6776
|
+
const rel = path11.relative(targetDir, resolvedHub).replace(/\\/g, "/");
|
|
6777
|
+
const isSubdir = !rel.startsWith("..") && !path11.isAbsolute(rel);
|
|
6738
6778
|
if (isSubdir || Boolean(flags["data-dir"])) {
|
|
6739
6779
|
hubRelPath = rel.startsWith(".") ? rel : "./" + rel;
|
|
6740
6780
|
}
|
|
@@ -6908,7 +6948,7 @@ ${colors.bold}Next Steps:${colors.reset}`);
|
|
|
6908
6948
|
}
|
|
6909
6949
|
console.log(`
|
|
6910
6950
|
${colors.bold}${colors.cyan}Esedre Central Configuration${colors.reset}`);
|
|
6911
|
-
console.log(` \u2022 Config File: ${configPath} ${
|
|
6951
|
+
console.log(` \u2022 Config File: ${configPath} ${fs11.existsSync(configPath) ? colors.green + "(active)" + colors.reset : colors.dim + "(default)" + colors.reset}`);
|
|
6912
6952
|
console.log(` \u2022 Default Port: ${globalCfg.port || DEFAULT_ESEDRE_PORT}`);
|
|
6913
6953
|
console.log(`
|
|
6914
6954
|
${colors.bold}Data Hubs (dataDir):${colors.reset}`);
|
|
@@ -6978,7 +7018,7 @@ ${colors.bold}${colors.green}\u2714 Esedre multi-workspace upgrade completed acr
|
|
|
6978
7018
|
}
|
|
6979
7019
|
return;
|
|
6980
7020
|
}
|
|
6981
|
-
const targetDir = positionals[0] ?
|
|
7021
|
+
const targetDir = positionals[0] ? path11.resolve(positionals[0]) : discovered.workspaceRoot;
|
|
6982
7022
|
const targetDiscovered = targetDir === discovered.workspaceRoot ? discovered : findEsedreConfig(targetDir, { fallbackToGlobal: true });
|
|
6983
7023
|
const res = configureWorkspace(targetDir, {
|
|
6984
7024
|
projectCode: targetDiscovered.config?.projectCode,
|
|
@@ -7107,7 +7147,7 @@ ${colors.yellow}\u26A0\uFE0F Warning: Daemon is running v${status.version}, but
|
|
|
7107
7147
|
}
|
|
7108
7148
|
} else {
|
|
7109
7149
|
console.log(`${colors.yellow}\u25CB Esedre daemon is STOPPED (port ${status.port})${colors.reset}`);
|
|
7110
|
-
if (status.logFile &&
|
|
7150
|
+
if (status.logFile && fs11.existsSync(status.logFile)) {
|
|
7111
7151
|
console.log(` \u2022 Recent Logs: ${status.logFile}`);
|
|
7112
7152
|
}
|
|
7113
7153
|
}
|
|
@@ -7312,11 +7352,11 @@ ${colors.dim}Total: ${tickets.length} tickets${colors.reset}`);
|
|
|
7312
7352
|
if (setPlan || filePath) {
|
|
7313
7353
|
let content = setPlan;
|
|
7314
7354
|
if (filePath) {
|
|
7315
|
-
if (!
|
|
7355
|
+
if (!fs11.existsSync(filePath)) {
|
|
7316
7356
|
console.error(`${colors.red}Error: Plan file "${filePath}" not found.${colors.reset}`);
|
|
7317
7357
|
process.exit(1);
|
|
7318
7358
|
}
|
|
7319
|
-
content =
|
|
7359
|
+
content = fs11.readFileSync(filePath, "utf-8");
|
|
7320
7360
|
}
|
|
7321
7361
|
await storage.savePlan(id, content, lastHash);
|
|
7322
7362
|
if (isJson) {
|
|
@@ -7388,8 +7428,8 @@ ${colors.dim}Total: ${tickets.length} tickets${colors.reset}`);
|
|
|
7388
7428
|
const milestone = flags["milestone"] || flags["m"] || void 0;
|
|
7389
7429
|
let detailMarkdown = detailArg;
|
|
7390
7430
|
if (fileArg) {
|
|
7391
|
-
const filePath =
|
|
7392
|
-
if (!
|
|
7431
|
+
const filePath = path11.resolve(fileArg);
|
|
7432
|
+
if (!fs11.existsSync(filePath)) {
|
|
7393
7433
|
if (isJson) {
|
|
7394
7434
|
console.error(JSON.stringify({ error: `Detail file "${fileArg}" not found` }));
|
|
7395
7435
|
} else {
|
|
@@ -7397,7 +7437,7 @@ ${colors.dim}Total: ${tickets.length} tickets${colors.reset}`);
|
|
|
7397
7437
|
}
|
|
7398
7438
|
process.exit(1);
|
|
7399
7439
|
}
|
|
7400
|
-
detailMarkdown =
|
|
7440
|
+
detailMarkdown = fs11.readFileSync(filePath, "utf-8");
|
|
7401
7441
|
}
|
|
7402
7442
|
const created = await storage.createTicket({
|
|
7403
7443
|
title,
|
|
@@ -7467,8 +7507,8 @@ ${colors.dim}Total: ${tickets.length} tickets${colors.reset}`);
|
|
|
7467
7507
|
const fileArg = flags["file"];
|
|
7468
7508
|
let detailMarkdown = detailArg ? normalizeDashesAndMojibake(detailArg) : void 0;
|
|
7469
7509
|
if (fileArg) {
|
|
7470
|
-
const filePath =
|
|
7471
|
-
if (!
|
|
7510
|
+
const filePath = path11.resolve(fileArg);
|
|
7511
|
+
if (!fs11.existsSync(filePath)) {
|
|
7472
7512
|
if (isJson) {
|
|
7473
7513
|
console.error(JSON.stringify({ error: `Detail file "${fileArg}" not found` }));
|
|
7474
7514
|
} else {
|
|
@@ -7476,7 +7516,7 @@ ${colors.dim}Total: ${tickets.length} tickets${colors.reset}`);
|
|
|
7476
7516
|
}
|
|
7477
7517
|
process.exit(1);
|
|
7478
7518
|
}
|
|
7479
|
-
detailMarkdown = normalizeDashesAndMojibake(
|
|
7519
|
+
detailMarkdown = normalizeDashesAndMojibake(fs11.readFileSync(filePath, "utf-8"));
|
|
7480
7520
|
}
|
|
7481
7521
|
if (detailMarkdown !== void 0) updates.detailMarkdown = detailMarkdown;
|
|
7482
7522
|
const updated = await storage.updateTicket(lookupKey, updates, lastHash);
|
|
@@ -7634,10 +7674,30 @@ ${colors.bold}Tickets:${colors.reset}`);
|
|
|
7634
7674
|
}
|
|
7635
7675
|
const projectFlag = flags["project"];
|
|
7636
7676
|
const lookupKey = projectFlag && /^\d+$/.test(idStr) ? `${projectFlag}-${idStr}` : idStr;
|
|
7637
|
-
|
|
7638
|
-
const
|
|
7677
|
+
let text;
|
|
7678
|
+
const filePath = flags["file"];
|
|
7679
|
+
if (filePath) {
|
|
7680
|
+
const resolvedPath = path11.resolve(process.cwd(), filePath);
|
|
7681
|
+
if (!fs11.existsSync(resolvedPath)) {
|
|
7682
|
+
console.error(`${colors.red}Error: File not found: ${filePath}${colors.reset}`);
|
|
7683
|
+
process.exit(1);
|
|
7684
|
+
}
|
|
7685
|
+
if (!fs11.statSync(resolvedPath).isFile()) {
|
|
7686
|
+
console.error(`${colors.red}Error: Path is not a file: ${filePath}${colors.reset}`);
|
|
7687
|
+
process.exit(1);
|
|
7688
|
+
}
|
|
7689
|
+
const fileContent = fs11.readFileSync(resolvedPath, "utf-8");
|
|
7690
|
+
if (!fileContent.trim()) {
|
|
7691
|
+
console.error(`${colors.red}Error: Comment file is empty: ${filePath}${colors.reset}`);
|
|
7692
|
+
process.exit(1);
|
|
7693
|
+
}
|
|
7694
|
+
text = fileContent;
|
|
7695
|
+
} else {
|
|
7696
|
+
const positionalText = positionals.slice(1).join(" ").trim();
|
|
7697
|
+
text = flags["text"] || (positionalText || void 0);
|
|
7698
|
+
}
|
|
7639
7699
|
if (!text) {
|
|
7640
|
-
console.error(`${colors.red}Error: Comment text is required (e.g. ese comment 96
|
|
7700
|
+
console.error(`${colors.red}Error: Comment text or --file is required (e.g. ese comment 96 --file note.md or ese comment 96 "Verified tests")${colors.reset}`);
|
|
7641
7701
|
process.exit(1);
|
|
7642
7702
|
}
|
|
7643
7703
|
const author = flags["author"] || "User";
|