project-librarian 0.5.3 → 0.5.5
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.ko.md +16 -2
- package/README.md +16 -2
- package/SKILL.md +14 -1
- package/dist/args.js +66 -2
- package/dist/code-index/ownership.js +8 -6
- package/dist/code-index-db.js +20 -12
- package/dist/code-index-file-policy.js +8 -10
- package/dist/code-index.js +4 -3
- package/dist/hooks.js +77 -5
- package/dist/init-project-wiki.js +58 -1
- package/dist/install-skill.js +99 -6
- package/dist/mcp-server.js +4 -4
- package/dist/migration.js +27 -2
- package/dist/modes.js +12 -5
- package/dist/session-handoff.js +627 -0
- package/dist/templates.js +5 -3
- package/dist/workspace.js +137 -10
- package/package.json +4 -2
|
@@ -7,6 +7,7 @@ const hooks_1 = require("./hooks");
|
|
|
7
7
|
const install_skill_1 = require("./install-skill");
|
|
8
8
|
const modes_1 = require("./modes");
|
|
9
9
|
const migration_1 = require("./migration");
|
|
10
|
+
const session_handoff_1 = require("./session-handoff");
|
|
10
11
|
const templates_1 = require("./templates");
|
|
11
12
|
const wiki_visualizer_1 = require("./wiki-visualizer");
|
|
12
13
|
const workspace_1 = require("./workspace");
|
|
@@ -16,7 +17,7 @@ function codeIndex() {
|
|
|
16
17
|
function printUsage() {
|
|
17
18
|
console.log(`Usage:
|
|
18
19
|
project-librarian [init|update] [options]
|
|
19
|
-
project-librarian install [--scope user|project] [--agents codex|claude|cursor|gemini|all]
|
|
20
|
+
project-librarian install [--scope user|project] [--agents codex|claude|cursor|gemini|all] [--dry-run]
|
|
20
21
|
project-librarian mcp
|
|
21
22
|
|
|
22
23
|
Options:
|
|
@@ -33,12 +34,23 @@ Options:
|
|
|
33
34
|
--issue-draft Print a problem/side-effect GitHub issue body draft.
|
|
34
35
|
--issue-body-file <path> With --issue-create, use an existing Markdown body file.
|
|
35
36
|
--issue-title <title> Override the generated issue draft title.
|
|
37
|
+
--dry-run With install, preview copied skill files without writing them.
|
|
36
38
|
--query <terms> Search wiki paths, metadata, titles, and bodies (answer-shaped, capped output).
|
|
37
39
|
--wiki-impact <page-or-term> Show wiki backlinks, decision_ref citations, and router depth for matching pages.
|
|
38
40
|
--wiki-visualize Write a static wiki graph visualizer to .project-wiki/wiki-graph.html.
|
|
39
41
|
--wiki-visualize-out <path> With --wiki-visualize, write under a custom .project-wiki/ path.
|
|
40
42
|
--refresh-index Update the managed auto-discovered wiki index block.
|
|
41
43
|
--capture-inbox Append a candidate note with --title, --content, and optional --category.
|
|
44
|
+
--handoff-save Save local generated session handoff state under .project-wiki/session/.
|
|
45
|
+
--handoff-show Print the current local session handoff.
|
|
46
|
+
--handoff-status Print JSON status for the local session handoff.
|
|
47
|
+
--handoff-clear Remove generated session handoff files.
|
|
48
|
+
--handoff-promote-inbox Promote selected generated handoff facts into wiki/inbox/project-candidates.md.
|
|
49
|
+
--handoff-injection-enable Opt in to capped full handoff injection in startup hooks.
|
|
50
|
+
--handoff-injection-disable Remove the generated full handoff injection opt-in.
|
|
51
|
+
--handoff-injection-status Print JSON status for the full handoff injection experiment.
|
|
52
|
+
--goal, --state, --blocked With --handoff-save, provide resume context fields.
|
|
53
|
+
--next, --decision With --handoff-save, repeat for next actions and decisions.
|
|
42
54
|
--glossary-init Create and route the optional glossary page.
|
|
43
55
|
--agents <list> With init/update, write only selected agent surfaces: codex, claude, cursor, gemini, or all. Existing project skill/setup surfaces are preserved by default.
|
|
44
56
|
--prune-check Report active pages with stale or unresolved signals.
|
|
@@ -161,6 +173,24 @@ if (args_1.codeIndexIncrementalMode && args_1.codeIndexFullMode) {
|
|
|
161
173
|
console.error("Use one code index update mode at a time: --incremental or --code-index-full.");
|
|
162
174
|
process.exit(1);
|
|
163
175
|
}
|
|
176
|
+
const activeHandoffModes = [
|
|
177
|
+
args_1.handoffSaveMode ? "--handoff-save" : "",
|
|
178
|
+
args_1.handoffShowMode ? "--handoff-show" : "",
|
|
179
|
+
args_1.handoffStatusMode ? "--handoff-status" : "",
|
|
180
|
+
args_1.handoffClearMode ? "--handoff-clear" : "",
|
|
181
|
+
args_1.handoffPromoteInboxMode ? "--handoff-promote-inbox" : "",
|
|
182
|
+
args_1.handoffInjectionEnableMode ? "--handoff-injection-enable" : "",
|
|
183
|
+
args_1.handoffInjectionDisableMode ? "--handoff-injection-disable" : "",
|
|
184
|
+
args_1.handoffInjectionStatusMode ? "--handoff-injection-status" : "",
|
|
185
|
+
].filter(Boolean);
|
|
186
|
+
if (activeHandoffModes.length > 1) {
|
|
187
|
+
console.error(`Use one session handoff mode at a time: ${activeHandoffModes.join(", ")}.`);
|
|
188
|
+
process.exit(1);
|
|
189
|
+
}
|
|
190
|
+
if (args_1.handoffInputMode && !args_1.handoffSaveMode) {
|
|
191
|
+
console.error("--goal, --state, --blocked, --next, --decision, --open-question, --last-success-command, --last-failure-command, and --verification are only supported with --handoff-save.");
|
|
192
|
+
process.exit(1);
|
|
193
|
+
}
|
|
164
194
|
if (args_1.command === "install" || args_1.command === "install-skill") {
|
|
165
195
|
(0, install_skill_1.runInstallSkillMode)();
|
|
166
196
|
process.exit(0);
|
|
@@ -176,6 +206,33 @@ else {
|
|
|
176
206
|
runInitCommand();
|
|
177
207
|
}
|
|
178
208
|
function runInitCommand() {
|
|
209
|
+
const activeHandoffMode = activeHandoffModes[0];
|
|
210
|
+
if (activeHandoffMode) {
|
|
211
|
+
try {
|
|
212
|
+
if (args_1.handoffSaveMode)
|
|
213
|
+
(0, session_handoff_1.runHandoffSaveMode)();
|
|
214
|
+
else if (args_1.handoffShowMode)
|
|
215
|
+
(0, session_handoff_1.runHandoffShowMode)();
|
|
216
|
+
else if (args_1.handoffStatusMode)
|
|
217
|
+
(0, session_handoff_1.runHandoffStatusMode)();
|
|
218
|
+
else if (args_1.handoffClearMode)
|
|
219
|
+
(0, session_handoff_1.runHandoffClearMode)();
|
|
220
|
+
else if (args_1.handoffPromoteInboxMode)
|
|
221
|
+
(0, session_handoff_1.runHandoffPromoteInboxMode)();
|
|
222
|
+
else if (args_1.handoffInjectionEnableMode)
|
|
223
|
+
(0, session_handoff_1.runHandoffInjectionEnableMode)();
|
|
224
|
+
else if (args_1.handoffInjectionDisableMode)
|
|
225
|
+
(0, session_handoff_1.runHandoffInjectionDisableMode)();
|
|
226
|
+
else if (args_1.handoffInjectionStatusMode)
|
|
227
|
+
(0, session_handoff_1.runHandoffInjectionStatusMode)();
|
|
228
|
+
exitAfterStdoutDrain(0);
|
|
229
|
+
}
|
|
230
|
+
catch (error) {
|
|
231
|
+
console.error(error instanceof Error ? error.message : String(error));
|
|
232
|
+
process.exit(1);
|
|
233
|
+
}
|
|
234
|
+
return;
|
|
235
|
+
}
|
|
179
236
|
const activeCodeModes = activeCodeEvidenceCliModes();
|
|
180
237
|
if (activeCodeModes.length > 1) {
|
|
181
238
|
console.error("Use one code evidence mode at a time: --code-index, --code-index-health, --code-query, --code-report, --code-status, --code-files, --code-impact, --code-context-pack, or --code-search-symbol.");
|
package/dist/install-skill.js
CHANGED
|
@@ -112,26 +112,119 @@ function installTarget(agent, scope) {
|
|
|
112
112
|
const base = scope === "user" ? userAgentRoot(agent) : path.join(process.cwd(), projectAgentRoot(agent));
|
|
113
113
|
return path.join(base, "skills", skillName);
|
|
114
114
|
}
|
|
115
|
+
function assertNoTargetSymlink(targetRoot, target, includeLeaf) {
|
|
116
|
+
const rootResolved = path.resolve(targetRoot);
|
|
117
|
+
const targetResolved = path.resolve(target);
|
|
118
|
+
if (targetResolved !== rootResolved && !targetResolved.startsWith(`${rootResolved}${path.sep}`)) {
|
|
119
|
+
fail(`skill install target escaped target root: ${target}`);
|
|
120
|
+
}
|
|
121
|
+
if (fs.existsSync(rootResolved) && fs.lstatSync(rootResolved).isSymbolicLink()) {
|
|
122
|
+
fail("skill install refuses to follow destination symlink: .");
|
|
123
|
+
}
|
|
124
|
+
const relative = path.relative(rootResolved, targetResolved);
|
|
125
|
+
const parts = relative ? relative.split(path.sep).filter(Boolean) : [];
|
|
126
|
+
const checkedParts = includeLeaf ? parts : parts.slice(0, -1);
|
|
127
|
+
let current = rootResolved;
|
|
128
|
+
for (const part of checkedParts) {
|
|
129
|
+
current = path.join(current, part);
|
|
130
|
+
if (!fs.existsSync(current))
|
|
131
|
+
continue;
|
|
132
|
+
const stat = fs.lstatSync(current);
|
|
133
|
+
if (stat.isSymbolicLink()) {
|
|
134
|
+
fail(`skill install refuses to follow destination symlink: ${path.relative(rootResolved, current) || "."}`);
|
|
135
|
+
}
|
|
136
|
+
if (current !== targetResolved && !stat.isDirectory()) {
|
|
137
|
+
fail(`skill install target has a non-directory path component: ${path.relative(rootResolved, current)}`);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
function isInsidePath(base, target) {
|
|
142
|
+
const baseResolved = path.resolve(base);
|
|
143
|
+
const targetResolved = path.resolve(target);
|
|
144
|
+
return targetResolved === baseResolved || targetResolved.startsWith(`${baseResolved}${path.sep}`);
|
|
145
|
+
}
|
|
146
|
+
function mkdirFromBaseNoSymlink(base, target) {
|
|
147
|
+
const baseResolved = path.resolve(base);
|
|
148
|
+
const targetResolved = path.resolve(target);
|
|
149
|
+
if (!isInsidePath(baseResolved, targetResolved)) {
|
|
150
|
+
fail(`skill install target escaped checked base: ${target}`);
|
|
151
|
+
}
|
|
152
|
+
let current = baseResolved;
|
|
153
|
+
const parts = path.relative(baseResolved, targetResolved).split(path.sep).filter(Boolean);
|
|
154
|
+
for (const part of parts) {
|
|
155
|
+
current = path.join(current, part);
|
|
156
|
+
if (fs.existsSync(current)) {
|
|
157
|
+
const stat = fs.lstatSync(current);
|
|
158
|
+
if (stat.isSymbolicLink()) {
|
|
159
|
+
fail(`skill install refuses to follow destination symlink: ${path.relative(baseResolved, current)}`);
|
|
160
|
+
}
|
|
161
|
+
if (!stat.isDirectory()) {
|
|
162
|
+
fail(`skill install target has a non-directory path component: ${path.relative(baseResolved, current)}`);
|
|
163
|
+
}
|
|
164
|
+
continue;
|
|
165
|
+
}
|
|
166
|
+
fs.mkdirSync(current);
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
function mkdirpNoTargetSymlink(targetRoot, target) {
|
|
170
|
+
const rootResolved = path.resolve(targetRoot);
|
|
171
|
+
const targetResolved = path.resolve(target);
|
|
172
|
+
const cwdResolved = path.resolve(process.cwd());
|
|
173
|
+
if (isInsidePath(cwdResolved, rootResolved)) {
|
|
174
|
+
mkdirFromBaseNoSymlink(cwdResolved, rootResolved);
|
|
175
|
+
}
|
|
176
|
+
else {
|
|
177
|
+
fs.mkdirSync(rootResolved, { recursive: true });
|
|
178
|
+
}
|
|
179
|
+
const relative = path.relative(rootResolved, targetResolved);
|
|
180
|
+
let current = rootResolved;
|
|
181
|
+
if (!relative) {
|
|
182
|
+
if (fs.existsSync(current) && fs.lstatSync(current).isSymbolicLink()) {
|
|
183
|
+
fail("skill install refuses to follow destination symlink: .");
|
|
184
|
+
}
|
|
185
|
+
if (!fs.existsSync(current))
|
|
186
|
+
fs.mkdirSync(current);
|
|
187
|
+
return;
|
|
188
|
+
}
|
|
189
|
+
for (const part of relative.split(path.sep).filter(Boolean)) {
|
|
190
|
+
current = path.join(current, part);
|
|
191
|
+
if (fs.existsSync(current)) {
|
|
192
|
+
const stat = fs.lstatSync(current);
|
|
193
|
+
if (stat.isSymbolicLink()) {
|
|
194
|
+
fail(`skill install refuses to follow destination symlink: ${path.relative(rootResolved, current)}`);
|
|
195
|
+
}
|
|
196
|
+
if (!stat.isDirectory()) {
|
|
197
|
+
fail(`skill install target has a non-directory path component: ${path.relative(rootResolved, current)}`);
|
|
198
|
+
}
|
|
199
|
+
continue;
|
|
200
|
+
}
|
|
201
|
+
fs.mkdirSync(current);
|
|
202
|
+
}
|
|
203
|
+
}
|
|
115
204
|
function sameFile(source, target) {
|
|
116
|
-
if (!fs.existsSync(target)
|
|
205
|
+
if (!fs.existsSync(target))
|
|
206
|
+
return false;
|
|
207
|
+
const stat = fs.lstatSync(target);
|
|
208
|
+
if (stat.isSymbolicLink() || !stat.isFile())
|
|
117
209
|
return false;
|
|
118
210
|
return fs.readFileSync(source).equals(fs.readFileSync(target));
|
|
119
211
|
}
|
|
120
|
-
function copyPath(source, target, dryRun) {
|
|
212
|
+
function copyPath(source, target, targetRoot, dryRun) {
|
|
121
213
|
if (!fs.existsSync(source))
|
|
122
214
|
fail(`missing package file: ${source}`);
|
|
123
215
|
const existed = fs.existsSync(target);
|
|
124
216
|
if (dryRun)
|
|
125
217
|
return "dry-run";
|
|
126
218
|
const sourceStat = fs.statSync(source);
|
|
219
|
+
assertNoTargetSymlink(targetRoot, target, true);
|
|
127
220
|
if (sourceStat.isDirectory()) {
|
|
128
|
-
|
|
221
|
+
mkdirpNoTargetSymlink(targetRoot, target);
|
|
129
222
|
for (const entry of fs.readdirSync(source, { withFileTypes: true })) {
|
|
130
|
-
copyPath(path.join(source, entry.name), path.join(target, entry.name), false);
|
|
223
|
+
copyPath(path.join(source, entry.name), path.join(target, entry.name), targetRoot, false);
|
|
131
224
|
}
|
|
132
225
|
return existed ? "updated" : "created";
|
|
133
226
|
}
|
|
134
|
-
|
|
227
|
+
mkdirpNoTargetSymlink(targetRoot, path.dirname(target));
|
|
135
228
|
if (sameFile(source, target))
|
|
136
229
|
return "exists";
|
|
137
230
|
fs.copyFileSync(source, target);
|
|
@@ -146,7 +239,7 @@ function copyPackageFiles(targetRoot, dryRun, labelRoot = targetRoot) {
|
|
|
146
239
|
return packageFiles.map((relativePath) => {
|
|
147
240
|
const source = path.join(root, relativePath);
|
|
148
241
|
const target = path.join(targetRoot, relativePath);
|
|
149
|
-
return [path.join(labelRoot, relativePath), copyPath(source, target, dryRun)];
|
|
242
|
+
return [path.join(labelRoot, relativePath), copyPath(source, target, targetRoot, dryRun)];
|
|
150
243
|
});
|
|
151
244
|
}
|
|
152
245
|
function syncProjectSkillInstall(agent) {
|
package/dist/mcp-server.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
// Hand-rolled MCP stdio server for the code-evidence index.
|
|
3
3
|
//
|
|
4
|
-
// Why hand-rolled: the
|
|
5
|
-
// node:sqlite
|
|
6
|
-
//
|
|
7
|
-
//
|
|
4
|
+
// Why hand-rolled: the package keeps the MCP surface dependency-light. Code
|
|
5
|
+
// evidence already depends on node:sqlite, the package's TypeScript dependency,
|
|
6
|
+
// and optional tree-sitter grammars; adopting @modelcontextprotocol/sdk would add
|
|
7
|
+
// another hard runtime dependency for a small stdio slice.
|
|
8
8
|
//
|
|
9
9
|
// Transport: MCP stdio transport carries one JSON-RPC message per line on stdin
|
|
10
10
|
// and stdout (newline-delimited JSON, "ndjson"); stderr is free for logging. We
|
package/dist/migration.js
CHANGED
|
@@ -57,6 +57,7 @@ exports.migrationCoverageStatusMap = migrationCoverageStatusMap;
|
|
|
57
57
|
exports.semanticStatusForInboxStatus = semanticStatusForInboxStatus;
|
|
58
58
|
exports.runReviewMigrationMode = runReviewMigrationMode;
|
|
59
59
|
const fs = __importStar(require("node:fs"));
|
|
60
|
+
const path = __importStar(require("node:path"));
|
|
60
61
|
const taxonomy_1 = require("./taxonomy");
|
|
61
62
|
const workspace_1 = require("./workspace");
|
|
62
63
|
const templates_1 = require("./templates");
|
|
@@ -317,9 +318,31 @@ function activeMigrationLegacyRoots() {
|
|
|
317
318
|
const verifiedRoot = migrationVerificationLegacyRoot();
|
|
318
319
|
return verifiedRoot ? [verifiedRoot] : legacyWikiRoots();
|
|
319
320
|
}
|
|
321
|
+
function migrationDirectoryPath(relativePath) {
|
|
322
|
+
const resolved = path.resolve(workspace_1.root, relativePath);
|
|
323
|
+
const rootResolved = path.resolve(workspace_1.root);
|
|
324
|
+
if (resolved !== rootResolved && !resolved.startsWith(`${rootResolved}${path.sep}`)) {
|
|
325
|
+
throw new Error(`migration directory must stay inside the project root: ${relativePath}`);
|
|
326
|
+
}
|
|
327
|
+
const stat = fs.lstatSync(resolved);
|
|
328
|
+
if (stat.isSymbolicLink()) {
|
|
329
|
+
throw new Error(`migration refuses to follow symlinked wiki directory: ${relativePath}`);
|
|
330
|
+
}
|
|
331
|
+
if (!stat.isDirectory()) {
|
|
332
|
+
throw new Error(`migration path is not a directory: ${relativePath}`);
|
|
333
|
+
}
|
|
334
|
+
const realPath = fs.realpathSync(resolved);
|
|
335
|
+
if (realPath !== rootResolved && !realPath.startsWith(`${rootResolved}${path.sep}`)) {
|
|
336
|
+
throw new Error(`migration directory must resolve inside the project root: ${relativePath}`);
|
|
337
|
+
}
|
|
338
|
+
return resolved;
|
|
339
|
+
}
|
|
320
340
|
function expectedMigrationUnits() {
|
|
321
341
|
return activeMigrationLegacyRoots()
|
|
322
|
-
.flatMap((legacyRoot) =>
|
|
342
|
+
.flatMap((legacyRoot) => {
|
|
343
|
+
const legacyPath = migrationDirectoryPath(legacyRoot);
|
|
344
|
+
return (0, wiki_files_1.walkMarkdownFiles)(legacyPath, [], legacyPath);
|
|
345
|
+
})
|
|
323
346
|
.flatMap((file) => extractMigrationUnits(file.basePath, (0, workspace_1.read)(file.path)));
|
|
324
347
|
}
|
|
325
348
|
function loadMigrationUnitContext() {
|
|
@@ -962,6 +985,7 @@ function nextLegacyPath() {
|
|
|
962
985
|
}
|
|
963
986
|
function prepareMigrationMode() {
|
|
964
987
|
if ((0, workspace_1.exists)("wiki")) {
|
|
988
|
+
migrationDirectoryPath("wiki");
|
|
965
989
|
const legacyPath = nextLegacyPath();
|
|
966
990
|
fs.renameSync((0, workspace_1.abs)("wiki"), (0, workspace_1.abs)(legacyPath));
|
|
967
991
|
return { legacyPath, note: `moved wiki to ${legacyPath}` };
|
|
@@ -981,7 +1005,8 @@ function migrationTargetForKind(kind) {
|
|
|
981
1005
|
}
|
|
982
1006
|
function runMigrationMode(migrationState) {
|
|
983
1007
|
const legacyPath = migrationState.legacyPath;
|
|
984
|
-
const
|
|
1008
|
+
const legacyRootPath = legacyPath && (0, workspace_1.exists)(legacyPath) ? migrationDirectoryPath(legacyPath) : "";
|
|
1009
|
+
const markdownFiles = legacyRootPath ? (0, wiki_files_1.walkMarkdownFiles)(legacyRootPath, [], legacyRootPath) : [];
|
|
985
1010
|
const fileRecords = markdownFiles.map((file) => {
|
|
986
1011
|
const text = (0, workspace_1.read)(file.path);
|
|
987
1012
|
return { file, text, formOnlyReason: formOnlyMigrationDocumentReason(file.basePath, text) };
|
package/dist/modes.js
CHANGED
|
@@ -37,6 +37,7 @@ exports.buildRefreshIndexBlock = buildRefreshIndexBlock;
|
|
|
37
37
|
exports.runQueryMode = runQueryMode;
|
|
38
38
|
exports.runWikiImpactMode = runWikiImpactMode;
|
|
39
39
|
exports.projectCandidatesContent = projectCandidatesContent;
|
|
40
|
+
exports.appendProjectCandidate = appendProjectCandidate;
|
|
40
41
|
exports.appendCaptureInbox = appendCaptureInbox;
|
|
41
42
|
exports.runIssueDraftMode = runIssueDraftMode;
|
|
42
43
|
exports.runIssueCreateMode = runIssueCreateMode;
|
|
@@ -338,23 +339,29 @@ function projectCandidatesContent() {
|
|
|
338
339
|
| --- | --- | --- | --- | --- |
|
|
339
340
|
`;
|
|
340
341
|
}
|
|
341
|
-
function
|
|
342
|
+
function appendProjectCandidate(input = {}) {
|
|
342
343
|
(0, workspace_1.mkdirp)("wiki/inbox");
|
|
343
344
|
const relativePath = "wiki/inbox/project-candidates.md";
|
|
344
345
|
const existed = (0, workspace_1.exists)(relativePath);
|
|
345
346
|
if (!existed)
|
|
346
347
|
(0, workspace_1.write)(relativePath, projectCandidatesContent());
|
|
347
|
-
|
|
348
|
+
const candidateTitle = input.title ?? args_1.captureTitle;
|
|
349
|
+
const candidateContent = input.content ?? args_1.captureContent;
|
|
350
|
+
const candidateCategory = input.category ?? args_1.captureCategory;
|
|
351
|
+
if (!candidateTitle && !candidateContent)
|
|
348
352
|
return existed ? "exists" : "created";
|
|
349
|
-
const title = (
|
|
350
|
-
const content = (
|
|
351
|
-
const row = `| ${workspace_1.today} | ${title} | ${
|
|
353
|
+
const title = (candidateTitle || "Untitled candidate").replace(/\|/g, "/");
|
|
354
|
+
const content = (candidateContent || "").replace(/\r?\n/g, "<br>").replace(/\|/g, "/");
|
|
355
|
+
const row = `| ${workspace_1.today} | ${title} | ${candidateCategory.replace(/\|/g, "/")} | ${content} | pending |`;
|
|
352
356
|
const current = (0, workspace_1.read)(relativePath);
|
|
353
357
|
if (current.includes(row))
|
|
354
358
|
return "exists";
|
|
355
359
|
(0, workspace_1.write)(relativePath, `${current.trimEnd()}\n${row}\n`);
|
|
356
360
|
return "updated";
|
|
357
361
|
}
|
|
362
|
+
function appendCaptureInbox() {
|
|
363
|
+
return appendProjectCandidate();
|
|
364
|
+
}
|
|
358
365
|
function gitOutput(args) {
|
|
359
366
|
try {
|
|
360
367
|
return childProcess.execFileSync("git", args, {
|