Skip to content
Back to skills

Complete Project

ASecurity

Explicitly complete a project and route artefacts - prevents zombie projects lingering in the 03 Projects root

  • 5 stars
  • 0 votes
  • 0 copies
  • 0 views
  • Added October 4, 2026
ai-agentspythongoshellbashnodegit

Works with

  • cli

Security analysis

A100/100

Pro scans all 2 files and shows the line behind each finding

Scanned October 5, 2026

npx -y skills add OpenCairn/OpenCairn --skill complete-project --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Complete Project?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Complete Project
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/opencairn-complete-project/badge)](https://www.skillsdirectory.com/skills/opencairn-complete-project)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: complete-project
description: Explicitly complete a project and route artefacts - prevents zombie projects lingering in the 03 Projects root
---

# Complete Project - Formal Project Completion

**Scoped rule loading:** `_shared-rules-planning.md` before task linking; `_shared-rules-content.md` before structural moves. Read the applicable numbered sections at that point, from the same directory as the core `_shared-rules.md`; do not preload unrelated supplements.

You are helping the user formally complete a project. This command prevents "zombie projects" that linger in the `03 Projects/` root long after they're effectively done.

## Philosophy

Projects often fade away rather than explicitly complete. This creates clutter in the active root and uncertainty ("Am I still doing this? Should I be?"). Explicit completion:
- Provides psychological closure
- Documents outcomes for future reference
- Keeps the `03 Projects/` root accurate — folder location IS status, so moving the doc out of the root is the de-registration
- Allows celebration of completion

## Instructions

0. **Resolve Vault Path**

   ```bash
   "${VAULT_PATH:?VAULT_PATH not set}/.claude/scripts/resolve-vault.sh"
   ```

   If error, abort. Read `~/.codex/skills/_shared-rules.md` and apply its rules throughout this skill. All code below uses `{VAULT}` as a placeholder — substitute the resolved vault path.

1. **Check current date and time** using bash `date` command:
   - Get current date: `date +"%Y-%m-%d"`
   - Get current time: `date +"%I:%M%p" | tr '[:upper:]' '[:lower:]'`
   - Store for metadata

2. **Identify project to complete:**
   - List project docs under `{VAULT}/03 Projects/` (root) and `{VAULT}/03 Projects/Cold/` — root = active, Cold = paused. Include `Backlog/` on request. The headline completion case under "When to Use" (a project stalled 30+ days) often sits in Cold, so a root-only list hides exactly the projects this command exists to close.
   - If project name provided as parameter: Use that
   - Otherwise: Ask the user which project to complete
   - Validate the project doc exists under `03 Projects/`, `03 Projects/Backlog/`, or `03 Projects/Cold/`, and **record the exact path found** — Steps 4-5 bind to it.
   - **Initiative check:** run `rg -F --glob '*.md' -- '**Initiative:** [[03 Projects/[Project Name]]]' "{VAULT}/03 Projects"`. (`-F` is the literal-search flag; in ripgrep, `-r` means replacement text, not recursion.) Hits mean this is an initiative with child projects — list them and confirm with the user that each child is completed or re-homed before proceeding; an initiative completed out from under live children leaves them pointing at a moved hub.
   - **Obsidian preflight:** Step 4's moves need the `obsidian` CLI with the app running. Probe now — `obsidian vault info=path` — **before any writes**. Use vault information rather than `obsidian version` alone: depending on the sender, `version` may report an installed version or reach the app, but it does not identify the active vault. `vault info=path` reaches the running app’s vault index. Require exit 0, a nonblank path reply without an explicit error, and equality between the app reply and `{VAULT}` from the resolver after expanding user paths and resolving both to existing directories; a mismatched or invalid path fails the preflight. A reachable app on another vault is not ready for Step 4. A blank or whitespace-only reply gets the bounded read-only retries and narrowly classified transient-empty statuses in §24 before being treated as unavailable; explicit errors on stdout or stderr and unrelated nonzero statuses fail immediately. **A failed probe does not block completion** — ask the user to open the resolved vault in Obsidian; if they can't or won't, proceed with the interview and completion marker but defer every move (Step 4 becomes a hand-off: name each pending move for the user to drag in Obsidian's file explorer, which heals links in one pass). Never fall back to raw `mv`.

3. **Interactive completion interview:**
   First, scan the project file for unchecked `- [ ]` items. Each one gets resolved with the user before completion — done, abandoned, or migrated to a live home (This Week, Tickler, another project doc). **Write mechanism (F1):** migrations write via `write-tickler.sh` (Tickler) or `locked-edit.sh` (This Week.md, project docs) per `_shared-rules.md` §5 — never a raw edit, and never left as a note-to-self that never lands. A completed file leaves every surfacing loop; unchecked tasks moved with it become invisible zombie tasks.
   Then ask the user:
   - **Outcome:** "How did this project end? (Completed successfully / Abandoned / Superseded / Merged into other work)"
   - **Result:** "What was accomplished or learned?"
   - **Why now:** "Why are you completing this now?" (helps catch premature completion)
   - **Domain:** "Does this project belong to a specific area (Health, Hobbies, Work, etc.)?" (determines routing in Step 4)

4. **Route project artefacts and move the project file:**

   The key principle: **`06 Archive/` holds write-once records only, never working files.** A completed project's useful material (reference docs, templates, learnings — and usually the project file itself) belongs on the owning Area's archival shelf. Material with no reference value is deleted, not archived — a session-log record of the completion is proof enough that it existed.

   **Read the vault's archiving convention before proposing a shelf.** Run `rg -nF '**Archiving convention' "{VAULT}/07 System/Vault Organisation Principles.md"`, then read the full convention and any referenced *Decision rule*. State the convention found and apply it to the shelf proposal; explicit user instructions take precedence. Exit 1 means state `archiving convention not found — using default Area Archive/` and use that default. Any other non-zero exit is a read/tool error: report it and stop before routing, rather than treating it as an absent convention.

   **Resolve that shelf before routing anything — propose, then ask. Never invent a shelf, and never file silently.** Apply the resolved convention to the owning Area's actual subfolders, including any partition rules. Check whether the candidate shelf exists, then **put the proposed shelf to the user and wait for confirmation before any move — whether or not the folder already exists.** The proposal is a suggestion, not a decision. Where the convention calls for `Archive/`, propose it if it exists; otherwise show the Area's existing subfolders and propose a shelf whose scope matches the material's lifetime. Never create an `Archive/` *unilaterally*. **If the Area has no subfolders at all**, say the listing is empty and propose creating a shelf consistent with the resolved convention (`Archive/` under the default); create it only once confirmed. Everything below writes to the **confirmed shelf**, written `[shelf]`.

   **State the shelf with its observable, not as an assertion** — print the listing the resolution rests on (`ls` of the Area) alongside the chosen path. "No `Archive/` here, the Area holds X and Y, proposing X" is checkable; "the Area has no archive" is not.

   **⚠ How to move files — use the lock-aware Obsidian wrapper, never shell `mv`.** Every move in this step (artefacts *and* the project file) must go through the wrapper so concurrent writes serialize and inbound links auto-heal:

   ```bash
   SOURCE="{VAULT}/03 Projects/[Project Name].md"
   DESTINATION="{VAULT}/[shelf]/[Project Name].md"
   SOURCE_SHA256="$(python3 -c 'import hashlib,sys; print(hashlib.sha256(open(sys.argv[1], "rb").read()).hexdigest())' "$SOURCE")"
   "{VAULT}/.claude/scripts/locked-edit.sh" "$SOURCE" --move "$DESTINATION" "$SOURCE_SHA256"
   ```

   Shell `mv` moves the bytes but leaves every inbound link dangling. This matters most for the **project file**: per `_shared-rules-planning.md` §3, items link to projects with *path-based* references (`→ [[03 Projects/Project Name]]`). The wrapper requires the running Obsidian app and fails closed if the source changed, the destination appeared, the CLI did nothing, the content hash changed, or old path-qualified links remain. A failed preflight means every move here is deferred to the user per Step 2; completion still proceeds.

   **Batches are fine, but drive each item through the wrapper per `_shared-rules-content.md` §24.** Run the first item as a canary; if it fails, stop the batch.

   **Step 4a — Route artefacts:**
   - Check if the project has associated files (resource folders, reference docs, templates, setup guides) beyond the project file itself — explicitly including `05 Resources/[Project Name]/` (created by `$start-project` Step 7) and anything linked from the project file's `## Resources` section, not just folders under `03 Projects/`
   - **Present the artefacts as one batch with a proposed destination each, not one question per file.** Default every artefact to the owning Area (reference value is the common case and the reversible one) and ask the user only to name the exceptions — "these all go to `[shelf]` unless you call one dead or still live." An empty reply accepts the defaults.
     - **Reference value** → `[shelf]` (a doc still in active use goes in the Area proper, not on its archival shelf)
     - **No reference value** (old CSVs, superseded docs, one-time exports) → **delete**
   - **Stopping rule:** one pass. Don't re-interrogate artefacts the user has already routed, and don't ask about files under ~5 that are obviously project-internal scratch — delete them with the user's batch approval.
   - If the project has a resource folder in `03 Projects/`, apply the same test to its contents — don't move the whole folder blindly
   - **Moving a folder:** a folder move means moving each file individually, per the batch note above and `_shared-rules-content.md` §24. `mkdir -p` the destination, move each file, then remove the empty source folder. If the Obsidian app is unavailable, hand off to the user to multi-select in the file explorer and drag the batch across — Obsidian heals every inbound link in one pass.
   - **Before any delete:** check inbound links via a backlinks route §24 marks reliable — and treat a nil result from an unreliable route as *unverified*, not as zero. Update live-doc references to the deleted file; links inside frozen records (session logs, snapshots) may be left dangling — the record of the deletion lives in the session log.

   **Step 4b — Move the project file:**
   - Determine destination:
     - **Reference value** (the common case — the doc records decisions, results, learnings): `[shelf]/[Project Name].md`
     - **No reference value** (nothing anyone will re-read): **delete the file** — after the Step 4a backlink check. If the user wants a durable trace beyond the session log, write a short completion record (outcome, result, dates) to `06 Archive/Projects/YYYY/[Project Name].md` — a new write-once record, not the project file. `mkdir -p "{VAULT}/06 Archive/Projects/$(date +%Y)"` first (derive the real year; never a literal `YYYY` folder).
   - If unsure, ask: "Will you ever re-read this doc? Area archive if yes, delete if no."
   - **Create the destination folder first** — `mkdir -p "{VAULT}/[shelf]"`, using the shelf **confirmed** at the top of Step 4. Only ever `mkdir` a path the user confirmed; never let this line be the thing that invents a new shelf. The wrapper requires the destination parent to exist.
   - Then hash and move the file from the exact path recorded in Step 2 using `locked-edit.sh --move` (see the move-mechanics note above)
   - **If the move fails** (Electron lock, app closed since the preflight): don't loop-retry. Continue to Step 5 — the completion marker still lands — and report in Step 7 that the file has not moved, naming the exact path it is still at and the intended destination. A named pending move the user can finish is recoverable; a silent failure is not.

   **Step 4c — Verify link integrity:**
   - **Baseline first, before the Step 4b move:** take the unresolved-count reading exactly as `_shared-rules-content.md` §24 (Verification) defines it. After the move, take it again and compare. If either reading ends `UNVERIFIED`, carry that through to Step 7 like the timeout below. A bare post-move `obsidian unresolved` lists every pre-existing dangling link in the vault and cannot tell you which ones *this move* introduced — the count delta can.
   - Bound the call (it reindexes the vault and can hang). On timeout, print `link integrity: UNVERIFIED (unresolved check timed out)` and carry that through to Step 7 rather than reporting success.
   - On the **delete** route, an unresolved-count rise equal to the frozen-record inbound links is expected — report it as such, not as a failure.
   - **Also grep the moved project's path form and its bare title as plain text** — `rg --hidden --no-ignore -n -F -i -g '!**/.git/**' -g '!**/.obsidian*/**' -g '!**/.trash/**' -g '!**/.Trash*/**' -g '!**/.stversions/**' -g '!**/node_modules/**' -g '!**/__pycache__/**' -g '!**/.cache/**' -g '!*.lock' -g '!**/07 System/.Provenance/**' -- '03 Projects/Project Name' "{VAULT}"` and `rg --hidden --no-ignore -n -F -i -g '!**/.git/**' -g '!**/.obsidian*/**' -g '!**/.trash/**' -g '!**/.Trash*/**' -g '!**/.stversions/**' -g '!**/node_modules/**' -g '!**/__pycache__/**' -g '!**/.cache/**' -g '!*.lock' -g '!**/07 System/.Provenance/**' -- 'Project Name' "{VAULT}"`. This searches hidden, ignored and non-Markdown authored text as well as Markdown, omitting repository/editor/dependency/cache/trash state, lock files and frozen provenance; binary content and symlink targets require separate format-aware or target checks. Retain exact commands, exit statuses and stdout/stderr: exit 1 is a checked no-match, while exit >1 is unverified. Both are needed: the path form catches links and locators, and only the bare title catches the prose references ("see the Project Name doc") this check exists for. A wikilink example is redundant — under `-F` the path form is a substring of `[[03 Projects/Project Name]]` and matches it anyway. Scope to `{VAULT}` explicitly — an unscoped `rg` searches whatever cwd the session happens to be in and false-passes. Grep the path the file was **actually found at** in Step 2: a `Backlog/` or `Cold/` project's path form is `03 Projects/Backlog/Project Name`, which the root form misses. `-i` catches lowercased prose forms like "the project name doc". The wrapper verifies healed wikilinks and `obsidian unresolved` catches dangling ones, but neither changes *prose/plain-text* references to the moved project ("see the Project Name doc", a path inside a fenced code block, a `**Source:**` line). Grep with NO keyword conjunction; triage each hit per `_shared-rules.md §12` (grep-hit triage — stale pointer → update; live locator → update on move; historical record → leave).

5. **Mark the completion in the project doc** (skip if the doc was deleted in Step 4b — the session record carries the outcome):

   This runs **after** the move on purpose: the marker landing last means a run that dies partway leaves an ordinary un-marked project, never a marked-done doc still registered in the root. Apply the edits at the doc's **current** path — the Step 4b destination, or the Step 2 path if the move was deferred.

   - **Write mechanism (F1):** apply this edit through `locked-edit.sh` (per `_shared-rules.md` §5), NOT a raw edit. To prepend the completion block, `--replace` the file's first heading line with the block followed by that same heading line.
     - **Failure branches (the script's exit codes, don't improvise):** exit 2 = anchor not found — abort before any write and report which file. exit 3 = the anchor matched more than once (the H1 text recurs in the body) — re-anchor on a longer unique span that includes the following line, or use `--replace-all` only if every occurrence should change. Never retry the same failing anchor.
     - After each edit, grep the file for the separator token `========OPENCAIRN-LOCKED-EDIT-SEP========`. A hit means the heredoc leaked the separator into the file instead of splitting on it — fix before continuing.
   - Add completion section at top:
     ```markdown
     **Status:** COMPLETED ([Date])
     **Outcome:** [Completed successfully / Abandoned / etc.]
     **Result:** [What was accomplished]

     ---
     ```
   - **Legacy docs only:** if the file carries a pre-cutover body `**Status:**` line (the old template wrote one; new-format docs have none — folder location is status), rewrite it to `COMPLETED` with a second `locked-edit.sh --replace` so the body doesn't still read as live. New-format docs: skip — the prepended block is the only status marker.
   - **If the document has an explicit current-state section**, close out that existing section with another `locked-edit.sh --replace`: replace its body with one line stating the end state, and bump any co-located `**Last update:**` stamp to today's date (Step 1). Recognise `## Current Objective`, legacy `## Current Status`, or another clearly equivalent current-state heading; do not invent a section when none exists.
   - This preserves project history while marking completion

6. **Session record:**
   - The completion lands in the session log at the next `$park` — the lock-safe owner of the session entry. State the outcome plainly in-conversation so park's summary captures it; no separate completion-record structure or hand-off fields are needed.
   - **Do NOT hand-append to the session file with a raw edit.** Session-file writes must go through the locked session scripts (`_shared-rules.md` §5); a raw append bypasses the flock and can be clobbered by a concurrent `$park` or `$goodnight`.

7. **Display confirmation:**

```
✓ Project completed: [Project Name]
✓ Outcome: [Completed successfully / etc.]
[✓ Project file moved to: [actual destination path] | ✓ Project file deleted (no reference value) | ⚠ Move pending — file marked COMPLETED but still at [path]; intended destination [path]]
[✓ Link integrity verified (unresolved count unchanged: N) | ⚠ Link integrity UNVERIFIED (check timed out) | ⚠ N new unresolved links — triage before finishing]

Project completion complete. Well done.
```

## Guidelines

- **Explicit completion prevents drift:** Projects often fade rather than explicitly end - this forces a decision
- **Completion ≠ success:** Abandoned projects are valid completions. Acknowledging abandonment is better than indefinite limbo.
- **Outcomes:** Be honest - "Completed successfully", "Abandoned (lost interest)", "Superseded by X", "Merged into Y"
- **Route by value:** Useful reference → the Area's archival shelf (resolved from the vault's convention and confirmed in Step 4). No reference value → delete. `06 Archive/` holds only write-once records (a completion record, if one is written) — never the project file or working material.
- **Session archive is the completion log:** Completed projects are recorded in session logs, not in any dashboard
- **Preserve history where it earns its keep:** A doc with reference value stays intact, just moved. A doc nobody will re-read is deleted, not entombed.
- **Psychology matters:** Explicit completion provides closure and allows celebration

## When to Use This Command

**Use when:**
- Project genuinely complete (shipped, delivered, done)
- Project abandoned (decided not to pursue)
- Project superseded (better approach found)
- Project merged into larger work
- Project stalled for 30+ days with no intent to resume

**Don't use when:**
- Project just on hold temporarily or revivable → use `$set-project-status [Project Name] cold` (folder is status; Cold = paused, not dead)
- Waiting for external dependency
- Will resume within weeks

If unsure, ask the user: "Is this project truly complete, or just on hold?"

## Integration

- **03 Projects root:** moving the doc out of the root IS the de-registration — the active set stays clean by location
- **Weekly synthesis:** Can review session archive for completion patterns
- **Session summaries:** Searchable record of when projects ended
- **06 Archive:** write-once records only — a project's history lives with its Area, not in Archive

Files in this skill

  • SKILL.md18.5 KB
  • agents/openai.yaml241 B

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.