sentinelayer-cli 0.37.0 → 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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sentinelayer-cli",
3
- "version": "0.37.0",
3
+ "version": "0.37.1",
4
4
  "description": "Scaffold Sentinelayer spec/prompt/guide artifacts with secure browser auth and token bootstrap.",
5
5
  "type": "module",
6
6
  "scripts": {
@@ -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(targetPath = process.cwd(), { sourceReason = "caller_target_path" } = {}) {
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 sourceLabel = `${WORK_PLAN_RELATIVE_PATH} from ${workspaceLabel}#${workspaceFingerprint}`;
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, WORK_PLAN_RELATIVE_PATH),
105
- path: WORK_PLAN_RELATIVE_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 readWorkPlanSummary({
635
- targetPath = process.cwd(),
636
- limit = DEFAULT_WORK_PLAN_SUMMARY_LIMIT,
637
- sourceReason = "caller_target_path",
638
- } = {}) {
639
- const sourceContext = buildWorkPlanSourceContext(targetPath, { sourceReason });
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();