planfs-core 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/README.md +125 -0
- package/dist/ai.d.ts +98 -0
- package/dist/ai.d.ts.map +1 -0
- package/dist/ai.js +265 -0
- package/dist/ai.js.map +1 -0
- package/dist/backlog.d.ts +40 -0
- package/dist/backlog.d.ts.map +1 -0
- package/dist/backlog.js +206 -0
- package/dist/backlog.js.map +1 -0
- package/dist/bulk.d.ts +29 -0
- package/dist/bulk.d.ts.map +1 -0
- package/dist/bulk.js +206 -0
- package/dist/bulk.js.map +1 -0
- package/dist/files.d.ts +38 -0
- package/dist/files.d.ts.map +1 -0
- package/dist/files.js +179 -0
- package/dist/files.js.map +1 -0
- package/dist/git.d.ts +74 -0
- package/dist/git.d.ts.map +1 -0
- package/dist/git.js +426 -0
- package/dist/git.js.map +1 -0
- package/dist/graph.d.ts +56 -0
- package/dist/graph.d.ts.map +1 -0
- package/dist/graph.js +188 -0
- package/dist/graph.js.map +1 -0
- package/dist/index.d.ts +20 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +36 -0
- package/dist/index.js.map +1 -0
- package/dist/loader.d.ts +26 -0
- package/dist/loader.d.ts.map +1 -0
- package/dist/loader.js +248 -0
- package/dist/loader.js.map +1 -0
- package/dist/milestone.d.ts +23 -0
- package/dist/milestone.d.ts.map +1 -0
- package/dist/milestone.js +60 -0
- package/dist/milestone.js.map +1 -0
- package/dist/next-work.d.ts +34 -0
- package/dist/next-work.d.ts.map +1 -0
- package/dist/next-work.js +239 -0
- package/dist/next-work.js.map +1 -0
- package/dist/parser.d.ts +37 -0
- package/dist/parser.d.ts.map +1 -0
- package/dist/parser.js +132 -0
- package/dist/parser.js.map +1 -0
- package/dist/pull-request.d.ts +38 -0
- package/dist/pull-request.d.ts.map +1 -0
- package/dist/pull-request.js +147 -0
- package/dist/pull-request.js.map +1 -0
- package/dist/references.d.ts +10 -0
- package/dist/references.d.ts.map +1 -0
- package/dist/references.js +30 -0
- package/dist/references.js.map +1 -0
- package/dist/repository.d.ts +85 -0
- package/dist/repository.d.ts.map +1 -0
- package/dist/repository.js +495 -0
- package/dist/repository.js.map +1 -0
- package/dist/search.d.ts +25 -0
- package/dist/search.d.ts.map +1 -0
- package/dist/search.js +150 -0
- package/dist/search.js.map +1 -0
- package/dist/types.d.ts +119 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +6 -0
- package/dist/types.js.map +1 -0
- package/dist/validator.d.ts +24 -0
- package/dist/validator.d.ts.map +1 -0
- package/dist/validator.js +540 -0
- package/dist/validator.js.map +1 -0
- package/package.json +46 -0
package/dist/files.js
ADDED
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* File discovery module
|
|
4
|
+
* Discovers and loads PlanFS files from a repository
|
|
5
|
+
*/
|
|
6
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
7
|
+
if (k2 === undefined) k2 = k;
|
|
8
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
9
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
10
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
11
|
+
}
|
|
12
|
+
Object.defineProperty(o, k2, desc);
|
|
13
|
+
}) : (function(o, m, k, k2) {
|
|
14
|
+
if (k2 === undefined) k2 = k;
|
|
15
|
+
o[k2] = m[k];
|
|
16
|
+
}));
|
|
17
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
18
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
19
|
+
}) : function(o, v) {
|
|
20
|
+
o["default"] = v;
|
|
21
|
+
});
|
|
22
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
23
|
+
var ownKeys = function(o) {
|
|
24
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
25
|
+
var ar = [];
|
|
26
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
27
|
+
return ar;
|
|
28
|
+
};
|
|
29
|
+
return ownKeys(o);
|
|
30
|
+
};
|
|
31
|
+
return function (mod) {
|
|
32
|
+
if (mod && mod.__esModule) return mod;
|
|
33
|
+
var result = {};
|
|
34
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
35
|
+
__setModuleDefault(result, mod);
|
|
36
|
+
return result;
|
|
37
|
+
};
|
|
38
|
+
})();
|
|
39
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
40
|
+
exports.discoverFiles = discoverFiles;
|
|
41
|
+
exports.discoverArchiveFiles = discoverArchiveFiles;
|
|
42
|
+
exports.readFile = readFile;
|
|
43
|
+
exports.writeFile = writeFile;
|
|
44
|
+
exports.deleteFile = deleteFile;
|
|
45
|
+
exports.planfsDirectoryExists = planfsDirectoryExists;
|
|
46
|
+
exports.ensurePlanfsStructure = ensurePlanfsStructure;
|
|
47
|
+
const fs_1 = require("fs");
|
|
48
|
+
const path = __importStar(require("path"));
|
|
49
|
+
const PLANFS_DIRECTORIES = [
|
|
50
|
+
'.planfs',
|
|
51
|
+
'.planfs/tasks',
|
|
52
|
+
'.planfs/epics',
|
|
53
|
+
'.planfs/milestones',
|
|
54
|
+
'.planfs/decisions',
|
|
55
|
+
'.planfs/filters',
|
|
56
|
+
'.planfs/archive',
|
|
57
|
+
'.planfs/archive/tasks',
|
|
58
|
+
'.planfs/archive/epics'
|
|
59
|
+
];
|
|
60
|
+
/**
|
|
61
|
+
* Discover all PlanFS files in a repository
|
|
62
|
+
*/
|
|
63
|
+
async function discoverFiles(rootPath) {
|
|
64
|
+
const planfsDir = path.join(rootPath, '.planfs');
|
|
65
|
+
const files = [];
|
|
66
|
+
const entityTypes = ['tasks', 'epics', 'milestones', 'decisions'];
|
|
67
|
+
for (const entityType of entityTypes) {
|
|
68
|
+
const dir = path.join(planfsDir, entityType);
|
|
69
|
+
try {
|
|
70
|
+
const entries = await fs_1.promises.readdir(dir, { withFileTypes: true });
|
|
71
|
+
for (const entry of entries) {
|
|
72
|
+
if (entry.isFile() && entry.name.endsWith('.md')) {
|
|
73
|
+
files.push({
|
|
74
|
+
path: path.join(dir, entry.name),
|
|
75
|
+
name: entry.name,
|
|
76
|
+
type: entityType.slice(0, -1)
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
catch (error) {
|
|
82
|
+
// Directory may not exist, which is okay
|
|
83
|
+
if (!(error instanceof Error && 'code' in error && error.code === 'ENOENT')) {
|
|
84
|
+
throw error;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
return files;
|
|
89
|
+
}
|
|
90
|
+
async function discoverArchiveFiles(rootPath) {
|
|
91
|
+
const archiveDir = path.join(rootPath, '.planfs', 'archive');
|
|
92
|
+
const files = [];
|
|
93
|
+
const entityTypes = ['tasks', 'epics'];
|
|
94
|
+
for (const entityType of entityTypes) {
|
|
95
|
+
const dir = path.join(archiveDir, entityType);
|
|
96
|
+
try {
|
|
97
|
+
const entries = await fs_1.promises.readdir(dir, { withFileTypes: true });
|
|
98
|
+
for (const entry of entries) {
|
|
99
|
+
if (entry.isFile() && entry.name.endsWith('.md')) {
|
|
100
|
+
files.push({
|
|
101
|
+
path: path.join(dir, entry.name),
|
|
102
|
+
name: entry.name,
|
|
103
|
+
type: entityType.slice(0, -1),
|
|
104
|
+
archived: true
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
catch (error) {
|
|
110
|
+
if (error.code !== 'ENOENT') {
|
|
111
|
+
throw error;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
return files;
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* Read file content
|
|
119
|
+
*/
|
|
120
|
+
async function readFile(filePath) {
|
|
121
|
+
const content = await fs_1.promises.readFile(filePath, 'utf-8');
|
|
122
|
+
return content;
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* Write file content
|
|
126
|
+
*/
|
|
127
|
+
async function writeFile(filePath, content) {
|
|
128
|
+
// Create parent directories if needed
|
|
129
|
+
const dir = path.dirname(filePath);
|
|
130
|
+
await fs_1.promises.mkdir(dir, { recursive: true });
|
|
131
|
+
await fs_1.promises.writeFile(filePath, content, 'utf-8');
|
|
132
|
+
}
|
|
133
|
+
async function deleteFile(filePath) {
|
|
134
|
+
await fs_1.promises.unlink(filePath);
|
|
135
|
+
}
|
|
136
|
+
/**
|
|
137
|
+
* Check if .planfs directory exists
|
|
138
|
+
*/
|
|
139
|
+
async function planfsDirectoryExists(rootPath) {
|
|
140
|
+
try {
|
|
141
|
+
const planfsDir = path.join(rootPath, '.planfs');
|
|
142
|
+
const stats = await fs_1.promises.stat(planfsDir);
|
|
143
|
+
return stats.isDirectory();
|
|
144
|
+
}
|
|
145
|
+
catch {
|
|
146
|
+
return false;
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
/**
|
|
150
|
+
* Ensure .planfs directory structure exists
|
|
151
|
+
*/
|
|
152
|
+
async function ensurePlanfsStructure(rootPath) {
|
|
153
|
+
const result = {
|
|
154
|
+
root: path.join(rootPath, '.planfs'),
|
|
155
|
+
created: [],
|
|
156
|
+
existing: []
|
|
157
|
+
};
|
|
158
|
+
for (const dir of PLANFS_DIRECTORIES) {
|
|
159
|
+
const absolutePath = path.join(rootPath, dir);
|
|
160
|
+
if (await directoryExists(absolutePath)) {
|
|
161
|
+
result.existing.push(dir);
|
|
162
|
+
}
|
|
163
|
+
else {
|
|
164
|
+
await fs_1.promises.mkdir(absolutePath, { recursive: true });
|
|
165
|
+
result.created.push(dir);
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
return result;
|
|
169
|
+
}
|
|
170
|
+
async function directoryExists(dir) {
|
|
171
|
+
try {
|
|
172
|
+
const stats = await fs_1.promises.stat(dir);
|
|
173
|
+
return stats.isDirectory();
|
|
174
|
+
}
|
|
175
|
+
catch {
|
|
176
|
+
return false;
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
//# sourceMappingURL=files.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"files.js","sourceRoot":"","sources":["../src/files.ts"],"names":[],"mappings":"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"}
|
package/dist/git.d.ts
ADDED
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Git-aware planning helpers
|
|
3
|
+
*/
|
|
4
|
+
import { EntityType, Task } from './types';
|
|
5
|
+
export type BranchFileStatus = 'added' | 'modified' | 'deleted' | 'renamed' | 'copied' | 'unmerged' | 'unknown';
|
|
6
|
+
export interface BranchPlanningOptions {
|
|
7
|
+
baseRef?: string;
|
|
8
|
+
}
|
|
9
|
+
export interface BranchChangedFile {
|
|
10
|
+
path: string;
|
|
11
|
+
previousPath?: string;
|
|
12
|
+
status: BranchFileStatus;
|
|
13
|
+
entityType?: EntityType;
|
|
14
|
+
entityId?: string;
|
|
15
|
+
}
|
|
16
|
+
export interface BranchTaskChange {
|
|
17
|
+
id: string;
|
|
18
|
+
title: string;
|
|
19
|
+
status: Task['status'];
|
|
20
|
+
filePath: string;
|
|
21
|
+
previous?: {
|
|
22
|
+
title: string;
|
|
23
|
+
status: Task['status'];
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
export interface BranchConflict {
|
|
27
|
+
path: string;
|
|
28
|
+
status: string;
|
|
29
|
+
suggestion: string;
|
|
30
|
+
}
|
|
31
|
+
export interface BranchPullRequestPreview {
|
|
32
|
+
title: string;
|
|
33
|
+
summary: string;
|
|
34
|
+
relatedTaskIds: string[];
|
|
35
|
+
}
|
|
36
|
+
export interface BranchPlanningContext {
|
|
37
|
+
currentBranch: string;
|
|
38
|
+
baseRef: string;
|
|
39
|
+
comparisonRef: string;
|
|
40
|
+
taskIdsInBranchName: string[];
|
|
41
|
+
relatedTaskIds: string[];
|
|
42
|
+
changedFiles: BranchChangedFile[];
|
|
43
|
+
addedTasks: BranchTaskChange[];
|
|
44
|
+
modifiedTasks: BranchTaskChange[];
|
|
45
|
+
deletedTaskIds: string[];
|
|
46
|
+
conflicts: BranchConflict[];
|
|
47
|
+
pullRequestPreview: BranchPullRequestPreview;
|
|
48
|
+
}
|
|
49
|
+
export interface CommitMessageSuggestion {
|
|
50
|
+
message: string;
|
|
51
|
+
taskIds: string[];
|
|
52
|
+
source: 'related-task' | 'branch-name';
|
|
53
|
+
}
|
|
54
|
+
export interface CommitMessageValidationResult {
|
|
55
|
+
valid: boolean;
|
|
56
|
+
taskIds: string[];
|
|
57
|
+
missingTaskIds: string[];
|
|
58
|
+
warnings: string[];
|
|
59
|
+
}
|
|
60
|
+
export interface RepositoryDeveloper {
|
|
61
|
+
name: string;
|
|
62
|
+
email?: string;
|
|
63
|
+
label: string;
|
|
64
|
+
}
|
|
65
|
+
export interface CurrentRepositoryUser extends RepositoryDeveloper {
|
|
66
|
+
aliases: string[];
|
|
67
|
+
}
|
|
68
|
+
export declare function getBranchPlanningContext(rootPath: string, options?: BranchPlanningOptions): Promise<BranchPlanningContext>;
|
|
69
|
+
export declare function extractTaskIds(value: string): string[];
|
|
70
|
+
export declare function suggestCommitMessage(rootPath: string, options?: BranchPlanningOptions): Promise<CommitMessageSuggestion>;
|
|
71
|
+
export declare function validateCommitMessage(rootPath: string, message: string): Promise<CommitMessageValidationResult>;
|
|
72
|
+
export declare function getRepositoryDevelopers(rootPath: string): Promise<RepositoryDeveloper[]>;
|
|
73
|
+
export declare function getCurrentRepositoryUser(rootPath: string): Promise<CurrentRepositoryUser | undefined>;
|
|
74
|
+
//# sourceMappingURL=git.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"git.d.ts","sourceRoot":"","sources":["../src/git.ts"],"names":[],"mappings":"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"}
|
package/dist/git.js
ADDED
|
@@ -0,0 +1,426 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Git-aware planning helpers
|
|
4
|
+
*/
|
|
5
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
6
|
+
if (k2 === undefined) k2 = k;
|
|
7
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
8
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
9
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
10
|
+
}
|
|
11
|
+
Object.defineProperty(o, k2, desc);
|
|
12
|
+
}) : (function(o, m, k, k2) {
|
|
13
|
+
if (k2 === undefined) k2 = k;
|
|
14
|
+
o[k2] = m[k];
|
|
15
|
+
}));
|
|
16
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
17
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
18
|
+
}) : function(o, v) {
|
|
19
|
+
o["default"] = v;
|
|
20
|
+
});
|
|
21
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
22
|
+
var ownKeys = function(o) {
|
|
23
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
24
|
+
var ar = [];
|
|
25
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
26
|
+
return ar;
|
|
27
|
+
};
|
|
28
|
+
return ownKeys(o);
|
|
29
|
+
};
|
|
30
|
+
return function (mod) {
|
|
31
|
+
if (mod && mod.__esModule) return mod;
|
|
32
|
+
var result = {};
|
|
33
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
34
|
+
__setModuleDefault(result, mod);
|
|
35
|
+
return result;
|
|
36
|
+
};
|
|
37
|
+
})();
|
|
38
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
39
|
+
exports.getBranchPlanningContext = getBranchPlanningContext;
|
|
40
|
+
exports.extractTaskIds = extractTaskIds;
|
|
41
|
+
exports.suggestCommitMessage = suggestCommitMessage;
|
|
42
|
+
exports.validateCommitMessage = validateCommitMessage;
|
|
43
|
+
exports.getRepositoryDevelopers = getRepositoryDevelopers;
|
|
44
|
+
exports.getCurrentRepositoryUser = getCurrentRepositoryUser;
|
|
45
|
+
const child_process_1 = require("child_process");
|
|
46
|
+
const path = __importStar(require("path"));
|
|
47
|
+
const util_1 = require("util");
|
|
48
|
+
const loader_1 = require("./loader");
|
|
49
|
+
const repository_1 = require("./repository");
|
|
50
|
+
const execFileAsync = (0, util_1.promisify)(child_process_1.execFile);
|
|
51
|
+
async function getBranchPlanningContext(rootPath, options = {}) {
|
|
52
|
+
const repository = await (0, repository_1.loadRepository)(rootPath);
|
|
53
|
+
const currentBranch = await getCurrentBranch(rootPath);
|
|
54
|
+
const comparisonRef = options.baseRef ?? await getDefaultComparisonRef(rootPath);
|
|
55
|
+
const baseRef = displayBaseRef(comparisonRef);
|
|
56
|
+
const diffOutput = await git(rootPath, [
|
|
57
|
+
'diff',
|
|
58
|
+
'--name-status',
|
|
59
|
+
`${comparisonRef}...HEAD`,
|
|
60
|
+
'--',
|
|
61
|
+
'.planfs'
|
|
62
|
+
]);
|
|
63
|
+
const statusOutput = await git(rootPath, ['status', '--porcelain', '--', '.planfs']);
|
|
64
|
+
const changedFiles = mergeChangedFiles([
|
|
65
|
+
...parseChangedFiles(diffOutput),
|
|
66
|
+
...parsePorcelainChangedFiles(statusOutput)
|
|
67
|
+
]);
|
|
68
|
+
const conflicts = parseConflicts(statusOutput);
|
|
69
|
+
const addedTasks = [];
|
|
70
|
+
const modifiedTasks = [];
|
|
71
|
+
const deletedTaskIds = [];
|
|
72
|
+
for (const file of changedFiles) {
|
|
73
|
+
if (file.entityType !== 'task' || !file.entityId) {
|
|
74
|
+
continue;
|
|
75
|
+
}
|
|
76
|
+
if (file.status === 'deleted') {
|
|
77
|
+
deletedTaskIds.push(file.entityId);
|
|
78
|
+
continue;
|
|
79
|
+
}
|
|
80
|
+
const task = repository.tasks.get(file.entityId);
|
|
81
|
+
if (!task) {
|
|
82
|
+
continue;
|
|
83
|
+
}
|
|
84
|
+
const change = {
|
|
85
|
+
id: task.id,
|
|
86
|
+
title: task.title,
|
|
87
|
+
status: task.status,
|
|
88
|
+
filePath: file.path
|
|
89
|
+
};
|
|
90
|
+
if (file.status === 'modified' || file.status === 'renamed') {
|
|
91
|
+
const previous = await loadBaseTask(rootPath, comparisonRef, file.previousPath ?? file.path);
|
|
92
|
+
if (previous) {
|
|
93
|
+
change.previous = {
|
|
94
|
+
title: previous.title,
|
|
95
|
+
status: previous.status
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
modifiedTasks.push(change);
|
|
99
|
+
}
|
|
100
|
+
else if (file.status === 'added' || file.status === 'copied') {
|
|
101
|
+
addedTasks.push(change);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
const taskIdsInBranchName = extractTaskIds(currentBranch);
|
|
105
|
+
const relatedTaskIds = unique([
|
|
106
|
+
...taskIdsInBranchName,
|
|
107
|
+
...addedTasks.map(task => task.id),
|
|
108
|
+
...modifiedTasks.map(task => task.id),
|
|
109
|
+
...deletedTaskIds
|
|
110
|
+
]);
|
|
111
|
+
return {
|
|
112
|
+
currentBranch,
|
|
113
|
+
baseRef,
|
|
114
|
+
comparisonRef,
|
|
115
|
+
taskIdsInBranchName,
|
|
116
|
+
relatedTaskIds,
|
|
117
|
+
changedFiles,
|
|
118
|
+
addedTasks,
|
|
119
|
+
modifiedTasks,
|
|
120
|
+
deletedTaskIds,
|
|
121
|
+
conflicts,
|
|
122
|
+
pullRequestPreview: {
|
|
123
|
+
title: createPreviewTitle(currentBranch, relatedTaskIds),
|
|
124
|
+
summary: createPreviewSummary(addedTasks, modifiedTasks, deletedTaskIds, conflicts),
|
|
125
|
+
relatedTaskIds
|
|
126
|
+
}
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
function extractTaskIds(value) {
|
|
130
|
+
return unique(value.match(/TASK-\d+/gi)?.map(match => match.toUpperCase()) ?? []);
|
|
131
|
+
}
|
|
132
|
+
async function suggestCommitMessage(rootPath, options = {}) {
|
|
133
|
+
const repository = await (0, repository_1.loadRepository)(rootPath);
|
|
134
|
+
const context = await getBranchPlanningContext(rootPath, options);
|
|
135
|
+
const primaryTaskId = context.relatedTaskIds[0];
|
|
136
|
+
if (primaryTaskId) {
|
|
137
|
+
const task = repository.tasks.get(primaryTaskId);
|
|
138
|
+
return {
|
|
139
|
+
message: `${primaryTaskId}: ${task?.title ?? branchToTitle(context.currentBranch)}`,
|
|
140
|
+
taskIds: context.relatedTaskIds,
|
|
141
|
+
source: 'related-task'
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
return {
|
|
145
|
+
message: branchToTitle(context.currentBranch),
|
|
146
|
+
taskIds: [],
|
|
147
|
+
source: 'branch-name'
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
async function validateCommitMessage(rootPath, message) {
|
|
151
|
+
const repository = await (0, repository_1.loadRepository)(rootPath);
|
|
152
|
+
const taskIds = extractTaskIds(message);
|
|
153
|
+
const missingTaskIds = taskIds.filter(id => !repository.tasks.has(id));
|
|
154
|
+
const warnings = taskIds.length === 0
|
|
155
|
+
? ['Commit message does not reference a PlanFS task ID.']
|
|
156
|
+
: [];
|
|
157
|
+
return {
|
|
158
|
+
valid: missingTaskIds.length === 0,
|
|
159
|
+
taskIds,
|
|
160
|
+
missingTaskIds,
|
|
161
|
+
warnings
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
async function getRepositoryDevelopers(rootPath) {
|
|
165
|
+
try {
|
|
166
|
+
const output = await git(rootPath, [
|
|
167
|
+
'log',
|
|
168
|
+
'--format=%an%x00%ae%x00%cn%x00%ce',
|
|
169
|
+
'--all',
|
|
170
|
+
'--max-count=500'
|
|
171
|
+
]);
|
|
172
|
+
const developers = new Map();
|
|
173
|
+
for (const line of output.split('\n').filter(Boolean)) {
|
|
174
|
+
const [authorName, authorEmail, committerName, committerEmail] = line.split('\0');
|
|
175
|
+
addDeveloper(developers, authorName, authorEmail);
|
|
176
|
+
addDeveloper(developers, committerName, committerEmail);
|
|
177
|
+
}
|
|
178
|
+
return Array.from(developers.values()).sort((a, b) => a.label.localeCompare(b.label));
|
|
179
|
+
}
|
|
180
|
+
catch {
|
|
181
|
+
return [];
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
async function getCurrentRepositoryUser(rootPath) {
|
|
185
|
+
try {
|
|
186
|
+
const [name, email] = await Promise.all([
|
|
187
|
+
git(rootPath, ['config', 'user.name']),
|
|
188
|
+
git(rootPath, ['config', 'user.email'])
|
|
189
|
+
]);
|
|
190
|
+
const developer = createDeveloper(name.trim(), email.trim());
|
|
191
|
+
if (!developer) {
|
|
192
|
+
return undefined;
|
|
193
|
+
}
|
|
194
|
+
return {
|
|
195
|
+
...developer,
|
|
196
|
+
aliases: developerAliases(developer)
|
|
197
|
+
};
|
|
198
|
+
}
|
|
199
|
+
catch {
|
|
200
|
+
return undefined;
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
function parseChangedFiles(output) {
|
|
204
|
+
return output
|
|
205
|
+
.split('\n')
|
|
206
|
+
.map(line => line.trim())
|
|
207
|
+
.filter(Boolean)
|
|
208
|
+
.map(line => {
|
|
209
|
+
const parts = line.split('\t');
|
|
210
|
+
const code = parts[0] ?? '';
|
|
211
|
+
const status = parseStatusCode(code);
|
|
212
|
+
const previousPath = status === 'renamed' || status === 'copied'
|
|
213
|
+
? parts[1]
|
|
214
|
+
: undefined;
|
|
215
|
+
const filePath = previousPath ? parts[2] : parts[1];
|
|
216
|
+
const entity = filePath ? entityFromPath(filePath) : {};
|
|
217
|
+
return {
|
|
218
|
+
path: filePath ?? '',
|
|
219
|
+
previousPath,
|
|
220
|
+
status,
|
|
221
|
+
...entity
|
|
222
|
+
};
|
|
223
|
+
})
|
|
224
|
+
.filter(file => file.path.endsWith('.md'));
|
|
225
|
+
}
|
|
226
|
+
function parsePorcelainChangedFiles(output) {
|
|
227
|
+
return output
|
|
228
|
+
.split('\n')
|
|
229
|
+
.filter(Boolean)
|
|
230
|
+
.filter(line => !isUnmergedStatus(line.slice(0, 2)))
|
|
231
|
+
.map(line => {
|
|
232
|
+
const code = line.slice(0, 2);
|
|
233
|
+
const status = parseStatusCode(code.trim());
|
|
234
|
+
const filePath = line.slice(3).trim();
|
|
235
|
+
const entity = entityFromPath(filePath);
|
|
236
|
+
return {
|
|
237
|
+
path: filePath,
|
|
238
|
+
status,
|
|
239
|
+
...entity
|
|
240
|
+
};
|
|
241
|
+
})
|
|
242
|
+
.filter(file => file.path.startsWith('.planfs/') && file.path.endsWith('.md'));
|
|
243
|
+
}
|
|
244
|
+
function mergeChangedFiles(files) {
|
|
245
|
+
const byPath = new Map();
|
|
246
|
+
for (const file of files) {
|
|
247
|
+
const existing = byPath.get(file.path);
|
|
248
|
+
if (!existing || existing.status === 'modified') {
|
|
249
|
+
byPath.set(file.path, file);
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
return Array.from(byPath.values());
|
|
253
|
+
}
|
|
254
|
+
function parseConflicts(output) {
|
|
255
|
+
return output
|
|
256
|
+
.split('\n')
|
|
257
|
+
.filter(line => isUnmergedStatus(line.slice(0, 2)))
|
|
258
|
+
.map(line => {
|
|
259
|
+
const status = line.slice(0, 2);
|
|
260
|
+
const filePath = line.slice(3).trim();
|
|
261
|
+
return {
|
|
262
|
+
path: filePath,
|
|
263
|
+
status,
|
|
264
|
+
suggestion: conflictSuggestion(status)
|
|
265
|
+
};
|
|
266
|
+
})
|
|
267
|
+
.filter(conflict => conflict.path.startsWith('.planfs/'));
|
|
268
|
+
}
|
|
269
|
+
function parseStatusCode(code) {
|
|
270
|
+
if (code === '??')
|
|
271
|
+
return 'added';
|
|
272
|
+
if (code.startsWith('A'))
|
|
273
|
+
return 'added';
|
|
274
|
+
if (code.startsWith('M'))
|
|
275
|
+
return 'modified';
|
|
276
|
+
if (code.startsWith('D'))
|
|
277
|
+
return 'deleted';
|
|
278
|
+
if (code.startsWith('R'))
|
|
279
|
+
return 'renamed';
|
|
280
|
+
if (code.startsWith('C'))
|
|
281
|
+
return 'copied';
|
|
282
|
+
if (isUnmergedStatus(code))
|
|
283
|
+
return 'unmerged';
|
|
284
|
+
return 'unknown';
|
|
285
|
+
}
|
|
286
|
+
function isUnmergedStatus(status) {
|
|
287
|
+
return ['DD', 'AU', 'UD', 'UA', 'DU', 'AA', 'UU'].includes(status);
|
|
288
|
+
}
|
|
289
|
+
function conflictSuggestion(status) {
|
|
290
|
+
switch (status) {
|
|
291
|
+
case 'AA':
|
|
292
|
+
return 'Both branches added this PlanFS file. Keep one task ID, merge useful body text, and remove duplicate metadata.';
|
|
293
|
+
case 'UU':
|
|
294
|
+
return 'Both branches edited this PlanFS file. Preserve YAML frontmatter, merge Markdown body changes, then run validation.';
|
|
295
|
+
case 'DU':
|
|
296
|
+
case 'UD':
|
|
297
|
+
return 'One branch deleted this PlanFS file while the other edited it. Decide whether the task should remain, then validate references.';
|
|
298
|
+
default:
|
|
299
|
+
return 'Resolve the PlanFS file conflict, keep valid YAML frontmatter, and run planfs validate before merging.';
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
async function loadBaseTask(rootPath, comparisonRef, filePath) {
|
|
303
|
+
try {
|
|
304
|
+
const content = await git(rootPath, ['show', `${comparisonRef}:${filePath}`]);
|
|
305
|
+
const entity = (0, loader_1.loadEntity)(discoveredFile(rootPath, filePath), content);
|
|
306
|
+
return entity.type === 'task' ? entity : undefined;
|
|
307
|
+
}
|
|
308
|
+
catch {
|
|
309
|
+
return undefined;
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
function entityFromPath(filePath) {
|
|
313
|
+
const parts = filePath.split('/');
|
|
314
|
+
const directory = parts[parts.length - 2];
|
|
315
|
+
const filename = parts[parts.length - 1];
|
|
316
|
+
if (!filename) {
|
|
317
|
+
return {};
|
|
318
|
+
}
|
|
319
|
+
const typeByDirectory = {
|
|
320
|
+
tasks: 'task',
|
|
321
|
+
epics: 'epic',
|
|
322
|
+
milestones: 'milestone',
|
|
323
|
+
decisions: 'decision'
|
|
324
|
+
};
|
|
325
|
+
const entityType = directory ? typeByDirectory[directory] : undefined;
|
|
326
|
+
return {
|
|
327
|
+
entityType,
|
|
328
|
+
entityId: entityType ? (0, loader_1.getIdFromFilename)(filename) : undefined
|
|
329
|
+
};
|
|
330
|
+
}
|
|
331
|
+
function discoveredFile(rootPath, filePath) {
|
|
332
|
+
const entity = entityFromPath(filePath);
|
|
333
|
+
if (!entity.entityType) {
|
|
334
|
+
throw new Error(`Unsupported PlanFS path: ${filePath}`);
|
|
335
|
+
}
|
|
336
|
+
return {
|
|
337
|
+
path: path.join(rootPath, filePath),
|
|
338
|
+
name: path.basename(filePath),
|
|
339
|
+
type: entity.entityType
|
|
340
|
+
};
|
|
341
|
+
}
|
|
342
|
+
async function getCurrentBranch(rootPath) {
|
|
343
|
+
const branch = await git(rootPath, ['rev-parse', '--abbrev-ref', 'HEAD']);
|
|
344
|
+
return branch.trim();
|
|
345
|
+
}
|
|
346
|
+
async function getDefaultComparisonRef(rootPath) {
|
|
347
|
+
try {
|
|
348
|
+
const originHead = await git(rootPath, [
|
|
349
|
+
'symbolic-ref',
|
|
350
|
+
'refs/remotes/origin/HEAD',
|
|
351
|
+
'--short'
|
|
352
|
+
]);
|
|
353
|
+
return originHead.trim();
|
|
354
|
+
}
|
|
355
|
+
catch {
|
|
356
|
+
return 'main';
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
function displayBaseRef(ref) {
|
|
360
|
+
return ref.replace(/^origin\//, '');
|
|
361
|
+
}
|
|
362
|
+
function createPreviewTitle(branch, relatedTaskIds) {
|
|
363
|
+
const taskPrefix = relatedTaskIds.length > 0 ? `${relatedTaskIds.join(', ')}: ` : '';
|
|
364
|
+
return `${taskPrefix}${branch}`;
|
|
365
|
+
}
|
|
366
|
+
function createPreviewSummary(addedTasks, modifiedTasks, deletedTaskIds, conflicts) {
|
|
367
|
+
const parts = [
|
|
368
|
+
`${addedTasks.length} added task${addedTasks.length === 1 ? '' : 's'}`,
|
|
369
|
+
`${modifiedTasks.length} modified task${modifiedTasks.length === 1 ? '' : 's'}`,
|
|
370
|
+
`${deletedTaskIds.length} deleted task${deletedTaskIds.length === 1 ? '' : 's'}`
|
|
371
|
+
];
|
|
372
|
+
if (conflicts.length > 0) {
|
|
373
|
+
parts.push(`${conflicts.length} PlanFS conflict${conflicts.length === 1 ? '' : 's'}`);
|
|
374
|
+
}
|
|
375
|
+
return parts.join(', ');
|
|
376
|
+
}
|
|
377
|
+
function branchToTitle(branch) {
|
|
378
|
+
return branch
|
|
379
|
+
.replace(/^refs\/heads\//, '')
|
|
380
|
+
.replace(/^(feature|fix|bugfix|hotfix|chore|docs|task)\//, '')
|
|
381
|
+
.replace(/TASK-\d+/gi, '')
|
|
382
|
+
.replace(/[-_]+/g, ' ')
|
|
383
|
+
.trim()
|
|
384
|
+
.replace(/\s+/g, ' ') || branch;
|
|
385
|
+
}
|
|
386
|
+
function unique(values) {
|
|
387
|
+
return Array.from(new Set(values));
|
|
388
|
+
}
|
|
389
|
+
function addDeveloper(developers, name, email) {
|
|
390
|
+
const developer = createDeveloper(name, email);
|
|
391
|
+
const key = (developer?.email || developer?.name || '').toLowerCase();
|
|
392
|
+
if (!developer || !key || developers.has(key)) {
|
|
393
|
+
return;
|
|
394
|
+
}
|
|
395
|
+
developers.set(key, developer);
|
|
396
|
+
}
|
|
397
|
+
function createDeveloper(name, email) {
|
|
398
|
+
const normalizedName = name?.trim();
|
|
399
|
+
const normalizedEmail = email?.trim();
|
|
400
|
+
if (!normalizedName && !normalizedEmail) {
|
|
401
|
+
return undefined;
|
|
402
|
+
}
|
|
403
|
+
const label = normalizedName && normalizedEmail
|
|
404
|
+
? `${normalizedName} <${normalizedEmail}>`
|
|
405
|
+
: normalizedName || normalizedEmail || '';
|
|
406
|
+
return {
|
|
407
|
+
name: normalizedName || normalizedEmail || '',
|
|
408
|
+
email: normalizedEmail || undefined,
|
|
409
|
+
label
|
|
410
|
+
};
|
|
411
|
+
}
|
|
412
|
+
function developerAliases(developer) {
|
|
413
|
+
return unique([
|
|
414
|
+
developer.name,
|
|
415
|
+
developer.email ?? '',
|
|
416
|
+
developer.label
|
|
417
|
+
].map(value => value.trim()).filter(Boolean));
|
|
418
|
+
}
|
|
419
|
+
async function git(rootPath, args) {
|
|
420
|
+
const result = await execFileAsync('git', args, {
|
|
421
|
+
cwd: rootPath,
|
|
422
|
+
maxBuffer: 1024 * 1024
|
|
423
|
+
});
|
|
424
|
+
return result.stdout.replace(/\s+$/, '');
|
|
425
|
+
}
|
|
426
|
+
//# sourceMappingURL=git.js.map
|