sentinelayer-cli 0.36.5 → 0.37.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.
- package/package.json +1 -1
- package/src/commands/mcp.js +57 -30
- package/src/session/recap.js +99 -10
package/package.json
CHANGED
package/src/commands/mcp.js
CHANGED
|
@@ -481,37 +481,64 @@ Docs: https://github.com/mrrCarter/create-sentinelayer/blob/main/docs/mcp.md`,
|
|
|
481
481
|
console.log(pc.gray("Next: validate with registry cross-check before runtime wiring."));
|
|
482
482
|
});
|
|
483
483
|
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
484
|
+
const registerSessionRegistryInit = ({
|
|
485
|
+
commandName,
|
|
486
|
+
description,
|
|
487
|
+
outputSuffix,
|
|
488
|
+
payloadCommand,
|
|
489
|
+
successLabel,
|
|
490
|
+
nextMessage,
|
|
491
|
+
}) => {
|
|
492
|
+
registry
|
|
493
|
+
.command(commandName)
|
|
494
|
+
.description(description)
|
|
495
|
+
.option("--path <path>", "Destination file path override")
|
|
496
|
+
.option("--output-dir <path>", "Optional artifact output root override")
|
|
497
|
+
.option("--force", "Overwrite destination file if it already exists")
|
|
498
|
+
.option("--json", "Emit machine-readable output")
|
|
499
|
+
.action(async (options, command) => {
|
|
500
|
+
const defaultPath = await resolveDefaultMcpOutputPath({
|
|
501
|
+
cwd: process.cwd(),
|
|
502
|
+
outputDir: options.outputDir,
|
|
503
|
+
env: process.env,
|
|
504
|
+
});
|
|
505
|
+
const outputPath = normalizeOutputPath(options.path, defaultPath.replace(".schema", outputSuffix));
|
|
506
|
+
const template = buildSentinelayerSessionRegistryTemplate();
|
|
507
|
+
validateMcpToolRegistry(template);
|
|
508
|
+
const writtenPath = await writeJsonFile(outputPath, template, { force: Boolean(options.force) });
|
|
509
|
+
const payload = {
|
|
510
|
+
command: payloadCommand,
|
|
511
|
+
outputPath: writtenPath,
|
|
512
|
+
toolCount: template.tools.length,
|
|
513
|
+
tools: template.tools.map((tool) => tool.name),
|
|
514
|
+
};
|
|
507
515
|
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
516
|
+
if (shouldEmitJson(options, command)) {
|
|
517
|
+
console.log(JSON.stringify(payload, null, 2));
|
|
518
|
+
return;
|
|
519
|
+
}
|
|
520
|
+
console.log(pc.green(`Wrote ${successLabel}: ${writtenPath}`));
|
|
521
|
+
console.log(pc.gray(nextMessage));
|
|
522
|
+
});
|
|
523
|
+
};
|
|
524
|
+
|
|
525
|
+
registerSessionRegistryInit({
|
|
526
|
+
commandName: "init-session",
|
|
527
|
+
description: "Write the built-in SentinelLayer session MCP tool registry",
|
|
528
|
+
outputSuffix: ".session-tools",
|
|
529
|
+
payloadCommand: "mcp registry init-session",
|
|
530
|
+
successLabel: "SentinelLayer session MCP registry template",
|
|
531
|
+
nextMessage: "Next: run 'sl mcp server init --id sentinelayer-session --registry-file <path>'.",
|
|
532
|
+
});
|
|
533
|
+
|
|
534
|
+
registerSessionRegistryInit({
|
|
535
|
+
commandName: "init-senti-session",
|
|
536
|
+
description: "Write the built-in Senti session MCP tool registry",
|
|
537
|
+
outputSuffix: ".senti-session-tools",
|
|
538
|
+
payloadCommand: "mcp registry init-senti-session",
|
|
539
|
+
successLabel: "Senti session MCP registry template",
|
|
540
|
+
nextMessage: "Next: run 'sl mcp registry init-hosted-session-connector --registry-file <path>' for hosted MCP.",
|
|
541
|
+
});
|
|
515
542
|
|
|
516
543
|
registry
|
|
517
544
|
.command("init-hosted-session-connector")
|
package/src/session/recap.js
CHANGED
|
@@ -23,6 +23,7 @@ const DEFAULT_WORK_PLAN_SUMMARY_LIMIT = 5;
|
|
|
23
23
|
const DEFAULT_ACTIVITY_SNIPPET_MAX_CHARS = 120;
|
|
24
24
|
const MAX_WORK_PLAN_BYTES = 128_000;
|
|
25
25
|
const WORK_PLAN_RELATIVE_PATH = "tasks/todo.md";
|
|
26
|
+
const ACTIVE_WORK_PLAN_FILE_PATTERN = /^reconciled-fix-plan-\d{4}-\d{2}-\d{2}\.md$/;
|
|
26
27
|
const HISTORICAL_WORK_PLAN_MIN_COMPLETED = 25;
|
|
27
28
|
const HISTORICAL_WORK_PLAN_MIN_TOTAL = 50;
|
|
28
29
|
const HISTORICAL_WORK_PLAN_COMPLETED_RATIO = 0.75;
|
|
@@ -91,18 +92,22 @@ function normalizePositiveInteger(value, fallbackValue) {
|
|
|
91
92
|
return Math.max(1, Math.floor(normalized));
|
|
92
93
|
}
|
|
93
94
|
|
|
94
|
-
function buildWorkPlanSourceContext(
|
|
95
|
+
function buildWorkPlanSourceContext(
|
|
96
|
+
targetPath = process.cwd(),
|
|
97
|
+
{ sourceReason = "caller_target_path", relativePath = WORK_PLAN_RELATIVE_PATH } = {},
|
|
98
|
+
) {
|
|
95
99
|
const workspacePath = path.resolve(String(targetPath || "."));
|
|
96
100
|
const workspaceLabel = normalizeString(path.basename(workspacePath)) || ".";
|
|
97
101
|
const workspaceFingerprint = createHash("sha256")
|
|
98
102
|
.update(workspacePath)
|
|
99
103
|
.digest("hex")
|
|
100
104
|
.slice(0, 8);
|
|
101
|
-
const
|
|
105
|
+
const normalizedRelativePath = normalizeString(relativePath) || WORK_PLAN_RELATIVE_PATH;
|
|
106
|
+
const sourceLabel = `${normalizedRelativePath} from ${workspaceLabel}#${workspaceFingerprint}`;
|
|
102
107
|
return {
|
|
103
108
|
workspacePath,
|
|
104
|
-
filePath: path.join(workspacePath,
|
|
105
|
-
path:
|
|
109
|
+
filePath: path.join(workspacePath, normalizedRelativePath),
|
|
110
|
+
path: normalizedRelativePath,
|
|
106
111
|
workspaceLabel,
|
|
107
112
|
workspaceFingerprint,
|
|
108
113
|
sourceLabel,
|
|
@@ -559,6 +564,37 @@ function selectActionableWorkPlanOpenRecords(records = [], { limit = DEFAULT_WOR
|
|
|
559
564
|
return selected.slice(-Math.max(1, normalizePositiveInteger(limit, DEFAULT_WORK_PLAN_SUMMARY_LIMIT)));
|
|
560
565
|
}
|
|
561
566
|
|
|
567
|
+
function parseMarkdownTableCells(line = "") {
|
|
568
|
+
const trimmed = String(line || "").trim();
|
|
569
|
+
if (!trimmed.startsWith("|") || !trimmed.endsWith("|")) {
|
|
570
|
+
return [];
|
|
571
|
+
}
|
|
572
|
+
const cells = trimmed
|
|
573
|
+
.slice(1, -1)
|
|
574
|
+
.split("|")
|
|
575
|
+
.map((cell) => shortWorkPlanText(cell));
|
|
576
|
+
if (cells.length === 0 || cells.every((cell) => /^:?-{3,}:?$/.test(cell))) {
|
|
577
|
+
return [];
|
|
578
|
+
}
|
|
579
|
+
return cells;
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
function isCompletedPairProgrammingRow(cells = []) {
|
|
583
|
+
const joined = cells.map((cell) => normalizeString(cell).toLowerCase()).join(" ");
|
|
584
|
+
return /\bclosed\b/.test(joined) || /\bfully closed\b/.test(joined);
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
function pairProgrammingRowTask(cells = []) {
|
|
588
|
+
const lane = normalizeString(cells[0]);
|
|
589
|
+
const state = normalizeString(cells[3]);
|
|
590
|
+
const gate = normalizeString(cells[4]);
|
|
591
|
+
if (!lane || lane.toLowerCase() === "lane") {
|
|
592
|
+
return "";
|
|
593
|
+
}
|
|
594
|
+
const detail = state || gate;
|
|
595
|
+
return shortWorkPlanText(detail ? `${lane}: ${detail}` : lane);
|
|
596
|
+
}
|
|
597
|
+
|
|
562
598
|
function formatWorkPlanOpenItems(items = []) {
|
|
563
599
|
return (Array.isArray(items) ? items : [])
|
|
564
600
|
.map((item) => {
|
|
@@ -593,6 +629,25 @@ function summarizeWorkPlanMarkdown(
|
|
|
593
629
|
|
|
594
630
|
const taskMatch = /^\s*[-*]\s+\[([ xX])\]\s+(.+?)\s*$/.exec(line);
|
|
595
631
|
if (!taskMatch) {
|
|
632
|
+
if (/pair-programming matrix/i.test(section)) {
|
|
633
|
+
const cells = parseMarkdownTableCells(line);
|
|
634
|
+
const task = pairProgrammingRowTask(cells);
|
|
635
|
+
if (task) {
|
|
636
|
+
const record = {
|
|
637
|
+
status: isCompletedPairProgrammingRow(cells) ? "completed" : "open",
|
|
638
|
+
section,
|
|
639
|
+
task,
|
|
640
|
+
};
|
|
641
|
+
records.push(record);
|
|
642
|
+
summary.total += 1;
|
|
643
|
+
if (record.status === "completed") {
|
|
644
|
+
summary.completed += 1;
|
|
645
|
+
} else {
|
|
646
|
+
summary.open += 1;
|
|
647
|
+
}
|
|
648
|
+
summary.currentSection = section || summary.currentSection;
|
|
649
|
+
}
|
|
650
|
+
}
|
|
596
651
|
continue;
|
|
597
652
|
}
|
|
598
653
|
const completed = taskMatch[1].toLowerCase() === "x";
|
|
@@ -631,12 +686,28 @@ function summarizeWorkPlanMarkdown(
|
|
|
631
686
|
return summary;
|
|
632
687
|
}
|
|
633
688
|
|
|
634
|
-
async function
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
689
|
+
async function findActiveWorkPlanSourceContext(targetPath = process.cwd(), { sourceReason = "caller_target_path" } = {}) {
|
|
690
|
+
const workspacePath = path.resolve(String(targetPath || "."));
|
|
691
|
+
const tasksPath = path.join(workspacePath, "tasks");
|
|
692
|
+
try {
|
|
693
|
+
const entries = await fsp.readdir(tasksPath, { withFileTypes: true });
|
|
694
|
+
const candidates = entries
|
|
695
|
+
.filter((entry) => entry.isFile() && ACTIVE_WORK_PLAN_FILE_PATTERN.test(entry.name))
|
|
696
|
+
.map((entry) => entry.name)
|
|
697
|
+
.sort((a, b) => b.localeCompare(a));
|
|
698
|
+
if (candidates.length === 0) {
|
|
699
|
+
return null;
|
|
700
|
+
}
|
|
701
|
+
return buildWorkPlanSourceContext(targetPath, {
|
|
702
|
+
sourceReason,
|
|
703
|
+
relativePath: path.posix.join("tasks", candidates[0]),
|
|
704
|
+
});
|
|
705
|
+
} catch {
|
|
706
|
+
return null;
|
|
707
|
+
}
|
|
708
|
+
}
|
|
709
|
+
|
|
710
|
+
async function readWorkPlanSummaryFromSource(sourceContext, { limit = DEFAULT_WORK_PLAN_SUMMARY_LIMIT } = {}) {
|
|
640
711
|
try {
|
|
641
712
|
const stats = await fsp.stat(sourceContext.filePath);
|
|
642
713
|
let source = "";
|
|
@@ -668,6 +739,24 @@ async function readWorkPlanSummary({
|
|
|
668
739
|
}
|
|
669
740
|
}
|
|
670
741
|
|
|
742
|
+
async function readWorkPlanSummary({
|
|
743
|
+
targetPath = process.cwd(),
|
|
744
|
+
limit = DEFAULT_WORK_PLAN_SUMMARY_LIMIT,
|
|
745
|
+
sourceReason = "caller_target_path",
|
|
746
|
+
} = {}) {
|
|
747
|
+
const activePlanContext = await findActiveWorkPlanSourceContext(targetPath, { sourceReason });
|
|
748
|
+
if (activePlanContext) {
|
|
749
|
+
const activePlanSummary = await readWorkPlanSummaryFromSource(activePlanContext, { limit });
|
|
750
|
+
if (activePlanSummary.exists && Number(activePlanSummary.total || 0) > 0) {
|
|
751
|
+
return activePlanSummary;
|
|
752
|
+
}
|
|
753
|
+
}
|
|
754
|
+
return readWorkPlanSummaryFromSource(
|
|
755
|
+
buildWorkPlanSourceContext(targetPath, { sourceReason }),
|
|
756
|
+
{ limit },
|
|
757
|
+
);
|
|
758
|
+
}
|
|
759
|
+
|
|
671
760
|
function summarizeTaskLedger(tasks = [], { limit = DEFAULT_TASK_SUMMARY_LIMIT } = {}) {
|
|
672
761
|
const summary = emptyTaskLedgerSummary();
|
|
673
762
|
const owners = new Map();
|