project-roadmap-tracking 0.1.0 → 0.2.1

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.
Files changed (58) hide show
  1. package/README.md +293 -24
  2. package/dist/commands/add.d.ts +2 -0
  3. package/dist/commands/add.js +39 -31
  4. package/dist/commands/complete.d.ts +2 -0
  5. package/dist/commands/complete.js +35 -12
  6. package/dist/commands/init.d.ts +1 -0
  7. package/dist/commands/init.js +63 -46
  8. package/dist/commands/list.d.ts +3 -0
  9. package/dist/commands/list.js +65 -62
  10. package/dist/commands/pass-test.d.ts +4 -1
  11. package/dist/commands/pass-test.js +36 -13
  12. package/dist/commands/show.d.ts +4 -1
  13. package/dist/commands/show.js +38 -59
  14. package/dist/commands/update.d.ts +3 -0
  15. package/dist/commands/update.js +77 -32
  16. package/dist/commands/validate.d.ts +4 -1
  17. package/dist/commands/validate.js +74 -32
  18. package/dist/errors/base.error.d.ts +21 -0
  19. package/dist/errors/base.error.js +35 -0
  20. package/dist/errors/circular-dependency.error.d.ts +8 -0
  21. package/dist/errors/circular-dependency.error.js +13 -0
  22. package/dist/errors/config-not-found.error.d.ts +7 -0
  23. package/dist/errors/config-not-found.error.js +12 -0
  24. package/dist/errors/index.d.ts +16 -0
  25. package/dist/errors/index.js +26 -0
  26. package/dist/errors/invalid-task.error.d.ts +7 -0
  27. package/dist/errors/invalid-task.error.js +12 -0
  28. package/dist/errors/roadmap-not-found.error.d.ts +7 -0
  29. package/dist/errors/roadmap-not-found.error.js +12 -0
  30. package/dist/errors/task-not-found.error.d.ts +7 -0
  31. package/dist/errors/task-not-found.error.js +12 -0
  32. package/dist/errors/validation.error.d.ts +16 -0
  33. package/dist/errors/validation.error.js +16 -0
  34. package/dist/repositories/config.repository.d.ts +76 -0
  35. package/dist/repositories/config.repository.js +282 -0
  36. package/dist/repositories/index.d.ts +2 -0
  37. package/dist/repositories/index.js +2 -0
  38. package/dist/repositories/roadmap.repository.d.ts +82 -0
  39. package/dist/repositories/roadmap.repository.js +201 -0
  40. package/dist/services/display.service.d.ts +182 -0
  41. package/dist/services/display.service.js +320 -0
  42. package/dist/services/error-handler.service.d.ts +114 -0
  43. package/dist/services/error-handler.service.js +169 -0
  44. package/dist/services/roadmap.service.d.ts +142 -0
  45. package/dist/services/roadmap.service.js +269 -0
  46. package/dist/services/task-dependency.service.d.ts +210 -0
  47. package/dist/services/task-dependency.service.js +371 -0
  48. package/dist/services/task-query.service.d.ts +123 -0
  49. package/dist/services/task-query.service.js +259 -0
  50. package/dist/services/task.service.d.ts +155 -0
  51. package/dist/services/task.service.js +233 -0
  52. package/dist/util/read-config.js +12 -2
  53. package/dist/util/read-roadmap.js +12 -2
  54. package/dist/util/types.d.ts +5 -0
  55. package/dist/util/update-task.js +2 -1
  56. package/dist/util/validate-task.js +6 -5
  57. package/oclif.manifest.json +128 -5
  58. package/package.json +28 -4
@@ -1,7 +1,11 @@
1
1
  import { Args, Command, Flags } from '@oclif/core';
2
+ import { getDefaultConfigRepository } from '../repositories/config.repository.js';
3
+ import { RoadmapRepository } from '../repositories/roadmap.repository.js';
4
+ import errorHandlerService from '../services/error-handler.service.js';
5
+ import taskService from '../services/task.service.js';
2
6
  import { readConfigFile } from '../util/read-config.js';
3
7
  import { readRoadmapFile } from '../util/read-roadmap.js';
4
- import { STATUS } from '../util/types.js';
8
+ import { STATUS, TASK_TYPE } from '../util/types.js';
5
9
  import { updateTaskInRoadmap } from '../util/update-task.js';
6
10
  import { validateTaskID } from '../util/validate-task-id.js';
7
11
  import { writeRoadmapFile } from '../util/write-roadmap.js';
@@ -20,6 +24,10 @@ export default class Update extends Command {
20
24
  char: 'd',
21
25
  description: 'update the dependencies of the task (comma-separated list of task IDs)',
22
26
  }),
27
+ 'no-repo': Flags.boolean({
28
+ default: false,
29
+ description: 'use legacy direct file I/O instead of repository pattern',
30
+ }),
23
31
  notes: Flags.string({ char: 'n', description: 'append notes to the task' }),
24
32
  status: Flags.string({
25
33
  char: 's',
@@ -31,6 +39,15 @@ export default class Update extends Command {
31
39
  description: 'update whether the task passes tests',
32
40
  options: ['true', 'false'],
33
41
  }),
42
+ type: Flags.string({
43
+ description: 'update the task type (reassigns task ID and cascades to all references)',
44
+ options: [TASK_TYPE.Bug, TASK_TYPE.Feature, TASK_TYPE.Improvement, TASK_TYPE.Planning, TASK_TYPE.Research],
45
+ }),
46
+ verbose: Flags.boolean({
47
+ char: 'v',
48
+ default: false,
49
+ description: 'show detailed error information including stack traces',
50
+ }),
34
51
  // flag with no value (-f, --force)
35
52
  // force: Flags.boolean({char: 'f'}),
36
53
  // flag with a value (-n, --name=VALUE)
@@ -38,41 +55,69 @@ export default class Update extends Command {
38
55
  };
39
56
  async run() {
40
57
  const { args, flags } = await this.parse(Update);
41
- const config = await readConfigFile();
42
- const roadmap = await readRoadmapFile(config.path);
43
- const updateObject = {};
44
- if (flags['clear-notes']) {
45
- updateObject.notes = '';
46
- }
47
- if (flags.notes) {
48
- let existingNotes = '';
49
- if (!flags['clear-notes']) {
50
- const existingTask = roadmap.tasks.find((task) => task.id === args.taskID);
51
- existingNotes = existingTask?.notes ? existingTask.notes + '\n' : '';
58
+ try {
59
+ // Use repository pattern by default, unless --no-repo flag is set
60
+ const config = flags['no-repo'] ? await readConfigFile() : await getDefaultConfigRepository().load();
61
+ const roadmap = flags['no-repo']
62
+ ? await readRoadmapFile(config.path)
63
+ : await RoadmapRepository.fromConfig(config).load(config.path);
64
+ // Handle type update separately since it requires ID reassignment and cascading
65
+ if (flags.type) {
66
+ const newType = flags.type;
67
+ const oldTaskId = args.taskID;
68
+ const { newTaskId, roadmap: updatedRoadmap } = taskService.updateTaskType(roadmap, oldTaskId, newType);
69
+ await (flags['no-repo']
70
+ ? writeRoadmapFile(config.path, updatedRoadmap)
71
+ : RoadmapRepository.fromConfig(config).save(config.path, updatedRoadmap));
72
+ if (newTaskId === oldTaskId) {
73
+ this.log(`Task ${oldTaskId} was already of type ${newType}. No changes made.`);
74
+ }
75
+ else {
76
+ this.log(`Task ${oldTaskId} has been updated to type ${newType} with new ID ${newTaskId}.`);
77
+ this.log(`All task references to ${oldTaskId} have been updated to ${newTaskId}.`);
78
+ }
79
+ return;
52
80
  }
53
- updateObject.notes = existingNotes + flags.notes;
54
- }
55
- if (flags.status) {
56
- updateObject.status = flags.status;
57
- }
58
- if (flags.tested) {
59
- updateObject['passes-tests'] = flags.tested === 'true';
60
- }
61
- if (flags.deps) {
62
- const depsArray = flags.deps.split(',').map((dep) => dep.trim());
63
- // checking that each depArray item matches TaskID format
64
- for (const dep of depsArray) {
65
- try {
66
- validateTaskID(dep);
81
+ const updateObject = {};
82
+ if (flags['clear-notes']) {
83
+ updateObject.notes = '';
84
+ }
85
+ if (flags.notes) {
86
+ let existingNotes = '';
87
+ if (!flags['clear-notes']) {
88
+ const existingTask = roadmap.tasks.find((task) => task.id === args.taskID);
89
+ existingNotes = existingTask?.notes ? existingTask.notes + '\n' : '';
67
90
  }
68
- catch {
69
- this.error(`Invalid task ID in dependencies: ${dep}`);
91
+ updateObject.notes = existingNotes + flags.notes;
92
+ }
93
+ if (flags.status) {
94
+ updateObject.status = flags.status;
95
+ }
96
+ if (flags.tested) {
97
+ updateObject['passes-tests'] = flags.tested === 'true';
98
+ }
99
+ if (flags.deps) {
100
+ const depsArray = flags.deps.split(',').map((dep) => dep.trim());
101
+ // checking that each depArray item matches TaskID format
102
+ for (const dep of depsArray) {
103
+ try {
104
+ validateTaskID(dep);
105
+ }
106
+ catch {
107
+ this.error(`Invalid task ID in dependencies: ${dep}`);
108
+ }
70
109
  }
110
+ updateObject['depends-on'] = depsArray;
71
111
  }
72
- updateObject['depends-on'] = depsArray;
112
+ const updatedRoadmap = await updateTaskInRoadmap(roadmap, args.taskID, updateObject);
113
+ await (flags['no-repo']
114
+ ? writeRoadmapFile(config.path, updatedRoadmap)
115
+ : RoadmapRepository.fromConfig(config).save(config.path, updatedRoadmap));
116
+ this.log(`Task ${args.taskID} has been updated.`);
117
+ }
118
+ catch (error) {
119
+ const exitCode = errorHandlerService.handleError(error);
120
+ this.error(errorHandlerService.formatErrorMessage(error, flags.verbose), { exit: exitCode });
73
121
  }
74
- const updatedRoadmap = await updateTaskInRoadmap(roadmap, args.taskID, updateObject);
75
- await writeRoadmapFile(config.path, updatedRoadmap);
76
- this.log(`Task ${args.taskID} updated.`);
77
122
  }
78
123
  }
@@ -3,6 +3,9 @@ export default class Validate extends Command {
3
3
  static args: {};
4
4
  static description: string;
5
5
  static examples: string[];
6
- static flags: {};
6
+ static flags: {
7
+ 'no-repo': import("@oclif/core/interfaces").BooleanFlag<boolean>;
8
+ verbose: import("@oclif/core/interfaces").BooleanFlag<boolean>;
9
+ };
7
10
  run(): Promise<void>;
8
11
  }
@@ -1,49 +1,91 @@
1
- import { Command } from '@oclif/core';
1
+ import { Command, Flags } from '@oclif/core';
2
2
  import { readFile } from 'node:fs/promises';
3
+ import { getDefaultConfigRepository } from '../repositories/config.repository.js';
4
+ import { RoadmapRepository } from '../repositories/roadmap.repository.js';
5
+ import displayService from '../services/display.service.js';
6
+ import errorHandlerService from '../services/error-handler.service.js';
7
+ import taskDependencyService from '../services/task-dependency.service.js';
3
8
  import { readConfigFile } from '../util/read-config.js';
4
9
  import { validateTask } from '../util/validate-task.js';
5
10
  export default class Validate extends Command {
6
11
  static args = {
7
12
  // file: Args.string({description: 'file to read'}),
8
13
  };
9
- static description = 'describe the command here';
14
+ static description = 'Validate roadmap structure, task data, and check for circular dependencies';
10
15
  static examples = ['<%= config.bin %> <%= command.id %>'];
11
16
  static flags = {
12
- // flag with no value (-f, --force)
13
- // force: Flags.boolean({char: 'f'}),
14
- // flag with a value (-n, --name=VALUE)
15
- // name: Flags.string({char: 'n', description: 'name to print'}),
17
+ 'no-repo': Flags.boolean({
18
+ default: false,
19
+ description: 'use legacy direct file I/O instead of repository pattern',
20
+ }),
21
+ // flag with no value (-f, --force)
22
+ // force: Flags.boolean({char: 'f'}),
23
+ // flag with a value (-n, --name=VALUE)
24
+ // name: Flags.string({char: 'n', description: 'name to print'}),
25
+ verbose: Flags.boolean({
26
+ char: 'v',
27
+ default: false,
28
+ description: 'show detailed error information including stack traces',
29
+ }),
16
30
  };
17
31
  async run() {
18
- await this.parse(Validate);
19
- this.log(`validating roadmap...`);
20
- const config = await readConfigFile().catch((error) => {
21
- this.error(`failed to read config file: ${error ? error.message : String(error)}`);
22
- });
23
- const roadmapPath = config.path;
24
- const roadmapData = await readFile(roadmapPath, 'utf8');
25
- let roadmap;
32
+ const { flags } = await this.parse(Validate);
26
33
  try {
27
- roadmap = JSON.parse(roadmapData);
28
- this.log(`roadmap at ${roadmapPath} is valid JSON`);
29
- }
30
- catch (error) {
31
- this.error(`roadmap at ${roadmapPath} is not valid JSON: ${error ? error.message : String(error)}`);
32
- }
33
- if (roadmap.tasks.length === 0) {
34
- this.log('roadmap contains no tasks to validate');
35
- this.log('roadmap validation complete');
36
- return;
37
- }
38
- for (const task of roadmap.tasks) {
39
- try {
40
- validateTask(task);
34
+ this.log(`validating roadmap...`);
35
+ // Use repository pattern by default, unless --no-repo flag is set
36
+ const config = flags['no-repo'] ? await readConfigFile() : await getDefaultConfigRepository().load();
37
+ const roadmapPath = config.path;
38
+ let roadmap;
39
+ if (flags['no-repo']) {
40
+ const roadmapData = await readFile(roadmapPath, 'utf8');
41
+ try {
42
+ roadmap = JSON.parse(roadmapData);
43
+ this.log(`roadmap at ${roadmapPath} is valid JSON`);
44
+ }
45
+ catch (error) {
46
+ this.error(`roadmap at ${roadmapPath} is not valid JSON: ${error ? error.message : String(error)}`);
47
+ }
48
+ }
49
+ else {
50
+ try {
51
+ roadmap = await RoadmapRepository.fromConfig(config).load(roadmapPath);
52
+ this.log(`roadmap at ${roadmapPath} is valid JSON`);
53
+ }
54
+ catch (error) {
55
+ this.error(`roadmap at ${roadmapPath} is not valid JSON: ${error ? error.message : String(error)}`);
56
+ }
41
57
  }
42
- catch (error) {
43
- this.error(`task ID ${task.id} is invalid: ${error ? error.message : String(error)}`);
58
+ if (roadmap.tasks.length === 0) {
59
+ this.log('roadmap contains no tasks to validate');
60
+ this.log('roadmap validation complete');
61
+ return;
44
62
  }
63
+ for (const task of roadmap.tasks) {
64
+ try {
65
+ validateTask(task);
66
+ }
67
+ catch (error) {
68
+ this.error(`task ID ${task.id} is invalid: ${error ? error.message : String(error)}`);
69
+ }
70
+ }
71
+ // this.log(roadmap.tasks.length > 1 ? `all ${roadmap.tasks.length} tasks are valid` : `1 task is valid`)
72
+ // Validate dependencies (including circular dependency check)
73
+ this.log(`validating task dependencies...`);
74
+ const dependencyErrors = taskDependencyService.validateDependencies(roadmap);
75
+ if (dependencyErrors.length > 0) {
76
+ const errorLines = displayService.formatValidationErrors(dependencyErrors);
77
+ for (const line of errorLines) {
78
+ this.log(line);
79
+ }
80
+ // Exit with error if any dependency errors found
81
+ this.error('Dependency validation failed');
82
+ }
83
+ this.log(`all task dependencies are valid`);
84
+ this.log(`roadmap validation complete`);
85
+ }
86
+ catch (error) {
87
+ const exitCode = errorHandlerService.handleError(error);
88
+ this.error(errorHandlerService.formatErrorMessage(error, flags.verbose), { exit: exitCode });
45
89
  }
46
- this.log(roadmap.tasks.length > 1 ? `all ${roadmap.tasks.length} tasks are valid` : `1 task is valid`);
47
- this.log(`roadmap validation complete`);
48
90
  }
49
91
  }
@@ -0,0 +1,21 @@
1
+ /**
2
+ * Error codes for all PRT errors
3
+ */
4
+ export declare enum PrtErrorCode {
5
+ PRT_FILE_CONFIG_NOT_FOUND = "PRT_FILE_CONFIG_NOT_FOUND",
6
+ PRT_FILE_ROADMAP_NOT_FOUND = "PRT_FILE_ROADMAP_NOT_FOUND",
7
+ PRT_TASK_ID_INVALID = "PRT_TASK_ID_INVALID",
8
+ PRT_TASK_INVALID = "PRT_TASK_INVALID",
9
+ PRT_TASK_NOT_FOUND = "PRT_TASK_NOT_FOUND",
10
+ PRT_UNKNOWN = "PRT_UNKNOWN",
11
+ PRT_VALIDATION_CIRCULAR_DEPENDENCY = "PRT_VALIDATION_CIRCULAR_DEPENDENCY",
12
+ PRT_VALIDATION_FAILED = "PRT_VALIDATION_FAILED"
13
+ }
14
+ /**
15
+ * Base error class for all PRT custom errors
16
+ */
17
+ export declare class PrtError extends Error {
18
+ readonly code: PrtErrorCode;
19
+ readonly context?: Record<string, unknown>;
20
+ constructor(message: string, code?: PrtErrorCode, context?: Record<string, unknown>);
21
+ }
@@ -0,0 +1,35 @@
1
+ /**
2
+ * Error codes for all PRT errors
3
+ */
4
+ export var PrtErrorCode;
5
+ (function (PrtErrorCode) {
6
+ PrtErrorCode["PRT_FILE_CONFIG_NOT_FOUND"] = "PRT_FILE_CONFIG_NOT_FOUND";
7
+ // File errors
8
+ PrtErrorCode["PRT_FILE_ROADMAP_NOT_FOUND"] = "PRT_FILE_ROADMAP_NOT_FOUND";
9
+ PrtErrorCode["PRT_TASK_ID_INVALID"] = "PRT_TASK_ID_INVALID";
10
+ PrtErrorCode["PRT_TASK_INVALID"] = "PRT_TASK_INVALID";
11
+ // Task errors
12
+ PrtErrorCode["PRT_TASK_NOT_FOUND"] = "PRT_TASK_NOT_FOUND";
13
+ // Generic
14
+ PrtErrorCode["PRT_UNKNOWN"] = "PRT_UNKNOWN";
15
+ PrtErrorCode["PRT_VALIDATION_CIRCULAR_DEPENDENCY"] = "PRT_VALIDATION_CIRCULAR_DEPENDENCY";
16
+ // Validation errors
17
+ PrtErrorCode["PRT_VALIDATION_FAILED"] = "PRT_VALIDATION_FAILED";
18
+ })(PrtErrorCode || (PrtErrorCode = {}));
19
+ /**
20
+ * Base error class for all PRT custom errors
21
+ */
22
+ export class PrtError extends Error {
23
+ code;
24
+ context;
25
+ constructor(message, code = PrtErrorCode.PRT_UNKNOWN, context) {
26
+ super(message);
27
+ this.name = this.constructor.name;
28
+ this.code = code;
29
+ this.context = context;
30
+ // Maintains proper stack trace for where our error was thrown (only available on V8)
31
+ if (Error.captureStackTrace) {
32
+ Error.captureStackTrace(this, this.constructor);
33
+ }
34
+ }
35
+ }
@@ -0,0 +1,8 @@
1
+ import type { TaskID } from '../util/types.js';
2
+ import { PrtError } from './base.error.js';
3
+ /**
4
+ * Error thrown when circular dependencies are detected in tasks
5
+ */
6
+ export declare class CircularDependencyError extends PrtError {
7
+ constructor(cycle: TaskID[], message?: string);
8
+ }
@@ -0,0 +1,13 @@
1
+ import { PrtError, PrtErrorCode } from './base.error.js';
2
+ /**
3
+ * Error thrown when circular dependencies are detected in tasks
4
+ */
5
+ export class CircularDependencyError extends PrtError {
6
+ constructor(cycle, message) {
7
+ const defaultMessage = `Circular dependency detected: ${cycle.join(' -> ')} -> ${cycle[0]}`;
8
+ super(message || defaultMessage, PrtErrorCode.PRT_VALIDATION_CIRCULAR_DEPENDENCY, {
9
+ cycle,
10
+ cycleLength: cycle.length,
11
+ });
12
+ }
13
+ }
@@ -0,0 +1,7 @@
1
+ import { PrtError } from './base.error.js';
2
+ /**
3
+ * Error thrown when a config file (.prtrc.json) cannot be found
4
+ */
5
+ export declare class ConfigNotFoundError extends PrtError {
6
+ constructor(filePath?: string, cause?: Error);
7
+ }
@@ -0,0 +1,12 @@
1
+ import { PrtError, PrtErrorCode } from './base.error.js';
2
+ /**
3
+ * Error thrown when a config file (.prtrc.json) cannot be found
4
+ */
5
+ export class ConfigNotFoundError extends PrtError {
6
+ constructor(filePath = '.prtrc.json', cause) {
7
+ super(`Config file not found: ${filePath}`, PrtErrorCode.PRT_FILE_CONFIG_NOT_FOUND, {
8
+ filePath,
9
+ ...(cause && { cause: cause.message }),
10
+ });
11
+ }
12
+ }
@@ -0,0 +1,16 @@
1
+ export { PrtError, PrtErrorCode } from './base.error.js';
2
+ export { CircularDependencyError } from './circular-dependency.error.js';
3
+ export { ConfigNotFoundError } from './config-not-found.error.js';
4
+ export { InvalidTaskError } from './invalid-task.error.js';
5
+ export { RoadmapNotFoundError } from './roadmap-not-found.error.js';
6
+ export { TaskNotFoundError } from './task-not-found.error.js';
7
+ export { ValidationError, type ValidationErrorDetail } from './validation.error.js';
8
+ import { PrtError, PrtErrorCode } from './base.error.js';
9
+ /**
10
+ * Type guard to check if an error is a PrtError
11
+ */
12
+ export declare function isPrtError(error: unknown): error is PrtError;
13
+ /**
14
+ * Get the error code from an error, or null if not a PrtError
15
+ */
16
+ export declare function getErrorCode(error: unknown): null | PrtErrorCode;
@@ -0,0 +1,26 @@
1
+ // Base error and error codes
2
+ export { PrtError, PrtErrorCode } from './base.error.js';
3
+ export { CircularDependencyError } from './circular-dependency.error.js';
4
+ // Specific error classes
5
+ export { ConfigNotFoundError } from './config-not-found.error.js';
6
+ export { InvalidTaskError } from './invalid-task.error.js';
7
+ export { RoadmapNotFoundError } from './roadmap-not-found.error.js';
8
+ export { TaskNotFoundError } from './task-not-found.error.js';
9
+ export { ValidationError } from './validation.error.js';
10
+ // Import for type guards
11
+ import { PrtError } from './base.error.js';
12
+ /**
13
+ * Type guard to check if an error is a PrtError
14
+ */
15
+ export function isPrtError(error) {
16
+ return error instanceof PrtError;
17
+ }
18
+ /**
19
+ * Get the error code from an error, or null if not a PrtError
20
+ */
21
+ export function getErrorCode(error) {
22
+ if (isPrtError(error)) {
23
+ return error.code;
24
+ }
25
+ return null;
26
+ }
@@ -0,0 +1,7 @@
1
+ import { PrtError } from './base.error.js';
2
+ /**
3
+ * Error thrown when a task fails validation
4
+ */
5
+ export declare class InvalidTaskError extends PrtError {
6
+ constructor(message: string, taskId?: string, validationField?: string);
7
+ }
@@ -0,0 +1,12 @@
1
+ import { PrtError, PrtErrorCode } from './base.error.js';
2
+ /**
3
+ * Error thrown when a task fails validation
4
+ */
5
+ export class InvalidTaskError extends PrtError {
6
+ constructor(message, taskId, validationField) {
7
+ super(message, PrtErrorCode.PRT_TASK_INVALID, {
8
+ ...(taskId && { taskId }),
9
+ ...(validationField && { validationField }),
10
+ });
11
+ }
12
+ }
@@ -0,0 +1,7 @@
1
+ import { PrtError } from './base.error.js';
2
+ /**
3
+ * Error thrown when a roadmap file cannot be found
4
+ */
5
+ export declare class RoadmapNotFoundError extends PrtError {
6
+ constructor(filePath: string, cause?: Error);
7
+ }
@@ -0,0 +1,12 @@
1
+ import { PrtError, PrtErrorCode } from './base.error.js';
2
+ /**
3
+ * Error thrown when a roadmap file cannot be found
4
+ */
5
+ export class RoadmapNotFoundError extends PrtError {
6
+ constructor(filePath, cause) {
7
+ super(`Roadmap file not found: ${filePath}`, PrtErrorCode.PRT_FILE_ROADMAP_NOT_FOUND, {
8
+ filePath,
9
+ ...(cause && { cause: cause.message }),
10
+ });
11
+ }
12
+ }
@@ -0,0 +1,7 @@
1
+ import { PrtError } from './base.error.js';
2
+ /**
3
+ * Error thrown when a task cannot be found by its ID
4
+ */
5
+ export declare class TaskNotFoundError extends PrtError {
6
+ constructor(taskId: string, roadmapPath?: string);
7
+ }
@@ -0,0 +1,12 @@
1
+ import { PrtError, PrtErrorCode } from './base.error.js';
2
+ /**
3
+ * Error thrown when a task cannot be found by its ID
4
+ */
5
+ export class TaskNotFoundError extends PrtError {
6
+ constructor(taskId, roadmapPath) {
7
+ super(`Task not found: ${taskId}`, PrtErrorCode.PRT_TASK_NOT_FOUND, {
8
+ taskId,
9
+ ...(roadmapPath && { roadmapPath }),
10
+ });
11
+ }
12
+ }
@@ -0,0 +1,16 @@
1
+ import { PrtError } from './base.error.js';
2
+ /**
3
+ * Details about a single validation error
4
+ */
5
+ export interface ValidationErrorDetail {
6
+ field?: string;
7
+ message: string;
8
+ taskId?: string;
9
+ type: 'circular-dependency' | 'duplicate-id' | 'invalid-reference' | 'invalid-value' | 'missing-field' | 'missing-task' | 'structure' | 'task';
10
+ }
11
+ /**
12
+ * Error thrown when validation fails with multiple issues
13
+ */
14
+ export declare class ValidationError extends PrtError {
15
+ constructor(errors: ValidationErrorDetail[]);
16
+ }
@@ -0,0 +1,16 @@
1
+ import { PrtError, PrtErrorCode } from './base.error.js';
2
+ /**
3
+ * Error thrown when validation fails with multiple issues
4
+ */
5
+ export class ValidationError extends PrtError {
6
+ constructor(errors) {
7
+ const errorCount = errors.length;
8
+ const errorTypes = [...new Set(errors.map((e) => e.type))];
9
+ const message = `Validation failed with ${errorCount} error${errorCount === 1 ? '' : 's'}`;
10
+ super(message, PrtErrorCode.PRT_VALIDATION_FAILED, {
11
+ errorCount,
12
+ errors,
13
+ errorTypes,
14
+ });
15
+ }
16
+ }
@@ -0,0 +1,76 @@
1
+ import { Config } from '../util/types.js';
2
+ /**
3
+ * Configuration for ConfigRepository
4
+ */
5
+ export interface ConfigRepositoryConfig {
6
+ cacheEnabled?: boolean;
7
+ searchPaths?: string[];
8
+ }
9
+ /**
10
+ * ConfigRepository provides caching, validation, and inheritance for config files.
11
+ * Features:
12
+ * - In-memory cache with mtime-based invalidation
13
+ * - Multi-level config inheritance (project → user → global)
14
+ * - JSON schema validation using schemas/config/v1.1.json
15
+ * - Shallow merge strategy for inherited configs
16
+ */
17
+ export declare class ConfigRepository {
18
+ private static configSchema;
19
+ private cache;
20
+ private config;
21
+ private validateSchema;
22
+ constructor(config?: ConfigRepositoryConfig);
23
+ /**
24
+ * Get the currently cached config (if any)
25
+ */
26
+ getCachedConfig(): Config | null;
27
+ /**
28
+ * Clear the in-memory cache
29
+ */
30
+ invalidateCache(): void;
31
+ /**
32
+ * Load config with caching and inheritance
33
+ * Searches for config files in order: project → user → global
34
+ * Merges configs with shallow merge (project overrides user overrides global)
35
+ */
36
+ load(): Promise<Config>;
37
+ /**
38
+ * Reload config from disk, bypassing cache
39
+ */
40
+ reload(): Promise<Config>;
41
+ /**
42
+ * Get default config file search paths
43
+ * Order: project → user → global
44
+ */
45
+ private getDefaultSearchPaths;
46
+ /**
47
+ * Check if cached config is still valid by comparing mtime
48
+ */
49
+ private isCacheValid;
50
+ /**
51
+ * Load all available configs from search paths
52
+ * Returns array in order of precedence (project first, global last)
53
+ */
54
+ private loadAllConfigs;
55
+ /**
56
+ * Load config from a specific path
57
+ */
58
+ private loadConfigFromPath;
59
+ /**
60
+ * Merge configs using shallow merge strategy
61
+ * Project-level config takes precedence over user-level over global-level
62
+ */
63
+ private mergeConfigs;
64
+ /**
65
+ * Validate config against JSON schema
66
+ */
67
+ private validateConfig;
68
+ }
69
+ /**
70
+ * Get the default config repository instance
71
+ */
72
+ export declare function getDefaultConfigRepository(): ConfigRepository;
73
+ /**
74
+ * Reset the default config repository instance (useful for testing)
75
+ */
76
+ export declare function resetDefaultConfigRepository(): void;