diff --git a/CHANGELOG.md b/CHANGELOG.md index 7254ce2..ac7df61 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,3 +10,5 @@ for tagged releases. - Initial Knowledge Agent and Digital TeamMate framework. - Allowlist-only publication boundary and repeatable installer. - Sanitized automation definitions and empty-vault example. +- `resolve-document-items` skill for closing open questions, unowned actions, assumptions, and pending decisions in documents through a calm one-question-at-a-time workflow. +- DOCX deliverable generation now renders Markdown pipe tables as Word tables. diff --git a/docs/installation.md b/docs/installation.md index 3e5414b..c924027 100644 --- a/docs/installation.md +++ b/docs/installation.md @@ -31,8 +31,8 @@ framework files with `--force` after reviewing the changes. ## Install the SecondBrain skills -The vault installer keeps canonical skills under `skills/`. To make `$dtm` and -`$voice` discoverable by Codex, copy them into the user's Codex skills +The vault installer keeps canonical skills under `skills/`. To make the +framework skills discoverable by Codex, copy them into the user's Codex skills directory: ```sh @@ -40,6 +40,7 @@ mkdir -p "${CODEX_HOME:-$HOME/.codex}/skills" cp -R skills/dtm "${CODEX_HOME:-$HOME/.codex}/skills/" cp -R skills/voice "${CODEX_HOME:-$HOME/.codex}/skills/" cp -R skills/markdown-to-pdf "${CODEX_HOME:-$HOME/.codex}/skills/" +cp -R skills/resolve-document-items "${CODEX_HOME:-$HOME/.codex}/skills/" ``` Start a thread in the installed vault and invoke `$dtm`. That thread remains in @@ -53,6 +54,11 @@ Invoke `$voice` after representative writing has reached `ready/` or Invoke `$markdown-to-pdf` when you want a shareable PDF from a Markdown note or managed document, including Mermaid diagrams when present. +Invoke `$resolve-document-items` when you want to work through open questions, +unowned actions, assumptions, and pending decisions in a document one question +at a time, updating the document only when the answer resolves or records the +item. + ## Optional PDF deliverable toolchain If you want managed Markdown-to-PDF generation with Mermaid support, install: diff --git a/framework/DTM.md b/framework/DTM.md index b202cd9..4add3ef 100644 --- a/framework/DTM.md +++ b/framework/DTM.md @@ -67,8 +67,7 @@ When work is delegated to another thread: ## DTM workspace -- `daily/YYYY-MM-DD.md` — active Daily Notes for the current 14-day retro period. -- `daily/archive/YYYY-MM-DD.md` — archived Daily Notes retained for reference once they age out of the active window. +- `daily/YYYY-MM-DD.md` — Daily Notes retained together in the main daily workspace. - `projects/` — durable project plans, status, milestones, and next actions. - `work/` — working documents, drafts, scratch analyses, and deliverables that are not yet durable wiki knowledge. @@ -100,6 +99,8 @@ Working-note statuses are: Reusable operational assets in `work/`, such as prompt templates, checklists, research packs, or working methods that are still being actively used or revised, should remain `current`. Do not mark a note `reference` merely because it is reusable. +New managed working notes default to `current` while they have an expected next use, even if that use is occasional or advisory. Create a new note directly as `reference` only when it is explicitly a retained historical record, superseded artefact, imported review, or other non-active material, and make the retention reason clear in the note. A newly created working note must not be marked `reference` just because it is a reusable comparison, research pack, or decision-support aid. + Keep project filenames and managed working-note filenames in stable lowercase kebab-case. Use `templates/project.md` for projects and `templates/working-note.md` for new managed working notes. @@ -114,6 +115,14 @@ physical line and rely on Obsidian for visual wrapping. Use new lines only when the Markdown structure itself changes, such as headings, list items, tables, block quotes, or code fences. +## Implementation environment preferences + +When advising on, creating, or changing containerised development environments, preserve the user's architecture preference unless the task supplies a stronger local constraint: + +- For containers that run custom application code on the user's Mac, prefer `linux/amd64` images under Rosetta, accepting the performance tradeoff in exchange for consistency with the more common non-ARM deployment and runtime architecture. +- For hosted services or off-the-shelf dependency containers where the container is not running custom application code, prefer native ARM images when they are available. +- Deviate from this only for a clear compatibility, tooling, or operational reason, and call out that reason explicitly before proceeding. + ## Collaborative writing Use `templates/writing-piece.md` for substantive pieces. Help develop the @@ -218,6 +227,7 @@ traceable at system level. - Put personal and professional tasks in their respective Daily Note sections. - Use Obsidian task syntax: `- [ ] Action` and `- [x] Completed action`. +- Treat the Daily Note `Focus` section as the morning commitment for the day, not a live next-action list. After the day has started, do not rewrite `Focus` to reflect progress, newly discovered details, or the next task unless the user explicitly asks to correct the focus wording itself. Track completion and changing operational state in to-do items, `Notes & Activity`, and linked project or working notes. - Make tasks concrete and outcome-oriented. Link the relevant project when one exists. - In the current Daily Note, preserve visible completion by checking completed diff --git a/framework/skills/resolve-document-items/SKILL.md b/framework/skills/resolve-document-items/SKILL.md new file mode 100644 index 0000000..44fb0b9 --- /dev/null +++ b/framework/skills/resolve-document-items/SKILL.md @@ -0,0 +1,93 @@ +--- +name: resolve-document-items +description: Work through unresolved items in a specified document through a structured, non-adversarial question-and-answer workflow. Use when the user invokes `$resolve-document-items` or explicitly asks to close open questions, unowned tasks, incomplete actions, assumptions, or pending decisions in a document one item at a time, updating the source document only when the user's answer resolves, assigns, confirms, changes, or explicitly records deferral for an item. +--- + +# Resolve Document Items + +Use this skill to help close open loops in an existing document calmly and efficiently. The goal is document completion, not idea exploration, red-team review, or Socratic challenge. + +## Core Behavior + +- Read the specified source document before asking questions. +- Identify unresolved open questions, unowned tasks, incomplete actions, assumptions needing confirmation, stale placeholders, and decisions still required. +- Work through items one at a time. +- Ask exactly one primary question at a time. +- Wait for the user's answer before moving to the next item. +- Ask a brief follow-up only when the answer is ambiguous, incomplete, internally inconsistent, or not enough to update the document accurately. +- Avoid challenging the user's reasoning unless the document cannot be updated safely without clarifying a real contradiction. +- Update the source document as soon as an answer resolves an item, assigns ownership, confirms a decision, changes action status, or records a user-requested deferral. +- Leave the source document unchanged when the user explicitly defers an item without asking for the deferral to be recorded. +- Continue until every identified item is resolved, updated, assigned, or explicitly deferred. + +## Distinction From Interview Skills + +This skill is not a thought-development interview. Do not use it to test the user's assumptions, deepen their argument, generate new ideas, or pressure-test strategy. Use the minimum question needed to make the document more complete and actionable. + +If a user appears to want exploration rather than close-out, ask whether they want this skill or a separate thinking/interview workflow. If they ask to resolve document items, stay practical. + +## Workflow + +1. Confirm the document path or identify the named document from the current workspace. +2. Read the document and any directly necessary local context, such as a linked index entry or project page that defines statuses or ownership conventions. Do not broaden into unrelated research. +3. Build a short internal queue of outstanding items. Prefer items already visible in headings, tables, checklists, `TBC`, `TODO`, `Open questions`, `Next actions`, `Assumptions`, `Risks`, `Decisions`, and owner/status columns. +4. Start with the item that is most blocking, easiest to resolve, or nearest to the document's current operating purpose. If no order is obvious, use document order. +5. Ask one focused question that makes clear: + - which item is being resolved; + - what answer format would let the document be updated; + - whether the likely update is a decision, owner, action, status, assumption confirmation, or deferral. +6. After the user answers, decide whether the answer is document-ready. +7. If document-ready, update the source document immediately and tell the user briefly what changed. +8. If not document-ready, ask one brief follow-up that names the missing piece. +9. If deferred, leave the source document unchanged unless the user asks to record the deferral. +10. Move to the next item and repeat until the queue is closed or the user stops. + +## Question Style + +Ask narrow, answerable questions. Prefer: + +- "For this open query, should the owner be Philip, Ben, another named person, or left unassigned for now?" +- "Can I mark this as resolved with the decision that the Kanban board remains the live delivery view and the project note is only a periodic snapshot?" +- "Should this action stay open, move to blocked, or be removed because it is no longer required?" +- "Is this assumption now confirmed, or should I leave it as an explicit assumption?" + +Avoid: + +- "Are you sure?" +- "What are the second-order consequences?" +- "Defend this decision." +- "Let's explore your underlying mental model." +- Multi-part questions that require the user to resolve several items at once. + +## Updating The Document + +When updating, preserve the document's existing style, structure, and status vocabulary. Make the smallest change that accurately captures the answer. + +Common update patterns: + +- Replace `TBC` with the confirmed value. +- Add or update an owner in a table. +- Move an action from open to blocked, completed, or deferred if the document has those statuses. +- Remove an open question only when the answer has been integrated into the relevant section. +- Add a concise decision bullet when the document has a decision section. +- Add a short note to an item when the user asks for a deferral or uncertainty to be recorded. + +Do not silently rewrite large sections while resolving one item. If an answer implies a broader restructure, say so and ask whether to make that broader edit after the current item is handled. + +## Progress Reporting + +Keep progress visible but lightweight. After each update, use a short confirmation such as: + +`Updated: assigned the workbook review-rhythm action to Philip and left Ben as consulted. Next item: the unresolved safeguarding mapping ownership.` + +When the queue is complete, give a concise summary of: + +- items resolved; +- items assigned; +- decisions confirmed; +- items explicitly deferred; +- any remaining blockers the document still shows. + +## DTM Compatibility + +If this skill is invoked inside an active `$dtm` thread, remain within the DTM workflow. Capture substantive document changes in today's Daily Note before closing out. For project, work, writing, or document workspace changes, run the relevant local lint command when one exists. diff --git a/framework/skills/resolve-document-items/agents/openai.yaml b/framework/skills/resolve-document-items/agents/openai.yaml new file mode 100644 index 0000000..da81ee7 --- /dev/null +++ b/framework/skills/resolve-document-items/agents/openai.yaml @@ -0,0 +1,6 @@ +interface: + display_name: "Resolve Document Items" + short_description: "Close open loops in a document" + default_prompt: "Use $resolve-document-items to work through unresolved items in the specified document one question at a time." +policy: + allow_implicit_invocation: false diff --git a/framework/tools/document_deliverables.py b/framework/tools/document_deliverables.py index e581563..1fc0bd0 100644 --- a/framework/tools/document_deliverables.py +++ b/framework/tools/document_deliverables.py @@ -333,6 +333,63 @@ def list_kind(block: str) -> str | None: return None +def is_markdown_table(block: str) -> bool: + lines = [line.strip() for line in block.splitlines() if line.strip()] + if len(lines) < 2: + return False + if not all(line.startswith("|") and line.endswith("|") for line in lines[:2]): + return False + separator_cells = [cell.strip() for cell in lines[1].strip("|").split("|")] + return bool(separator_cells) and all(re.match(r"^:?-{3,}:?$", cell) for cell in separator_cells) + + +def parse_markdown_table(block: str) -> tuple[list[str], list[list[str]]]: + lines = [line.strip() for line in block.splitlines() if line.strip()] + rows = [[cell.strip() for cell in line.strip("|").split("|")] for line in lines] + header = rows[0] + body = rows[2:] + width = len(header) + normalized = [(row + [""] * width)[:width] for row in body] + return header, normalized + + +def set_cell_margins(cell, *, top: int = 80, start: int = 80, bottom: int = 80, end: int = 80) -> None: + tc_pr = cell._tc.get_or_add_tcPr() + tc_mar = tc_pr.first_child_found_in("w:tcMar") + if tc_mar is None: + tc_mar = OxmlElement("w:tcMar") + tc_pr.append(tc_mar) + for name, value in (("top", top), ("start", start), ("bottom", bottom), ("end", end)): + element = tc_mar.find(qn(f"w:{name}")) + if element is None: + element = OxmlElement(f"w:{name}") + tc_mar.append(element) + element.set(qn("w:w"), str(value)) + element.set(qn("w:type"), "dxa") + + +def shade_cell(cell, fill: str) -> None: + tc_pr = cell._tc.get_or_add_tcPr() + shd = tc_pr.find(qn("w:shd")) + if shd is None: + shd = OxmlElement("w:shd") + tc_pr.append(shd) + shd.set(qn("w:fill"), fill) + + +def markdown_table_widths(headers: list[str], rows: list[list[str]]) -> list[float]: + lengths: list[int] = [] + for idx, header in enumerate(headers): + body_lengths = [len(row[idx]) for row in rows if idx < len(row)] + lengths.append(max([len(header), *body_lengths, 4])) + total = sum(lengths) or 1 + raw = [CONTENT_WIDTH_IN * (length / total) for length in lengths] + minimum = 0.65 if len(headers) >= 5 else 0.9 + adjusted = [max(minimum, width) for width in raw] + scale = CONTENT_WIDTH_IN / sum(adjusted) + return [width * scale for width in adjusted] + + def set_font(run, name: str, size: float, *, bold: bool = False, italic: bool = False, color: str = "000000") -> None: run.font.name = name run._element.rPr.rFonts.set(qn("w:ascii"), name) @@ -464,6 +521,40 @@ def add_list(doc: Document, block: str, ordered: bool) -> None: set_font(run, "Calibri", 11, color="000000") +def add_markdown_table(doc: Document, block: str) -> None: + headers, rows = parse_markdown_table(block) + if not headers: + return + + table = doc.add_table(rows=1, cols=len(headers)) + table.style = "Table Grid" + table.autofit = False + widths = markdown_table_widths(headers, rows) + font_size = 8 if len(headers) >= 5 else 9 + + for idx, (cell, header) in enumerate(zip(table.rows[0].cells, headers, strict=True)): + cell.width = Inches(widths[idx]) + set_cell_margins(cell, top=100, start=100, bottom=100, end=100) + shade_cell(cell, "D9EAF7") + para = cell.paragraphs[0] + style_paragraph(para, after=0, line=240) + run = para.add_run(clean_inline(header)) + set_font(run, "Calibri", font_size, bold=True, color="1F1F1F") + + for row_values in rows: + row = table.add_row() + for idx, cell in enumerate(row.cells): + cell.width = Inches(widths[idx]) + set_cell_margins(cell, top=90, start=100, bottom=90, end=100) + para = cell.paragraphs[0] + style_paragraph(para, after=0, line=240) + run = para.add_run(clean_inline(row_values[idx])) + set_font(run, "Calibri", font_size, color="000000") + + spacer = doc.add_paragraph() + style_paragraph(spacer, after=8, line=240) + + def add_summary_table(doc: Document, title: str, summary: str, recommendation: str) -> None: table = doc.add_table(rows=3, cols=2) table.style = "Table Grid" @@ -522,6 +613,9 @@ def build_docx(source: Path, output: Path) -> Path: if section.title and lowered != "document": add_heading(doc, section.title, section.level) for block in section.blocks: + if is_markdown_table(block): + add_markdown_table(doc, block) + continue kind = list_kind(block) if kind == "bullet": add_list(doc, block, ordered=False) diff --git a/framework/tools/dtm.py b/framework/tools/dtm.py index b66cff1..dff9091 100644 --- a/framework/tools/dtm.py +++ b/framework/tools/dtm.py @@ -14,6 +14,7 @@ ROOT = Path(__file__).resolve().parent.parent DAILY = ROOT / "daily" RECURRENCE = ROOT / "dtm" / "recurring-tasks.json" +LOG = ROOT / "log.md" SECTIONS = [ "Previous Day", @@ -34,8 +35,11 @@ } TASK_RE = re.compile(r"^- \[ \] .+$", re.M) QUESTION_RE = re.compile(r"^- (?!\[[ xX]\] )(?!