From 6bf1eebdd65e4da443d58efd7e210545eb51477a Mon Sep 17 00:00:00 2001 From: Philippos Savvides Date: Thu, 6 Aug 2026 07:58:14 -0700 Subject: [PATCH 1/2] feat!: drop the Codex CLI target, idstack is Claude Code only MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The OpenAI Codex CLI build that shipped in v2.5.0.0 is removed, and the Gemini CLI target that was on the roadmap is off it. Claude Code is the only host idstack was ever tested against end to end. Removed: - dist/codex/ (11 generated SKILL.md files) - AGENTS.md and templates/agent-context.md, the file it was generated from - setup's --codex / --no-codex flags and the whole Codex install block - bin/idstack-gen-skills' --target flag, the allowed-tools stripper, and render_codex_assets; one output layout now, skills//SKILL.md - ~/.agents/plugins/idstack and ~/.agents/skills/idstack from the $_IDSTACK resolution chain, in all five copies (snippet, three preamble blocks, manifest-schema longhand) - the preamble's per-CLI translation rule, and pipeline's Skill-unavailable branch that printed $ instructions The gate that replaces the 47 deleted Codex assertions is a repo-wide grep: nothing outside CHANGELOG.md may name the retired CLI. Three exemptions, each line-scoped and tagged. Three new mutations prove it bites, including one that catches a future rewrite from exempting lines to exempting the whole landing page. smoke-test 393 -> 353 assertions, mutation suite 18 -> 21. All ten suites green. docs/og-image.png regenerated from the edited template. Kept deliberately: the "Gemini Code Assist" attributions in test/. That is a GitHub PR-review bot that flagged the version classifier on #15, #19, #20 and #21 — it is why those cases exist, and it has nothing to do with Gemini CLI. Co-Authored-By: Claude Opus 5 --- .claude-plugin/plugin.json | 2 +- AGENTS.md | 223 --- CHANGELOG.md | 32 + CLAUDE.md | 30 +- CONTRIBUTING.md | 16 +- DESIGN.md | 2 +- README.md | 14 +- ROADMAP.md | 13 - TODOS.md | 21 +- VERSION | 2 +- bin/idstack-gen-skills | 131 +- .../idstack-accessibility-review/SKILL.md | 1160 ----------- .../skills/idstack-assessment-design/SKILL.md | 1356 ------------- .../skills/idstack-course-builder/SKILL.md | 1431 -------------- .../skills/idstack-course-export/SKILL.md | 1735 ----------------- .../skills/idstack-course-import/SKILL.md | 1555 --------------- .../idstack-course-quality-review/SKILL.md | 1556 --------------- dist/codex/skills/idstack-learn/SKILL.md | 454 ----- .../idstack-learning-objectives/SKILL.md | 1164 ----------- .../skills/idstack-needs-analysis/SKILL.md | 1114 ----------- dist/codex/skills/idstack-pipeline/SKILL.md | 487 ----- dist/codex/skills/idstack-red-team/SKILL.md | 773 -------- docs/index.html | 63 +- docs/og-image.png | Bin 104047 -> 100831 bytes docs/og-template.html | 2 +- docs/why-ai-native.md | 2 +- setup | 97 +- skills/accessibility-review/SKILL.md | 96 +- skills/assessment-design/SKILL.md | 96 +- skills/course-builder/SKILL.md | 88 +- skills/course-export/SKILL.md | 96 +- skills/course-import/SKILL.md | 80 +- skills/course-quality-review/SKILL.md | 88 +- skills/learn/SKILL.md | 98 +- skills/learn/SKILL.md.tmpl | 2 +- skills/learning-objectives/SKILL.md | 88 +- skills/needs-analysis/SKILL.md | 80 +- skills/pipeline/SKILL.md | 78 +- skills/pipeline/SKILL.md.tmpl | 14 +- skills/red-team/SKILL.md | 88 +- templates/agent-context.md | 223 --- templates/manifest-schema.md | 8 +- templates/preamble.md | 48 +- templates/snippets/idstack-resolve.sh | 8 +- test/integration-test.sh | 3 +- test/mutation-test.sh | 44 +- test/smoke-test.sh | 67 +- test/test-setup.sh | 10 +- test/test-version-classifier.sh | 2 +- 49 files changed, 692 insertions(+), 14148 deletions(-) delete mode 100644 AGENTS.md delete mode 100644 dist/codex/skills/idstack-accessibility-review/SKILL.md delete mode 100644 dist/codex/skills/idstack-assessment-design/SKILL.md delete mode 100644 dist/codex/skills/idstack-course-builder/SKILL.md delete mode 100644 dist/codex/skills/idstack-course-export/SKILL.md delete mode 100644 dist/codex/skills/idstack-course-import/SKILL.md delete mode 100644 dist/codex/skills/idstack-course-quality-review/SKILL.md delete mode 100644 dist/codex/skills/idstack-learn/SKILL.md delete mode 100644 dist/codex/skills/idstack-learning-objectives/SKILL.md delete mode 100644 dist/codex/skills/idstack-needs-analysis/SKILL.md delete mode 100644 dist/codex/skills/idstack-pipeline/SKILL.md delete mode 100644 dist/codex/skills/idstack-red-team/SKILL.md delete mode 100644 templates/agent-context.md diff --git a/.claude-plugin/plugin.json b/.claude-plugin/plugin.json index 7d90eda..b632f4d 100644 --- a/.claude-plugin/plugin.json +++ b/.claude-plugin/plugin.json @@ -1,6 +1,6 @@ { "name": "idstack", - "version": "3.3.0.4", + "version": "3.4.0.0", "description": "Evidence-based instructional design pipeline. 11 skills backed by peer-reviewed research across 11 domains. Analyze, design, build, review, and export courses with every recommendation citing its evidence tier.", "author": { "name": "Philippos Savvides", diff --git a/AGENTS.md b/AGENTS.md deleted file mode 100644 index 179892a..0000000 --- a/AGENTS.md +++ /dev/null @@ -1,223 +0,0 @@ -# idstack — agent context - -This file is auto-shipped to coding CLIs as `AGENTS.md` (Codex CLI), `GEMINI.md` (Gemini -CLI, future), or as the source for `CLAUDE.md` (Claude Code). It tells the model how -idstack is organized so a skill can do its job. - -## What is idstack - -An open-source set of skills that bring peer-reviewed instructional design research into -the course-design workflow. Each skill is a `SKILL.md` file with YAML frontmatter and a -conversational workflow backed by evidence from peer-reviewed research across 11 domains. - -idstack runs in multiple CLIs. The skill bodies are CLI-agnostic; small per-CLI shims -handle interaction primitives. - -## Architecture - -### Skills - -11 skills total. The 8-skill linear pipeline is the canonical sequence; `course-import` joins it -when there is an existing course to start from, and `learn` is a cross-project memory -utility that runs outside the pipeline. - -``` -pipeline (orchestrates the 8-skill linear chain, auto-skipping completed skills) - -needs-analysis → learning-objectives → assessment-design → course-builder - → course-quality-review → accessibility-review → red-team → course-export - ↑ -course-import ────────────────┘ - -learn (manage cross-project learnings) -``` - -Any skill also works standalone (asks questions directly if no manifest exists). -Invocation syntax depends on the host CLI: - -- Claude Code: `/idstack:` -- Codex CLI: `$` (or implicit selection by description) - -### Project manifest and reports — the dual-output contract - -Every skill that produces findings writes **both**: - -1. **JSON section** in `.idstack/project.json` — system state for downstream skills, the - pipeline orchestrator, and `bin/idstack-status`. -2. **HTML report** at `.idstack/exports//.html` — the human view, - branded and self-contained. Visual contract: `templates/report.html.tmpl` plus - `templates/assets/idstack.css`. Content contract: `templates/report-format.md`. The - skill writes the relative path back into its section's `report_path` field. - -The HTML is what the instructional designer reads (open in any browser; the folder is -self-contained with CSS bundled). The JSON is for the system. The two stay in sync — -every finding in the report corresponds to a finding in the manifest's structured arrays. - -`` is derived from the manifest's `project_name` via `bin/idstack-slugify` -(NFKD-fold, lowercase, kebab-case, ASCII-safe; empty → `untitled-course`). All per-course -artifacts — every per-skill HTML report, the pipeline `index.html` dashboard, and LMS -packages (`course-export.imscc`, `scorm-export.zip`) — live under the same -`.idstack/exports//` folder, so the deliverable is self-describing when -zipped, emailed, or handed off. - -`pipeline` produces `.idstack/exports//index.html` — the cross-cutting -course dashboard with readiness scores, links to every per-skill report, and a where-to- -start pointer. Regenerated on every pipeline run (including partial runs and explicit -re-runs when all skills are already complete). - -Manifest write rules: - -- Validate JSON on read. If malformed, report and stop. Never silently overwrite. -- Own your section only. Read full manifest, modify only your skill's section, preserve - everything else. -- Update the `updated` timestamp on every write. -- Use `bin/idstack-manifest-merge` for the write path: section-scoped, atomic (tempfile + - rename), preserves foreign sections, validates against the canonical schema in - `templates/manifest-schema.md`. Inline full-manifest edit is the deprecated fallback. -- **The one documented exception:** `needs-analysis` and `course-import` keep the - Read-modify-Write path because each writes several co-owned sections in one pass, which - whole-section merge cannot express. Both state why inline. Don't "fix" them to use the - merge tool, and don't copy their pattern into a single-section writer. -- Running standalone, call `bin/idstack-migrate --init` before merging. On a missing - manifest plain `idstack-migrate` is a no-op, so the merge that follows exits 4 and the - results are silently never persisted. - -Report write rules: - -- Follow the **visual contract** in `templates/report.html.tmpl` and the **content - contract** in `templates/report-format.md` — observation → evidence → why-it-matters → - suggestion, with severity (`critical|warning|info`) and evidence tier (`T1`–`T5`) on - every finding. Use the CSS hooks the stylesheet styles: `
`, ``, ``, `[Domain-N] [TN]`. -- Every skill that writes a report copies `$_IDSTACK/templates/assets/idstack.css` into - `.idstack/exports//assets/idstack.css` so the folder is self-contained - when zipped or moved. -- Phrase recommendations as suggestions ("consider…"), not directives. idstack is a - collaborator. -- Cite every recommendation. Findings without a `[Domain-N] [Tier]` citation belong in - *Limitations* or *Notes*, not *Findings*. - -### SKILL.md.tmpl structure - -Every skill template follows this pattern: - -1. **YAML frontmatter** with `name`, `description`, and (Claude target only) `allowed-tools` -2. **`{{PREAMBLE}}`** placeholder (replaced by `templates/preamble.md` during generation) -3. **Workflow** (Markdown defining the conversational flow, decision trees, outputs) -4. **`{{MANIFEST_SCHEMA}}`** placeholder (replaced by `templates/manifest-schema.md`) -5. **`{{IDSTACK_RESOLVE}}`** placeholder (replaced by `templates/snippets/idstack-resolve.sh`). - Unlike the other two it appears many times per template — once at the top of every - bash block that calls `$_IDSTACK/bin/...`. Bash blocks run in separate shells, so - `_IDSTACK` must be re-derived in each. The snippet is the single definition of that - resolution order: `CLAUDE_PLUGIN_ROOT`, `IDSTACK_HOME`, the Codex symlinks, then the - Claude Code marketplace cache. `templates/manifest-schema.md` is spliced verbatim and - writes the resolution out longhand; smoke-test keeps the two in lockstep. -6. **Timeline logging** (logs session data to `.idstack/timeline.jsonl` on completion) - -The shared preamble includes: update check, manifest check, preferences check, designer -profile check, and context recovery (reads timeline + learnings for welcome-back messages -and pipeline guidance). - -Python embedded in the preamble must parse on Python 3.9 — the version macOS ships. -`test/test-preamble-python.sh` runs every embedded block on 3.9 and 3.12; a syntax error -there dies silently at runtime, which is how context recovery stayed broken for several -releases. - -### Shared shell libraries - -Logic used by more than one script — or that deserves a unit test — lives in `bin/lib/` -and is sourced by its callers rather than inlined: - -- `bin/lib/version-classify.sh` — version comparison, shared by `setup` and `bin/idstack-doctor` -- `bin/lib/plugin-status.sh` — parses `claude plugin list` output into idstack's own entry - -Test the shipped file, never a copy. - -### Course memory - -Skills log session data to `.idstack/timeline.jsonl` (what skills ran, scores, -dimensions) and `.idstack/learnings.jsonl` (project-specific discoveries). The context -recovery preamble reads these on session start to provide continuity across -conversations. - -### Evidence standards - -Every recommendation in a skill must cite its evidence tier using domain codes from -`evidence/references.md`: - -| Tier | Meaning | -|------|---------| -| T1 | Meta-analyses, RCTs | -| T2 | Quasi-experimental with controls | -| T3 | Systematic reviews of mixed evidence | -| T4 | Observational, no comparison group | -| T5 | Expert opinion, theoretical frameworks | - -Format: `[DomainCode-Number] [Tier]` (e.g., `[Alignment-14] [T1]`). Stronger evidence -takes precedence when tiers conflict. - -### Interaction pattern - -- Ask one structured question at a time. Never batch multiple questions. (In Claude Code - this is the `AskUserQuestion` tool; in Codex CLI it is a numbered multiple-choice - question in plain text.) -- Skills must work without a manifest (fallback to asking questions directly). - -## Commands (run from the idstack install directory) - -```bash -./setup # Install for the detected CLI(s) -./setup --local # Install at project scope instead of user scope -./setup --codex / --no-codex # Force or skip the Codex bundle -./setup --keep-legacy # Leave pre-v2.0.1.0 installs in place -bin/idstack-gen-skills # Regenerate skill files for all targets -bin/idstack-gen-skills --target codex # Regenerate Codex flavor only -bin/idstack-gen-skills --dry-run # Check if generated files are up to date -bin/idstack-doctor # Diagnose installs across CLIs -bin/idstack-status # Course health dashboard (run in a project dir) -bin/idstack-status --readiness # Pre-export readiness check only -bin/idstack-migrate # Migrate .idstack/project.json to the latest schema -bin/idstack-migrate --init # Also create a canonical manifest when none exists -bin/idstack-manifest-merge --section --payload # Canonical manifest write path -bin/idstack-slugify "" # Derive the for .idstack/exports/ -``` - -Tests — all ten run in CI on every push and PR (`.github/workflows/test.yml`, -ubuntu on Python 3.9 + 3.12, macOS on 3.12): - -```bash -./test/smoke-test.sh # Install, SKILL.md freshness, frontmatter, version agreement, - # canonical section names, namespacing, resolve-snippet lockstep -./test/integration-test.sh # Behavioral tests across bin/; proves the suite leaves the tree untouched -./test/test-setup.sh # ./setup behavior: flags, scope, legacy cleanup, failure handling -./test/test-doctor.sh # bin/idstack-doctor PROBLEM/WARNING branches -./test/test-status.sh # bin/idstack-status, including the --readiness export gate -./test/test-manifest-merge.sh # bin/idstack-manifest-merge unit tests -./test/test-version-classifier.sh # bin/lib/version-classify.sh unit tests -./test/test-plugin-status.sh # bin/lib/plugin-status.sh unit tests -./test/test-preamble-python.sh # Runs the preamble's embedded python on 3.9 and 3.12 -./test/mutation-test.sh # Reintroduces each fixed defect, asserts its guarding test fails -``` - -`test/test-helper.sh` is sourced by every suite rather than run: it owns the -shared `PASS`/`FAIL`/`TOTAL` counters and the `check()` assertion. A suite must -not define its own counters — smoke-test fails on one. - -Python 3.9 is the oldest interpreter in the field (macOS system python3) and the -leg that catches modern-only syntax reaching the preamble's embedded scripts. - -No build step for users. No dependencies beyond bash (python3 recommended for full -features). Skills are plain Markdown files. - -## Design system - -Always read `DESIGN.md` (repo root) before making any visual or UI decision in this -project. All font choices, colors, spacing, border radii, motion timing, and aesthetic -direction are defined there. Do not deviate without explicit user approval and a -corresponding update to `DESIGN.md`. - -The canonical implementations are `templates/assets/idstack.css` (per-skill HTML -reports + course dashboard) and the inline ` - - - [Converted HTML content] - - -``` - -Name files as `module-01.html`, `module-02.html`, etc. matching module order. - -### C4. Generate imsmanifest.xml - -Write `$EXPORT_DIR/imsmanifest.xml` following the SCORM 1.2 specification: - -```xml - - - - - ADL SCORM - 1.2 - - - - - [Course Title] - - - [Module 1 Title] - - - - - - - - - - - - - -``` - -Rules for generating the manifest: -- Each module becomes one `` pointing to one `` -- Each resource is a SCO (type="webcontent", adlcp:scormtype="sco") -- Identifiers must be unique within the manifest -- Sanitize the course title for use in the manifest identifier (lowercase, hyphens, - no special characters) -- If modules have sub-modules, nest `` elements accordingly - -### C5. Package as ZIP - -```bash -# Package. $EXPORT_DIR here is the mktemp staging dir; $_EXPORT_DIR is the -# course-named folder under .idstack/exports// from the prep step above. -cd "$EXPORT_DIR" -zip -r scorm-export.zip imsmanifest.xml content/ -mv "$EXPORT_DIR/scorm-export.zip" "$_EXPORT_DIR/scorm-export.zip" -echo "SCORM package saved to $_EXPORT_DIR/scorm-export.zip" -``` - -### C6. Verify package - -```bash -file "$_EXPORT_DIR/scorm-export.zip" -unzip -l "$_EXPORT_DIR/scorm-export.zip" | head -20 -echo "Total files: $(unzip -l "$_EXPORT_DIR/scorm-export.zip" | tail -1)" -``` - -Verify: -- `imsmanifest.xml` exists at the root of the ZIP -- All `` references in the manifest have matching files in the ZIP -- The ZIP is not empty - -### C7. Present export summary - -``` -## SCORM 1.2 Export Complete - -File: .idstack/exports//scorm-export.zip -Format: SCORM 1.2 -SCOs: [count] (one per module) -Total files: [count] - -### How to import - -- **Any LMS:** Upload the .zip file through your LMS admin interface. - Most LMS platforms auto-detect SCORM packages. -- **Canvas:** Settings > Import Course Content > SCORM package -- **Moodle:** Add Activity > SCORM package > Upload -- **Blackboard:** Content > Build Content > SCORM package -- **Corporate LMS (Cornerstone, SAP SuccessFactors, etc.):** - Upload through your content management interface - -### Limitations - -- This SCORM package contains static HTML content. Interactive elements - (drag-and-drop, branching scenarios) are not generated. -- SCORM API tracking (completion, score reporting) is not included. - The LMS will mark the SCO as complete when the learner opens it. -- For richer interactivity, author in Articulate Rise or Storyline and - use idstack's /idstack:course-quality-review and /idstack:red-team on the exported package. -``` - -### C8. Cleanup - -```bash -rm -rf "$EXPORT_DIR" -``` - ---- - -## Generate Export Report - -After the export completes (any path), write the HTML report at `$_REPORT_PATH` (from the Prepare Course Export Folder step) — i.e., `.idstack/exports//course-export.html`. The report follows the **visual contract** in `templates/report.html.tmpl` and the **content contract** in `templates/report-format.md`. Use the standard CSS hooks: `
`, ``, ``, `[Domain-N] [TN]`. Customize for this skill: - -- **`{{skill_title}}`:** "Course Export Report" -- **`{{skill_name}}`:** `course-export` -- **`{{mode}}`:** include `format: imscc|canvas-api|scorm` in the header `meta` line. -- **Summary:** 2–3 sentences. What was exported, where it landed, and the readiness verdict (clean / with warnings) at the time of export. Include the optional one-line scoreboard: "Modules N · Pages M · Assignments P · Quizzes Q · Discussions R". -- **Skill-specific section before Findings** — add a `
` with `

Export contents

` and an HTML `` (Item type, Count, Notes). For Canvas API: include Created / Failed / Skipped columns instead. -- **Finding ids:** `export-1`, `readiness-1`, `qti-1`, etc. Findings come from auto-generated content the designer should review before publishing (quiz questions, rubric formatting, missing placeholders), readiness-check items at time of export, and items the API call failed on. If everything is clean, the Findings section can simply contain an `info` finding noting the clean state. -- **Optional skill-specific section** (after Top recommendations, before Limitations): `
` with `

Failed items

` only when the Canvas API path produced failed items. -- **Limitations:** SCORM/IMSCC packages contain static HTML; interactive elements aren't generated. Quiz questions are auto-generated from assessment specs and need designer review. Tokens are never written to disk. -- **Next steps:** Verify that the LMS import preserved everything correctly. Pay particular attention to quiz questions, assignment rubrics, discussion prompts, and module sequencing. If the readiness verdict was "with warnings," address the flagged findings before publishing. - ---- - -## Manifest Write - -After export completes (any path) and the report is written, update the project -manifest with export metadata. - -Save results via the section-scoped merge tool — it validates the section name -against the canonical schema, preserves every other section verbatim, bumps the -top-level `updated` timestamp, and writes atomically: - -```bash -# Resolve the idstack install dir. Re-derived at the top of every bash block — -# blocks run in separate shells, so a value derived in an earlier block is not -# available here. Priority: explicit env overrides, Codex-style symlinks, then -# the Claude Code marketplace cache. Empty if none found; guard -# "$_IDSTACK/bin/..." calls accordingly. -_IDSTACK="" -# Marketplace cache holds one dir per installed version. Sort the basenames by -# numeric version fields, not lexically — plain sort ranks 3.9.0.0 above -# 3.10.0.0 and would pick a stale install once the minor hits double digits. -_idstack_cache_root="$HOME/.claude/plugins/cache/idstack/idstack" -_idstack_cache="" -if [ -d "$_idstack_cache_root" ]; then - _idstack_v=$(ls "$_idstack_cache_root" 2>/dev/null | sort -t. -k1,1n -k2,2n -k3,3n -k4,4n | tail -1) - [ -n "$_idstack_v" ] && _idstack_cache="$_idstack_cache_root/$_idstack_v" -fi -for _p in "${CLAUDE_PLUGIN_ROOT:-}" "${IDSTACK_HOME:-}" "$HOME/.agents/plugins/idstack" "$HOME/.agents/skills/idstack" "$_idstack_cache"; do - if [ -n "$_p" ] && [ -d "$_p" ]; then _IDSTACK="${_p%/}"; break; fi -done -# payload file holds ONLY the export_metadata section object (shape below) -"$_IDSTACK/bin/idstack-manifest-merge" --section export_metadata --payload .idstack/.export_metadata-payload.json && rm -f .idstack/.export_metadata-payload.json -``` - -Write the payload object to `.idstack/.export_metadata-payload.json` with the -Write tool first, then run the merge. **If no manifest exists yet** (unlikely -for export, but possible), the merge tool exits with code 4 (`manifest not -found`) — in that case fall back to creating `.idstack/project.json` with the -Write tool, initializing ALL sections from the schema with empty/default values -and placing your `export_metadata` data inside it. - -**CRITICAL -- Manifest Integrity Rules:** -1. The payload must be valid JSON: matching braces, proper commas, quoted - strings, no trailing commas. -2. NEVER write secrets (Canvas tokens, credentials) to the manifest or the - payload file. - -### Readiness Info - -Before starting the export, check the manifest for prior review data. If any of -these sections exist, show a brief readiness summary as context (not a gate): - -``` -Export readiness: - Quality review: 78/100 ✓ (reviewed 2026-04-08) - Red-team audit: 2 critical, 3 warning - Accessibility review: WCAG score 70/100, 1 AA violation -``` - -If a section doesn't exist, show: "Not reviewed — run /idstack: for analysis." - -This is informational. Export proceeds regardless. The user can choose to address -findings first or export now, no AskUserQuestion needed, just show the info and continue. - -### Write Export Metadata - -The payload file holds the `export_metadata` section object (shown here wrapped -in its manifest key for context — the payload file contains only the inner object): - -```json -{ - "export_metadata": { - "report_path": "/course-export.html>", - "exported_at": "ISO-8601 timestamp", - "format": "imscc|canvas-api|scorm", - "destination": "file path (.idstack/exports//course-export.imscc) or Canvas URL", - "items_exported": { - "modules": 0, - "pages": 0, - "assignments": 0, - "quizzes": 0, - "discussions": 0 - }, - "failed_items": [], - "notes": "", - "readiness_check": { - "quality_score": 0, - "quality_reviewed": true, - "red_team_critical": 0, - "red_team_reviewed": false, - "accessibility_critical": 0, - "accessibility_reviewed": false, - "verdict": "export_clean|export_with_warnings" - } - } -} -``` - -The `readiness_check` section captures the state of prior reviews at export time. -Populate it by reading the `quality_review`, `red_team_audit`, and `accessibility_review` -sections from the manifest (if they exist). The `verdict` is: -- `export_clean`: all reviewed, no critical findings -- `export_with_warnings`: reviewed but has critical/warning findings -- `export_blocked`: not used (export never blocks, advisory only) -- Empty string if no reviews exist - -Run the merge, then confirm: - -"Your export metadata has been saved to `.idstack/project.json`. - -Your course has been exported. Verify that the LMS import preserved -everything correctly. Pay particular attention to: -- Quiz questions (auto-generated, may need editing) -- Assignment rubrics (verify formatting survived the transfer) -- Discussion prompts (check that instructions are clear) -- Module sequencing (verify order matches your intended flow)" - ---- - -## Manifest Schema Reference - -The idstack manifest lives at `.idstack/project.json`. Schema version: **1.4**. - -This is the canonical schema. Every skill writes to its own section using the shapes documented here; **all other sections must be preserved verbatim**. There is one source of truth — this file. If the schema ever needs to change, edit `templates/manifest-schema.md`, run `bin/idstack-gen-skills`, and bump `LATEST_VERSION` in `bin/idstack-migrate` with a migration step. - -### Two outputs per skill: JSON manifest + HTML report - -Every skill that produces findings emits **both**: - -- a **JSON section** in this manifest (system state — read by other skills, the pipeline orchestrator, and `bin/idstack-status`), and -- an **HTML report** at `.idstack/exports//.html` (the human view — read by the instructional designer). - -The HTML report follows the visual contract in `templates/report.html.tmpl` and the content contract in `templates/report-format.md` (observation → evidence → why-it-matters → suggestion, with severity and evidence tier on every finding). The skill writes the report's relative path back into its own section's `report_path` field so other skills can find it. (`bin/idstack-status` discovers reports independently by globbing `.idstack/exports//*.html`, so the dashboard survives a stale `report_path`.) - -`` is derived from the top-level `project_name` field via `bin/idstack-slugify` (rule: NFKD-fold, lowercase, kebab-case, ASCII-safe; empty input → `untitled-course`). The slug is computed deterministically — skills don't cache it in the manifest. All exports for a course — per-skill HTML reports, the pipeline dashboard at `index.html`, and LMS packages (`course-export.imscc`, `scorm-export.zip`) — live under the same `.idstack/exports//` folder so the deliverable is self-describing when zipped, emailed, or handed off. - -`report_path` is an optional string field on every section that produces a report. It is a path relative to the project root (typically `.idstack/exports//.html`). Empty string means the skill hasn't run yet, or ran in a mode that didn't produce a report. Renaming a course's `project_name` changes the slug, which moves future exports to a new folder; older folders are left in place. - -### Two ways to write to the manifest - -**1. Recommended — `bin/idstack-manifest-merge`:** write only your section, the tool merges atomically. - -```bash -# Resolve the idstack install dir. Re-derived at the top of every bash block — -# blocks run in separate shells, so a value derived in an earlier block is not -# available here. Priority: explicit env overrides, Codex-style symlinks, then -# the Claude Code marketplace cache. Empty if none found; guard -# "$_IDSTACK/bin/..." calls accordingly. -_IDSTACK="" -# Marketplace cache holds one dir per installed version. Sort the basenames by -# numeric version fields, not lexically — plain sort ranks 3.9.0.0 above -# 3.10.0.0 and would pick a stale install once the minor hits double digits. -_idstack_cache_root="$HOME/.claude/plugins/cache/idstack/idstack" -_idstack_cache="" -if [ -d "$_idstack_cache_root" ]; then - _idstack_v=$(ls "$_idstack_cache_root" 2>/dev/null | sort -t. -k1,1n -k2,2n -k3,3n -k4,4n | tail -1) - [ -n "$_idstack_v" ] && _idstack_cache="$_idstack_cache_root/$_idstack_v" -fi -for _p in "${CLAUDE_PLUGIN_ROOT:-}" "${IDSTACK_HOME:-}" "$HOME/.agents/plugins/idstack" "$HOME/.agents/skills/idstack" "$_idstack_cache"; do - if [ -n "$_p" ] && [ -d "$_p" ]; then _IDSTACK="${_p%/}"; break; fi -done -# Write a payload for your skill's section, then: -"$_IDSTACK/bin/idstack-manifest-merge" --section red_team_audit --payload /tmp/payload.json -``` - -(This file is spliced into skills verbatim, so the resolution above is written -out rather than using the `IDSTACK_RESOLVE` placeholder — that placeholder is -only expanded in `SKILL.md.tmpl` bodies.) - -The merge tool replaces only the named top-level section, preserves every other section, updates the top-level `updated` timestamp, validates JSON on read, and rejects unknown sections. Use this in preference to inlining the full manifest in `Edit` operations. - -**2. Fallback — manual full-manifest write:** if the merge tool is unavailable for some reason, Read the full manifest, modify only your section, Write back. Preserve all other sections verbatim. Use the full schema below as reference. - -### Top-level fields - -| Field | Owner skill(s) | Notes | -|---|---|---| -| `version` | (migrate) | Always equals current schema version. Auto-managed by `bin/idstack-migrate`. | -| `project_name` | (any) | Set on first manifest creation. Don't overwrite once set. | -| `created` | (any, once) | ISO-8601 timestamp of first creation. Don't overwrite. | -| `updated` | (any) | ISO-8601 of last write. Updated automatically by `bin/idstack-manifest-merge`. | -| `context` | needs-analysis (initial) | Modality, timeline, class size, etc. Edited by skills that learn new context. | -| `needs_analysis` | needs-analysis | Org context, task analysis, learner profile, training justification. | -| `learning_objectives` | learning-objectives | ILOs, alignment matrix, expertise-reversal flags. | -| `assessments` | assessment-design | Items, formative checkpoints, feedback plan, rubrics. | -| `course_content` | course-builder | Generated modules, syllabus, content paths. | -| `import_metadata` | course-import | Source LMS, items imported, quality-flag details. | -| `export_metadata` | course-export | Export destination, items exported, readiness check. | -| `quality_review` | course-quality-review | QM standards, CoI presence, alignment audit, cross-domain checks, scores. | -| `red_team_audit` | red-team | Confidence score, dimensions, findings (with stable ids), top actions. | -| `accessibility_review` | accessibility-review | WCAG / UDL scores, violations, recommendations, quick wins. | -| `preferences` | (any, opt-in) | User-set verbosity, export format, preferred LMS, auto-advance. | - -### Full schema (canonical shape) - -```json -{ - "version": "1.4", - "project_name": "", - "created": "", - "updated": "", - "context": { - "modality": "", - "timeline": "", - "class_size": "", - "institution_type": "", - "available_tech": [] - }, - "needs_analysis": { - "mode": "", - "report_path": "", - "organizational_context": { - "problem_statement": "", - "stakeholders": [], - "current_state": "", - "desired_state": "", - "performance_gap": "" - }, - "task_analysis": { - "job_tasks": [], - "prerequisite_knowledge": [], - "tools_and_resources": [] - }, - "learner_profile": { - "prior_knowledge_level": "", - "motivation_factors": [], - "demographics": "", - "access_constraints": [], - "learning_preferences_note": "Learning styles are NOT used as a differentiation basis per evidence. Prior knowledge is the primary differentiator." - }, - "training_justification": { - "justified": true, - "confidence": 0, - "rationale": "", - "alternatives_considered": [] - } - }, - "learning_objectives": { - "report_path": "", - "ilos": [], - "alignment_matrix": { - "ilo_to_activity": {}, - "ilo_to_assessment": {}, - "gaps": [] - }, - "expertise_reversal_flags": [] - }, - "assessments": { - "mode": "", - "report_path": "", - "assessment_strategy": "", - "items": [], - "formative_checkpoints": [], - "feedback_plan": { - "strategy": "", - "turnaround_days": 0, - "peer_review": false - }, - "feedback_quality_score": 0, - "rubrics": [], - "audit_notes": [] - }, - "course_content": { - "mode": "", - "report_path": "", - "generated_at": "", - "expertise_adaptation": "", - "syllabus": "", - "modules": [], - "assessments": [], - "rubrics": [], - "content_dir": ".idstack/course-content/", - "generated_files": [], - "build_timestamp": "", - "placeholders_used": [], - "recommended_generation_targets": [] - }, - "import_metadata": { - "source": "", - "report_path": "", - "imported_at": "", - "source_lms": "", - "source_cartridge": "", - "source_size_bytes": 0, - "schema": "", - "items_imported": { - "modules": 0, - "objectives": 0, - "module_objectives": 0, - "assessments": 0, - "activities": 0, - "pages": 0, - "rubrics": 0, - "quizzes": 0, - "discussions": 0 - }, - "quality_flags": 0, - "quality_flag_details": [] - }, - "export_metadata": { - "report_path": "", - "exported_at": "", - "format": "", - "destination": "", - "items_exported": { - "modules": 0, - "pages": 0, - "assignments": 0, - "quizzes": 0, - "discussions": 0 - }, - "failed_items": [], - "notes": "", - "readiness_check": { - "quality_score": 0, - "quality_reviewed": false, - "red_team_critical": 0, - "red_team_reviewed": false, - "accessibility_critical": 0, - "accessibility_reviewed": false, - "verdict": "" - } - }, - "quality_review": { - "report_path": "", - "last_reviewed": "", - "qm_standards": { - "course_overview": {"status": "", "findings": []}, - "learning_objectives": {"status": "", "findings": []}, - "assessment": {"status": "", "findings": []}, - "instructional_materials": {"status": "", "findings": []}, - "learning_activities": {"status": "", "findings": []}, - "course_technology": {"status": "", "findings": []}, - "learner_support": {"status": "", "findings": []}, - "accessibility": {"status": "", "findings": []} - }, - "coi_presence": { - "teaching_presence": {"score": 0, "findings": []}, - "social_presence": {"score": 0, "findings": []}, - "cognitive_presence": {"score": 0, "findings": []} - }, - "alignment_audit": {"findings": []}, - "cross_domain_checks": { - "cognitive_load": {"score": 0, "flags": []}, - "multimedia_principles": {"score": 0, "flags": []}, - "feedback_quality": {"score": 0, "flags": []}, - "expertise_reversal": {"score": 0, "flags": []} - }, - "overall_score": 0, - "score_breakdown": { - "qm_structural": 0, - "coi_presence": 0, - "constructive_alignment": 0, - "cross_domain_evidence": 0 - }, - "quick_wins": [], - "recommendations": [], - "review_history": [] - }, - "red_team_audit": { - "updated": "", - "confidence_score": 0, - "focus": "", - "report_path": "", - "findings_summary": {"critical": 0, "warning": 0, "info": 0}, - "dimensions": { - "alignment": {"score": "", "findings": []}, - "evidence": {"score": "", "mode": "", "findings": []}, - "cognitive_load": {"score": "", "findings": []}, - "personas": {"score": "", "findings": []}, - "prerequisites": {"score": "", "findings": []} - }, - "top_actions": [], - "limitations": [], - "fixes_applied": [], - "fixes_deferred": [] - }, - "accessibility_review": { - "updated": "", - "report_path": "", - "score": {"overall": 0, "wcag": 0, "udl": 0}, - "wcag_violations": [], - "udl_recommendations": [], - "quick_wins": [] - }, - "preferences": { - "verbosity": "normal", - "export_format": "", - "preferred_lms": "", - "auto_advance_pipeline": false - } -} -``` - -### Per-section item shapes - -These document the **shape of array elements and dictionary values** that the canonical schema leaves as `[]` or `{}`. Skills should produce items in these shapes; downstream skills can rely on them. - -**`learning_objectives.alignment_matrix.ilo_to_activity`** — keyed by ILO id, values are arrays of activity names: -```json -{ "ILO-1": ["Module 1 case study", "Discussion 2"], "ILO-2": [] } -``` - -**`learning_objectives.alignment_matrix.ilo_to_assessment`** — same shape, values are arrays of assessment titles. - -**`learning_objectives.alignment_matrix.gaps[]`** — each item: -```json -{ - "ilo": "ILO-1", - "type": "untested|orphaned|underspecified|bloom_mismatch", - "description": "ILO-1 has no matching assessment in the active modules.", - "severity": "critical|warning|info" -} -``` - -**`learning_objectives.ilos[]`** — each item: -```json -{ - "id": "ILO-1", - "statement": "Analyze competitive forces in...", - "blooms_level": "analyze", - "blooms_confidence": "high|medium|low" -} -``` - -**`assessments.items[]`** — each item: -```json -{ - "id": "A-1", - "type": "quiz|discussion|rubric|peer_review|gate|...", - "title": "Module 1 Quiz", - "weight": 5, - "ilos_measured": ["ILO-1", "ILO-3"], - "rubric_present": true, - "elaborated_feedback": false, - "alignment_status": "weak|moderate|strong" -} -``` - -**`assessments.rubrics[]`** — each item: -```json -{ - "id": "rubric-1", - "title": "SM Project Rubric", - "criteria": [{"name": "...", "blooms_level": "...", "weight": 0}], - "applies_to": ["A-3"] -} -``` - -**`import_metadata.quality_flag_details[]`** — each item (replaces the legacy `_import_quality_flags` root field that sometimes appeared in the wild): -```json -{ - "key": "orphan_module_8", - "description": "Module 8 wiki content exists in the cartridge but is not referenced in .", - "severity": "warning|critical|info", - "evidence": "Optional citation tag, e.g. [Alignment-1] [T5]" -} -``` - -**`red_team_audit.dimensions..findings[]`** — each item (matches the `-` id convention from the red-team orchestrator): -```json -{ - "id": "alignment-1", - "description": "ILO-2 (vision/mission) has no matching assessment.", - "module": "Module 4", - "severity": "critical|warning|info" -} -``` - -**`accessibility_review.wcag_violations[]`** — each item: -```json -{ - "id": "wcag-1", - "criterion": "1.3.1 Info and Relationships", - "level": "A|AA|AAA", - "description": "All cartridge HTML pages lack

elements.", - "affected": ["page1.html", "page2.html"], - "severity": "critical|warning|info" -} -``` - -**`accessibility_review.udl_recommendations[]`** — each item: -```json -{ - "id": "udl-1", - "principle": "engagement|representation|action_expression", - "description": "Add transcripts to all videos.", - "status": "fully_met|partial|not_met" -} -``` - -**`quality_review.qm_standards..findings[]`**, **`quality_review.alignment_audit.findings[]`**, **`quality_review.cross_domain_checks..flags[]`**, and other findings arrays — each item: -```json -{ - "id": "-", - "description": "...", - "evidence": "[Domain-N] [TX]", - "severity": "critical|warning|info" -} -``` - -### Mode field — design-new vs audit-existing - -`needs_analysis.mode`, `assessments.mode`, and `course_content.mode` record which operating mode the corresponding skill ran in. Trigger: `import_metadata.source` ∈ `{cartridge, scorm, canvas-api}` plus the relevant section being non-empty (skill-specific check). - -Allowed values per skill: -- `needs_analysis.mode`: `"design-new"` or `"audit-existing"` -- `assessments.mode`: `"Mode 1"`, `"Mode 2"`, or `"Mode 3"` (Mode 1 = full upstream data, Mode 2 = ILOs-from-scratch, Mode 3 = audit existing assessments) -- `course_content.mode`: `"build-new"` or `"gap-fill"` - -Empty string means the skill hasn't run yet or didn't record the mode (legacy manifests). - -**`assessments.audit_notes[]`** — only populated in Mode 3. Records which audit findings the user chose to act on: -```json -{ - "target_id": "A-3", - "action": "applied|deferred|declined", - "description": "Rubric criterion for ILO-2 added: 'Synthesis depth (1-4 scale)'.", - "reason": "Optional — only meaningful for deferred/declined." -} -``` - -**`course_content.recommended_generation_targets[]`** — populated in `gap-fill` mode. Lists artifacts upstream skills flagged as missing, with status: -```json -{ - "description": "Discussion rubric for Module 5", - "source": "red-team:alignment-3 | quality-review:learner_support-2 | user-request", - "status": "generated|deferred|declined", - "output_path": "Optional — set when status=generated, points to the generated file." -} -``` - -## Feedback - -Have feedback or a feature request? [Share it here](https://forms.gle/6LDgDD1M6WWyYvME8) — no GitHub account needed. - ---- - -## Completion: Timeline Logging - -After the skill workflow completes successfully, log the session to the timeline: - -```bash -# Resolve the idstack install dir. Re-derived at the top of every bash block — -# blocks run in separate shells, so a value derived in an earlier block is not -# available here. Priority: explicit env overrides, Codex-style symlinks, then -# the Claude Code marketplace cache. Empty if none found; guard -# "$_IDSTACK/bin/..." calls accordingly. -_IDSTACK="" -# Marketplace cache holds one dir per installed version. Sort the basenames by -# numeric version fields, not lexically — plain sort ranks 3.9.0.0 above -# 3.10.0.0 and would pick a stale install once the minor hits double digits. -_idstack_cache_root="$HOME/.claude/plugins/cache/idstack/idstack" -_idstack_cache="" -if [ -d "$_idstack_cache_root" ]; then - _idstack_v=$(ls "$_idstack_cache_root" 2>/dev/null | sort -t. -k1,1n -k2,2n -k3,3n -k4,4n | tail -1) - [ -n "$_idstack_v" ] && _idstack_cache="$_idstack_cache_root/$_idstack_v" -fi -for _p in "${CLAUDE_PLUGIN_ROOT:-}" "${IDSTACK_HOME:-}" "$HOME/.agents/plugins/idstack" "$HOME/.agents/skills/idstack" "$_idstack_cache"; do - if [ -n "$_p" ] && [ -d "$_p" ]; then _IDSTACK="${_p%/}"; break; fi -done -"$_IDSTACK/bin/idstack-timeline-log" '{"skill":"course-export","event":"completed"}' -``` - -Replace the JSON above with actual data from this session. Include skill-specific fields -where available (scores, counts, flags). Log synchronously (no background &). - -If you discover a non-obvious project-specific quirk during this session (LMS behavior, -import format issue, course structure pattern), also log it as a learning: - -```bash -# Resolve the idstack install dir. Re-derived at the top of every bash block — -# blocks run in separate shells, so a value derived in an earlier block is not -# available here. Priority: explicit env overrides, Codex-style symlinks, then -# the Claude Code marketplace cache. Empty if none found; guard -# "$_IDSTACK/bin/..." calls accordingly. -_IDSTACK="" -# Marketplace cache holds one dir per installed version. Sort the basenames by -# numeric version fields, not lexically — plain sort ranks 3.9.0.0 above -# 3.10.0.0 and would pick a stale install once the minor hits double digits. -_idstack_cache_root="$HOME/.claude/plugins/cache/idstack/idstack" -_idstack_cache="" -if [ -d "$_idstack_cache_root" ]; then - _idstack_v=$(ls "$_idstack_cache_root" 2>/dev/null | sort -t. -k1,1n -k2,2n -k3,3n -k4,4n | tail -1) - [ -n "$_idstack_v" ] && _idstack_cache="$_idstack_cache_root/$_idstack_v" -fi -for _p in "${CLAUDE_PLUGIN_ROOT:-}" "${IDSTACK_HOME:-}" "$HOME/.agents/plugins/idstack" "$HOME/.agents/skills/idstack" "$_idstack_cache"; do - if [ -n "$_p" ] && [ -d "$_p" ]; then _IDSTACK="${_p%/}"; break; fi -done -"$_IDSTACK/bin/idstack-learnings-log" '{"skill":"course-export","type":"operational","key":"SHORT_KEY","insight":"DESCRIPTION","confidence":8,"source":"observed"}' -``` diff --git a/dist/codex/skills/idstack-course-import/SKILL.md b/dist/codex/skills/idstack-course-import/SKILL.md deleted file mode 100644 index cc0c7c4..0000000 --- a/dist/codex/skills/idstack-course-import/SKILL.md +++ /dev/null @@ -1,1555 +0,0 @@ ---- -name: course-import -description: | - Universal course import from any LMS. Reads IMS Common Cartridge files, - pasted course documents, or Canvas REST API. Maps course structure to the - idstack manifest with quality flags, task analysis, and Bloom's classification. - Works with Canvas, Blackboard, Moodle, and D2L. (idstack) ---- - - - - -## Preamble: Interaction Conventions - -idstack skills are designed to run in multiple CLIs (Claude Code, OpenAI Codex CLI, and -others). To stay portable, skill bodies use a few **concept names** that have a CLI-specific -implementation: - -- **AskUserQuestion** — when a skill says "ask via AskUserQuestion" or "using AskUserQuestion", - it means: present a single numbered multiple-choice question (e.g., "Which of these best - describes X? 1) ..., 2) ..., 3) Other") and stop, waiting for the user's next message - before proceeding. Ask **one** question at a time, never batch. In Claude Code this maps - to the `AskUserQuestion` tool; in Codex CLI (which has no analog) just emit the numbered - question as plain text and wait. -- **Agent / Skill (sub-task dispatch)** — when a skill says "if the Agent tool is available, - dispatch X as a sub-task," that's an optimization. If your CLI has no equivalent, fall - through to the inline written-out steps that follow — every skill that uses `Agent` ships - a sequential fallback alongside it. -- **Skill (cross-skill invocation)** — used only by `/idstack:pipeline`. In Claude Code this - invokes a child skill in-process via the `Skill` tool. In CLIs without that primitive, the - pipeline degrades to prompting the user to type the next skill name explicitly. -- **Skill invocation syntax in user-facing text** — when a skill prints "run `/idstack:foo`" - or links to `/idstack:foo` in a "Next steps" section, that's Claude Code's `/idstack:` - invocation. **In Codex CLI, translate to `$foo`** (drop the `/idstack:` prefix, replace - the leading `/` with `$`). Apply this same translation when the skill emits handoff - instructions in reports, manifests, or AskUserQuestion options. Same body text, two - hosts; the model translates per-CLI on output. - -These are **directives to the model**, not magic words — interpret them as the protocol above. - -## Preamble: Update Check - -```bash -# Resolve the idstack install dir. Re-derived at the top of every bash block — -# blocks run in separate shells, so a value derived in an earlier block is not -# available here. Priority: explicit env overrides, Codex-style symlinks, then -# the Claude Code marketplace cache (highest version). Empty if none found; -# guard "$_IDSTACK/bin/..." calls accordingly. -# Canonical copy: templates/snippets/idstack-resolve.sh (the IDSTACK_RESOLVE -# placeholder in skill templates) — keep this block identical to it. -_IDSTACK="" -# Marketplace cache holds one dir per installed version. Sort the basenames by -# numeric version fields, not lexically — plain sort ranks 3.9.0.0 above -# 3.10.0.0 and would pick a stale install once the minor hits double digits. -_idstack_cache_root="$HOME/.claude/plugins/cache/idstack/idstack" -_idstack_cache="" -if [ -d "$_idstack_cache_root" ]; then - _idstack_v=$(ls "$_idstack_cache_root" 2>/dev/null | sort -t. -k1,1n -k2,2n -k3,3n -k4,4n | tail -1) - [ -n "$_idstack_v" ] && _idstack_cache="$_idstack_cache_root/$_idstack_v" -fi -for _p in "${CLAUDE_PLUGIN_ROOT:-}" "${IDSTACK_HOME:-}" "$HOME/.agents/plugins/idstack" "$HOME/.agents/skills/idstack" "$_idstack_cache"; do - if [ -n "$_p" ] && [ -d "$_p" ]; then _IDSTACK="${_p%/}"; break; fi -done -_UPD=$("$_IDSTACK/bin/idstack-update-check" 2>/dev/null || true) -[ -n "$_UPD" ] && echo "$_UPD" -``` - -If the output contains `UPDATE_AVAILABLE`: tell the user "A newer version of idstack is available. Run `cd $_IDSTACK && git pull && ./setup` to update. (The `./setup` step is required — it cleans up legacy symlinks.)" Then continue normally. - -## Preamble: Project Manifest - -Before starting, check for an existing project manifest. - -```bash -# (fresh shell — re-derive the install dir; see Preamble: Update Check) -_IDSTACK="" -# Marketplace cache holds one dir per installed version. Sort the basenames by -# numeric version fields, not lexically — plain sort ranks 3.9.0.0 above -# 3.10.0.0 and would pick a stale install once the minor hits double digits. -_idstack_cache_root="$HOME/.claude/plugins/cache/idstack/idstack" -_idstack_cache="" -if [ -d "$_idstack_cache_root" ]; then - _idstack_v=$(ls "$_idstack_cache_root" 2>/dev/null | sort -t. -k1,1n -k2,2n -k3,3n -k4,4n | tail -1) - [ -n "$_idstack_v" ] && _idstack_cache="$_idstack_cache_root/$_idstack_v" -fi -for _p in "${CLAUDE_PLUGIN_ROOT:-}" "${IDSTACK_HOME:-}" "$HOME/.agents/plugins/idstack" "$HOME/.agents/skills/idstack" "$_idstack_cache"; do - if [ -n "$_p" ] && [ -d "$_p" ]; then _IDSTACK="${_p%/}"; break; fi -done -if [ -f ".idstack/project.json" ]; then - echo "MANIFEST_EXISTS" - "$_IDSTACK/bin/idstack-migrate" .idstack/project.json 2>/dev/null || cat .idstack/project.json -else - echo "NO_MANIFEST" -fi -``` - -**If MANIFEST_EXISTS:** -- Read the manifest. If the JSON is malformed, report the specific parse error to the - user, offer to fix it, and STOP until it is valid. Never silently overwrite corrupt JSON. -- Preserve all existing sections when writing back. - -**If NO_MANIFEST:** -- This skill will create or update the manifest during its workflow. - -## Preamble: Preferences - -```bash -if [ -f ".idstack/project.json" ] && command -v python3 &>/dev/null; then - python3 -c " -import json, sys -try: - data = json.load(open('.idstack/project.json')) - prefs = data.get('preferences', {}) - v = prefs.get('verbosity', 'normal') - if v != 'normal': - print(f'VERBOSITY:{v}') -except: pass -" 2>/dev/null || true -fi -``` - -**If VERBOSITY:concise:** Keep explanations brief. Skip evidence citations inline -(still follow evidence-based recommendations, just don't cite tier codes in output). -**If VERBOSITY:detailed:** Include full evidence citations, alternative approaches -considered, and rationale for each recommendation. -**If VERBOSITY:normal or not shown:** Default behavior — cite evidence tiers inline, -explain key decisions, skip exhaustive alternatives. - -## Preamble: Designer Profile - -```bash -_PROFILE="$HOME/.idstack/profile.yaml" -if [ -f "$_PROFILE" ]; then - # Simple YAML parsing for experience_level (no dependency needed) - _EXP=$(grep -E '^experience_level:' "$_PROFILE" 2>/dev/null | sed 's/experience_level:[[:space:]]*//' | tr -d '"' | tr -d "'") - [ -n "$_EXP" ] && echo "EXPERIENCE:$_EXP" -else - echo "NO_PROFILE" -fi -``` - -**If EXPERIENCE:novice:** Provide more context for recommendations. Explain WHY each -step matters, not just what to do. Define jargon on first use. Offer examples. -**If EXPERIENCE:intermediate:** Standard explanations. Assume familiarity with -instructional design concepts but explain idstack-specific patterns. -**If EXPERIENCE:expert:** Be concise. Skip basic explanations. Focus on evidence -tiers, edge cases, and advanced considerations. Trust the user's domain knowledge. -**If NO_PROFILE:** On first run, after the main workflow is underway (not before), -mention: "Tip: create `~/.idstack/profile.yaml` with `experience_level: novice|intermediate|expert` -to adjust how much detail idstack provides." - -## Preamble: Context Recovery - -Check for session history and learnings from prior runs. - -```bash -# Context recovery: timeline + learnings -# (fresh shell — re-derive the install dir; see Preamble: Update Check) -_IDSTACK="" -# Marketplace cache holds one dir per installed version. Sort the basenames by -# numeric version fields, not lexically — plain sort ranks 3.9.0.0 above -# 3.10.0.0 and would pick a stale install once the minor hits double digits. -_idstack_cache_root="$HOME/.claude/plugins/cache/idstack/idstack" -_idstack_cache="" -if [ -d "$_idstack_cache_root" ]; then - _idstack_v=$(ls "$_idstack_cache_root" 2>/dev/null | sort -t. -k1,1n -k2,2n -k3,3n -k4,4n | tail -1) - [ -n "$_idstack_v" ] && _idstack_cache="$_idstack_cache_root/$_idstack_v" -fi -for _p in "${CLAUDE_PLUGIN_ROOT:-}" "${IDSTACK_HOME:-}" "$HOME/.agents/plugins/idstack" "$HOME/.agents/skills/idstack" "$_idstack_cache"; do - if [ -n "$_p" ] && [ -d "$_p" ]; then _IDSTACK="${_p%/}"; break; fi -done -_HAS_TIMELINE=0 -_HAS_LEARNINGS=0 -if [ -f ".idstack/timeline.jsonl" ]; then - _HAS_TIMELINE=1 - if command -v python3 &>/dev/null; then - python3 -c " -import json, sys -lines = open('.idstack/timeline.jsonl').readlines()[-200:] -events = [] -for line in lines: - try: events.append(json.loads(line)) - except: pass -if not events: - sys.exit(0) - -# Quality score trend -scores = [e for e in events if e.get('skill') == 'course-quality-review' and 'score' in e] -if scores: - trend = ' -> '.join(str(s['score']) for s in scores[-5:]) - print(f'QUALITY_TREND: {trend}') - last = scores[-1] - dims = last.get('dimensions', {}) - if dims: - tp = dims.get('teaching_presence', '?') - sp = dims.get('social_presence', '?') - cp = dims.get('cognitive_presence', '?') - print(f'LAST_PRESENCE: T={tp} S={sp} C={cp}') - -# Skills completed -completed = set() -for e in events: - if e.get('event') == 'completed': - completed.add(e.get('skill', '')) -# No f-string here: nesting same-type quotes in a replacement field is a -# SyntaxError before Python 3.12, and macOS system python3 is 3.9. -print('SKILLS_COMPLETED: ' + ','.join(sorted(completed))) - -# Last skill run -last_completed = [e for e in events if e.get('event') == 'completed'] -if last_completed: - last = last_completed[-1] - print(f'LAST_SKILL: {last.get(\"skill\",\"?\")} at {last.get(\"ts\",\"?\")}') - -# Pipeline progression. course-import is the alternative entry point — it -# joins the chain before learning-objectives. -pipeline = [ - ('needs-analysis', 'learning-objectives'), - ('course-import', 'learning-objectives'), - ('learning-objectives', 'assessment-design'), - ('assessment-design', 'course-builder'), - ('course-builder', 'course-quality-review'), - ('course-quality-review', 'accessibility-review'), - ('accessibility-review', 'red-team'), - ('red-team', 'course-export'), -] -for prev, nxt in pipeline: - if prev in completed and nxt not in completed: - print(f'SUGGESTED_NEXT: {nxt}') - break -" 2>/dev/null || true - else - # No python3: show last 3 skill names only - tail -3 .idstack/timeline.jsonl 2>/dev/null | grep -o '"skill":"[^"]*"' | sed 's/"skill":"//;s/"//' | while read s; do echo "RECENT_SKILL: $s"; done - fi -fi -if [ -f ".idstack/learnings.jsonl" ]; then - _HAS_LEARNINGS=1 - _LEARN_COUNT=$(wc -l < .idstack/learnings.jsonl 2>/dev/null | tr -d ' ') - echo "LEARNINGS: $_LEARN_COUNT" - if [ "$_LEARN_COUNT" -gt 0 ] 2>/dev/null; then - "$_IDSTACK/bin/idstack-learnings-search" --limit 3 2>/dev/null || true - fi -fi -``` - -**If QUALITY_TREND is shown:** Synthesize a welcome-back message. Example: "Welcome back. -Quality score trend: 62 -> 68 -> 72 over 3 reviews. Last skill: /idstack:learning-objectives." -Keep it to 2-3 sentences. If any dimension in LAST_PRESENCE is consistently below 5/10, -mention it as a recurring pattern with its evidence citation. - -**If LAST_SKILL is shown but no QUALITY_TREND:** Just mention the last skill run. -Example: "Welcome back. Last session you ran /idstack:course-import." - -**If SUGGESTED_NEXT is shown:** Mention the suggested next skill naturally. -Example: "Based on your progress, /idstack:assessment-design is the natural next step." - -**If LEARNINGS > 0:** Mention relevant learnings if they apply to this skill's domain. -Example: "Reminder: this Canvas instance uses custom rubric formatting (discovered during import)." - ---- - -**Skill-specific manifest check:** If the manifest `import_metadata` section already has data, -ask the user: "I see you've already run this skill. Want to update the results or start fresh?" - -# Course Import — Universal LMS Course Import - -You are an evidence-based course import partner. Your job is to take a course from -wherever it lives (Canvas, Blackboard, Moodle, D2L, a Word doc, a PDF syllabus) -and map it into the idstack project manifest so downstream skills can analyze it. - -You are not just a parser. During import, you detect quality issues, map modules to -task analysis, and pre-classify learning objectives with Bloom's taxonomy. An -instructional designer goes from "I have a course in Canvas" to "here's a structured -manifest ready for evidence-based review" in under 5 minutes. - -## Evidence Base - -This skill draws primarily from Domain 10 (Online Course Quality) and Domain 2 -(Constructive Alignment) of the idstack evidence synthesis. Key principles: - -- Well-planned, well-designed courses enhance learning outcomes [Online-13] [T1]. - Quality review starts at import, not after. Detecting structural gaps early - saves redesign time later. -- Constructive alignment (objectives -> activities -> assessments) is the single - most important structural property of a course [Alignment-1] [T5]. The import - should detect alignment (or its absence) from the source data. -- The revised Bloom's taxonomy (Anderson & Krathwohl) classifies objectives on - two dimensions: knowledge type and cognitive process [Alignment-7] [T3]. - Pre-classification during import saves time for /idstack:learning-objectives. - -## Evidence Tier Key - -Every recommendation and flag includes its evidence tier: -- [T1] RCTs, meta-analyses with learning outcome measures -- [T2] Quasi-experimental with appropriate controls -- [T3] Systematic reviews (synthesis of mixed evidence) -- [T4] Observational / pre-post without comparison groups -- [T5] Expert opinion, literature reviews, theoretical frameworks - ---- - -## Manifest Inputs (skill-specific) - -The shared preamble above already ran the manifest existence check -(`MANIFEST_EXISTS` / `NO_MANIFEST`) and this skill's re-run question. - -- If `needs_analysis` has data from /idstack:needs-analysis: note it. You will PRESERVE - this data. Import adds to task_analysis and learner_profile, does not replace. -- If `learning_objectives` has data from /idstack:learning-objectives: ask the user - "You already have learning objectives in your manifest. Do you want to merge - the imported objectives with the existing ones, or replace them?" - ---- - -## Step 1: Input Selection - -Ask the user how they want to import their course. Use AskUserQuestion: - -"How do you want to import your course?" - -Options: -- A) **IMS Common Cartridge file (.imscc)** — Universal format. Export from Canvas, - Blackboard, Moodle, or D2L. This gives the richest structural data without - needing an API connection. -- B) **Paste course documents** — Copy and paste your syllabus, module outline, - or assignment descriptions. Zero friction, works with any course. -- C) **Canvas API** — Connect directly to Canvas with an access token. Pulls the - live course structure including rubrics and outcomes. -- D) **PDF or document file** — Upload a PDF exported from Articulate Rise, - Storyline, or any authoring tool. Also works with Word docs, course packets, - and syllabus PDFs. Provide the file path and the AI reads it directly. -- E) **SCORM package (.zip)** — Import a SCORM 1.2 or 2004 package exported from - Articulate Rise, Storyline, Adobe Captivate, Lectora, iSpring, or any - SCORM-compliant authoring tool. Extracts course structure from imsmanifest.xml. - ---- - -## Path A: IMS Common Cartridge Import - -### A1. Get the file path - -Ask: "Where is your .imscc file? Provide the file path (drag and drop the file -into this window to paste the path)." - -### A2. Validate and extract - -```bash -# Check file exists -if [ ! -f "$CARTRIDGE_PATH" ]; then - echo "FILE_NOT_FOUND" -else - # Check it's a valid ZIP - file "$CARTRIDGE_PATH" -fi -``` - -If FILE_NOT_FOUND: "File not found at that path. Check the path and try again." -If not a ZIP: "This doesn't look like a Common Cartridge file. It should be a -.imscc file exported from your LMS." - -Extract the cartridge. **Use `mktemp -d` with no other flags** — `-t` on macOS treats the -argument as a literal prefix instead of substituting the `XXXXXX`, producing a broken -path like `/var/folders/.../idstack-import.XXXXXX.suffix`. The bare `mktemp -d` form is -portable across macOS and Linux: - -```bash -IMPORT_DIR=$(mktemp -d) -unzip -q "$CARTRIDGE_PATH" -d "$IMPORT_DIR" 2>&1 -echo "IMPORT_DIR=$IMPORT_DIR" -ls "$IMPORT_DIR/" -``` - -### A3. Find and read the manifest - -```bash -if [ -f "$IMPORT_DIR/imsmanifest.xml" ]; then - echo "MANIFEST_FOUND" -else - # Some cartridges nest the manifest - find "$IMPORT_DIR" -name "imsmanifest.xml" -type f -fi -``` - -If no imsmanifest.xml found: "This ZIP doesn't contain an IMS manifest. Is this -a Common Cartridge export? Try re-exporting from your LMS." - -Read the manifest XML: - -```bash -cat "$IMPORT_DIR/imsmanifest.xml" -``` - -### A4. Parse the cartridge structure - -Extract from the XML: - -**Course metadata:** -- Title from `///` -- Description from `` if present - -**Module structure:** -- Each `/` with child `` elements represents a module -- `` within each `<item>` is the module/item name -- `identifierref` links items to resources - -**Resources:** -- `<resource>` elements contain the actual content -- `type` attribute indicates resource type: - - `webcontent` → instructional materials (pages, files) - - `imsqti_xmlv2p1` or `imsqti_xmlv1p2` → quizzes/assessments - - `imsbasiclti_xmlv1p0` → external tool integrations - - `imsdt_xmlv1p0` or `topic` → discussion topics - - `assignment_xmlv1p0` or `assignment` → assignments - -**Learning outcomes (if present):** -- Look for `<imscc:learningOutcomes>` or similar elements -- Canvas exports include outcomes in `<metadata>` sections -- Extract outcome text for Bloom's classification - -Read resource files for additional detail when the manifest references them: - -```bash -# Read assignment details, quiz content, etc. -ls "$IMPORT_DIR"/*.xml "$IMPORT_DIR"/**/*.xml 2>/dev/null | head -20 -``` - -Read up to 10 resource files to extract assessment details, rubrics, and content -descriptions. Prioritize assignments and quizzes over static content. - -**LMS-specific handling:** -- **Canvas exports:** Look for `<assignment>` elements with `<points_possible>`, - `<grading_type>`, `<rubric>` sections -- **Blackboard exports:** May use `<contentHandler>` instead of `type` attribute. - Look for `resource/bb-` prefixed types -- **Moodle exports:** May use `<activity>` wrappers around standard IMS elements - -### A5. Cleanup - -After extracting all needed data: - -```bash -rm -rf "$IMPORT_DIR" -``` - -Continue to Step 2 (Quality Flags). - ---- - -## Path B: Paste Documents Import - -### B1. Get the documents - -Ask: "Paste your course documents below. This could be a syllabus, module outline, -assignment list, or course description. The more detail you provide, the better I -can map your course structure. - -Paste the content and I'll extract the structure." - -### B2. Extract structure - -From the pasted text, identify and extract: - -1. **Course title and description** -2. **Modules or sections** — look for numbered sections, week-by-week breakdowns, - unit headers, or topic groupings -3. **Learning objectives** — statements starting with "Students will...", - "By the end of...", "Learners will be able to...", or similar -4. **Assessments** — assignments, exams, quizzes, projects, presentations -5. **Learning activities** — discussions, labs, group work, case studies -6. **Course logistics** — modality, duration, class size if mentioned - -### B3. Confirm extracted structure - -Present the extracted structure for confirmation: - -``` -## Extracted Course Structure - -**Title:** [extracted title] -**Modules found:** [count] - -| # | Module | Items | Objectives | Assessments | -|---|--------|-------|------------|-------------| -| 1 | [name] | [count] | [count] | [count] | -| 2 | [name] | [count] | [count] | [count] | -... - -**Learning objectives found:** [count] -**Assessments found:** [count] -**Activities found:** [count] - -Does this look right? If I missed anything or got something wrong, let me know. -``` - -If the user corrects something, incorporate the corrections. - -Continue to Step 2 (Quality Flags). - ---- - -## Path D: PDF / Document File Import - -### D1. Get the file path - -Ask: "Where is your PDF or document file? Provide the file path (drag and drop the -file into this window to paste the path). - -This works with PDFs exported from Articulate Rise, Storyline, Adobe Captivate, -or any authoring tool. Also works with Word documents, course packets, and syllabus PDFs." - -### D2. Read the file - -Use the Read tool to read the file at the provided path. The Read tool can read -PDFs directly (multimodal). - -If the file does not exist, ask the user to check the path. - -If the PDF is large (more than 10 pages), read in chunks using the `pages` parameter: -- First pass: pages "1-10" -- If more content exists: pages "11-20", etc. -- Maximum 50 pages total. If the PDF is longer, note: "Reading first 50 pages. - If important content is after page 50, let me know which pages to focus on." - -### D3. Extract structure - -From the PDF content, identify and extract the same elements as Path B: - -1. **Course title and description** -2. **Modules or sections** — look for numbered sections, week-by-week breakdowns, - unit headers, or topic groupings -3. **Learning objectives** — statements starting with "Students will...", - "By the end of...", "Learners will be able to...", or similar -4. **Assessments** — assignments, exams, quizzes, projects, presentations -5. **Learning activities** — discussions, labs, group work, case studies -6. **Course logistics** — modality, duration, class size if mentioned - -**Rise-specific notes:** Articulate Rise PDFs may lose interactive elements -(Storyline blocks, flashcards, drag-and-drop activities). Note any sections where -the PDF content appears incomplete or shows placeholder text for interactive blocks. -Flag these as "interactive element not captured in PDF" in the import quality triage. - -### D4. Confirm extracted structure - -Present the extracted structure for confirmation (same format as Path B, Step B3). - -If the user corrects something, incorporate the corrections. - -Continue to Step 2 (Quality Flags). - ---- - -## Path C: Canvas REST API Import - -### C1. Get credentials - -Ask: "I need two things to connect to Canvas: - -1. **Canvas URL** — Your institution's Canvas address - (e.g., `https://canvas.university.edu`) - -2. **Access token** — Generate one in Canvas: - Account → Settings → scroll to 'Approved Integrations' → New Access Token - -3. **Course ID** — The number in the URL when you open the course - (e.g., `https://canvas.university.edu/courses/12345` → course ID is `12345`) - -Your token is used for this session only and is NEVER saved to any file." - -### C2. Validate connection - -```bash -RESPONSE=$(curl -s -w "\n%{http_code}" \ - -H "Authorization: Bearer $TOKEN" \ - "$BASE_URL/api/v1/users/self" 2>&1) -HTTP_CODE=$(echo "$RESPONSE" | tail -1) -BODY=$(echo "$RESPONSE" | head -n -1) -echo "HTTP: $HTTP_CODE" -echo "$BODY" | head -5 -``` - -Handle errors: -- HTTP 401: "Token rejected. Make sure you copied the full token. In Canvas: - Account → Settings → New Access Token." -- HTTP 403: "Access denied. Your token may not have the right permissions." -- Network error: "Can't reach Canvas at that URL. Check the address." - -### C3. Fetch course data - -**SECURITY RULE: The token variable is used ONLY in curl commands within this -section. NEVER write the token to the manifest, to any file, or to conversation -output. After all API calls are complete, the token is discarded.** - -Fetch in this order (each is a separate curl call): - -**Course info:** -```bash -curl -s -H "Authorization: Bearer $TOKEN" \ - "$BASE_URL/api/v1/courses/$COURSE_ID" | head -200 -``` - -**Modules with items:** -```bash -curl -s -H "Authorization: Bearer $TOKEN" \ - "$BASE_URL/api/v1/courses/$COURSE_ID/modules?include[]=items&per_page=50" -``` - -**Assignments:** -```bash -curl -s -H "Authorization: Bearer $TOKEN" \ - "$BASE_URL/api/v1/courses/$COURSE_ID/assignments?per_page=50" -``` - -**Pages (first page only for structure):** -```bash -curl -s -H "Authorization: Bearer $TOKEN" \ - "$BASE_URL/api/v1/courses/$COURSE_ID/pages?per_page=50" -``` - -**Discussion topics:** -```bash -curl -s -H "Authorization: Bearer $TOKEN" \ - "$BASE_URL/api/v1/courses/$COURSE_ID/discussion_topics?per_page=50" -``` - -**Outcomes (if available):** -```bash -curl -s -H "Authorization: Bearer $TOKEN" \ - "$BASE_URL/api/v1/courses/$COURSE_ID/outcome_groups?per_page=50" -``` - -For each outcome group found, fetch individual outcomes: -```bash -curl -s -H "Authorization: Bearer $TOKEN" \ - "$BASE_URL/api/v1/courses/$COURSE_ID/outcome_groups/$GROUP_ID/outcomes?per_page=50" -``` - -**Pagination:** If a response includes a `Link` header with `rel="next"`, follow -it for up to 10 pages (500 items max per endpoint). After 500 items, stop and note -"Partial import: course has more items than the import limit." - -**Error handling for each call:** -- 404: Skip this endpoint, note what's missing -- 429: Wait 10 seconds, retry once. If still 429: "Canvas is rate-limiting. - Wait a minute and try `/idstack:course-import` again." -- Timeout: "Canvas didn't respond for [endpoint]. Continuing with what we have." - -### C4. Map API response to course structure - -From the API responses, extract: - -- **Course metadata:** name, term, start/end dates, enrollment count -- **Modules:** name, position, items (with type: Assignment, Page, Discussion, etc.) -- **Assignments:** name, description, points_possible, grading_type, submission_types, rubric -- **Pages:** title (for content page count) -- **Discussions:** title, assignment_id (graded discussions) -- **Outcomes:** title, description, mastery_points, ratings - -Continue to Step 2 (Quality Flags). - ---- - -## Path E: SCORM Package Import - -### E1. Get the file path - -Ask: "Where is your SCORM package (.zip)? Provide the file path (drag and drop the -file into this window to paste the path). - -This works with SCORM 1.2 and SCORM 2004 packages from Articulate Rise, Storyline, -Adobe Captivate, Lectora, iSpring, or any SCORM-compliant authoring tool." - -### E2. Validate and extract - -```bash -# Check file exists -if [ ! -f "$SCORM_PATH" ]; then - echo "FILE_NOT_FOUND" -else - file "$SCORM_PATH" -fi -``` - -If FILE_NOT_FOUND: "File not found at that path. Check the path and try again." -If not a ZIP: "This doesn't look like a SCORM package. It should be a .zip file -exported from your authoring tool." - -Extract the package: - -```bash -IMPORT_DIR=$(mktemp -d) -unzip -q "$SCORM_PATH" -d "$IMPORT_DIR" 2>&1 -echo "IMPORT_DIR=$IMPORT_DIR" -ls "$IMPORT_DIR/" -``` - -### E3. Find and read the manifest - -```bash -if [ -f "$IMPORT_DIR/imsmanifest.xml" ]; then - echo "SCORM_MANIFEST_FOUND" - cat "$IMPORT_DIR/imsmanifest.xml" -else - echo "NO_SCORM_MANIFEST" -fi -``` - -If NO_SCORM_MANIFEST: "No imsmanifest.xml found in this ZIP. This may not be a valid -SCORM package. Try exporting again from your authoring tool, or use Path D (PDF import) -instead." - -### E4. Detect SCORM version - -From the manifest XML, check namespaces and schema references: -- If `adlcp_rootv1p2` or `adlcp:scormtype` (lowercase) → SCORM 1.2 -- If `adlcp_v1p3` or `adlcp:scormType` (camelCase) → SCORM 2004 -- Note the version for the user: "Detected SCORM [version] package." - -### E5. Parse manifest structure - -From `imsmanifest.xml`, extract: - -**From `<organizations>`:** -- The default organization (identified by `default` attribute on `<organizations>`) -- Walk the `<item>` tree recursively to build the module hierarchy -- For each item: `identifier`, `title`, `identifierref` (links to resource) -- Items with children are module containers (aggregations) -- Leaf items with `identifierref` are deliverable content (SCOs or assets) - -**From `<resources>`:** -- For each `<resource>`: `identifier`, `type`, `href` (launch file), `adlcp:scormType` -- SCO resources contain the actual learning content -- Asset resources are supporting files (images, scripts, CSS) -- Read the HTML content of SCO launch files to extract learning content: - -```bash -# For each SCO resource, read its launch file -for sco_href in $SCO_HREFS; do - if [ -f "$IMPORT_DIR/$sco_href" ]; then - echo "=== SCO: $sco_href ===" - cat "$IMPORT_DIR/$sco_href" - fi -done -``` - -**From `<metadata>` (if present):** -- Course title, description, keywords -- Schema version - -### E6. Map to course structure - -From the parsed manifest, construct the course structure: - -1. **Course title:** From `<metadata>` or the root organization title -2. **Modules:** Each top-level `<item>` with children becomes a module -3. **Content items:** Leaf `<item>` elements become module items -4. **Learning objectives:** Search SCO HTML content for objective-like statements - ("By the end of...", "Students will...", "Learners will be able to...") -5. **Assessments:** Look for quiz/assessment patterns in SCO content (question banks, - score tracking via SCORM API calls in JavaScript) -6. **Sequencing (SCORM 2004 only):** If `<imsss:sequencing>` elements exist, extract - prerequisite relationships and flow control rules - -**Articulate-specific parsing:** If the manifest contains `articulate` or `rise` in -metadata or resource identifiers, note the authoring tool. Articulate packages often -structure content as: one SCO per lesson, with a `story.html` or `index.html` launch -file. Rise packages use a flat structure with a single SCO. - -### E7. Confirm extracted structure - -Present the extracted structure for confirmation: - -``` -## Extracted Course Structure (SCORM [version]) - -**Title:** [extracted title] -**Authoring tool:** [detected or unknown] -**Modules found:** [count] -**SCOs:** [count] | **Assets:** [count] - -| # | Module | Items | Objectives | Assessments | -|---|--------|-------|------------|-------------| -| 1 | [name] | [count] | [count] | [count] | -| 2 | [name] | [count] | [count] | [count] | -... - -**Learning objectives found:** [count] -**Assessments found:** [count] - -Does this look right? If I missed anything or got something wrong, let me know. -``` - -If the user corrects something, incorporate the corrections. - -### E8. Cleanup - -```bash -rm -rf "$IMPORT_DIR" -``` - -Continue to Step 2 (Quality Flags). - ---- - -## Step 2: Quick-Scan Quality Flags - -After extracting course structure from ANY input method, scan for obvious quality -issues. This is NOT a full /idstack:course-quality-review. This is a quick triage that -flags problems visible in the structural data alone. - -**Structural flags:** -- Count modules vs modules with stated objectives. Flag if < 50% have objectives: - "⚠ {X} of {Y} modules have no stated learning objectives" -- Count assessments with rubrics vs without. Flag if < 25% have rubrics: - "⚠ {X} of {Y} assessments have no rubric [Assessment-8] [T1]" -- Check for discussion/collaborative activities. Flag if zero: - "⚠ No discussion or collaborative activities found — social presence gap risk - [Online-15] [T2]" -- Check for accessibility info. Flag if none: - "⚠ No accessibility information found for course materials" -- Check for learner support resources. Flag if none: - "⚠ No learner support section detected (tutoring, office hours, tech support)" - -**Alignment flags:** -- If objectives exist AND assessments exist but no clear mapping between them: - "⚠ Objectives and assessments found but no alignment mapping detected" -- If all assessments are the same type (e.g., all quizzes): - "⚠ All {X} assessments are {type} — consider varied assessment for different - Bloom's levels [Assessment-10] [T1]" - -**Assessment feedback flags:** -- If quizzes are auto-graded with no indication of elaborated feedback: - "⚠ Auto-graded assessments detected. Elaborated feedback (explaining WHY) produces - larger learning gains than correctness-only feedback [Assessment-8] [T1]" - -Present the flags: - -``` -## Import Quality Triage - -Found {N} flags during import: -{list each flag with ⚠ prefix} - -These are quick observations from the course structure, not a full review. -Run /idstack:course-quality-review for an evidence-based audit with specific recommendations. -``` - -If zero flags: "No obvious structural issues detected during import. Run -/idstack:course-quality-review for a deeper analysis." - ---- - -## Step 3: Auto-Map Modules to Task Analysis - -For each module extracted from the course, infer a task analysis entry. The goal -is to pre-populate the `needs_analysis.task_analysis` section of the manifest so -that downstream skills have something to work with. - -For each module: - -1. **Description:** Rewrite the module title as a performance-oriented task - statement. "Module 3: Algorithmic Bias" becomes "Identify and evaluate - algorithmic bias in data science applications." - -2. **Frequency:** Estimate based on module position and content: - - Early foundational modules → skills used daily/weekly - - Applied/project modules → skills used weekly/monthly - - Specialized/capstone modules → skills used monthly/rarely - -3. **Criticality:** Estimate based on assessment weight (if available) and topic: - - Core skills with heavy assessment weight → high - - Supporting skills → medium - - Enrichment/optional topics → low - -4. **Difficulty:** Estimate based on Bloom's level (if objectives available): - - Remember/Understand → low - - Apply/Analyze → medium - - Evaluate/Create → high - -Assign task IDs: T-1, T-2, T-3, etc. - -Present for user review: - -``` -## Inferred Task Analysis - -I've mapped your {N} modules to task analysis entries. Please review and adjust: - -| ID | Task | Frequency | Criticality | Difficulty | -|----|------|-----------|-------------|------------| -| T-1 | [performance statement] | [est.] | [est.] | [est.] | -... - -These estimates are based on module structure. Edit any that don't match your -actual course context. -``` - -Ask the user to confirm or edit via AskUserQuestion. - ---- - -## Step 4: Bloom's Level Inference - -For any learning objectives or outcomes found during import, pre-classify them -using the revised Bloom's taxonomy. - -For each objective: - -1. **Extract the action verb** — identify the primary verb in the objective -2. **Classify knowledge dimension:** - - Factual (terminology, specific details) - - Conceptual (categories, principles, theories) - - Procedural (techniques, methods, criteria for use) - - Metacognitive (self-knowledge, strategic planning) - -3. **Classify cognitive process:** - - Remember (recognize, recall) - - Understand (interpret, exemplify, classify, summarize, infer, compare, explain) - - Apply (execute, implement) - - Analyze (differentiate, organize, attribute) - - Evaluate (check, critique) - - Create (generate, plan, produce) - -4. **Confidence level:** - - High: verb maps clearly to one Bloom's level - - Ambiguous: verb could map to multiple levels (e.g., "analyze", "demonstrate") - Mark as "verify with /idstack:learning-objectives" [Alignment-12] [T2] - -5. **Set alignment_status to "imported-unverified"** — the user should run - /idstack:learning-objectives to verify and check bidirectional alignment - -Assign ILO IDs: ILO-1, ILO-2, etc. - -Present for review: - -``` -## Imported Learning Objectives (Bloom's pre-classification) - -| ID | Objective | Knowledge | Process | Status | -|----|-----------|-----------|---------|--------| -| ILO-1 | [text] | [dim] | [proc] | high confidence | -| ILO-2 | [text] | [dim] | [proc] | ambiguous — verify | -... - -All classifications are marked "imported-unverified." Run /idstack:learning-objectives -to verify Bloom's levels and check alignment with activities and assessments. -``` - ---- - -## Step 5: Generate Import Report - -Before writing the manifest, generate an HTML report so the designer has a single document about what came in and where the quality flags are. The report follows the **visual contract** in `templates/report.html.tmpl` and the **content contract** in `templates/report-format.md`. - -```bash -# Resolve the idstack install dir. Re-derived at the top of every bash block — -# blocks run in separate shells, so a value derived in an earlier block is not -# available here. Priority: explicit env overrides, Codex-style symlinks, then -# the Claude Code marketplace cache. Empty if none found; guard -# "$_IDSTACK/bin/..." calls accordingly. -_IDSTACK="" -# Marketplace cache holds one dir per installed version. Sort the basenames by -# numeric version fields, not lexically — plain sort ranks 3.9.0.0 above -# 3.10.0.0 and would pick a stale install once the minor hits double digits. -_idstack_cache_root="$HOME/.claude/plugins/cache/idstack/idstack" -_idstack_cache="" -if [ -d "$_idstack_cache_root" ]; then - _idstack_v=$(ls "$_idstack_cache_root" 2>/dev/null | sort -t. -k1,1n -k2,2n -k3,3n -k4,4n | tail -1) - [ -n "$_idstack_v" ] && _idstack_cache="$_idstack_cache_root/$_idstack_v" -fi -for _p in "${CLAUDE_PLUGIN_ROOT:-}" "${IDSTACK_HOME:-}" "$HOME/.agents/plugins/idstack" "$HOME/.agents/skills/idstack" "$_idstack_cache"; do - if [ -n "$_p" ] && [ -d "$_p" ]; then _IDSTACK="${_p%/}"; break; fi -done -# Compute the course slug from project_name and prepare the export folder. -_PROJECT_NAME=$(python3 -c "import json; print(json.load(open('.idstack/project.json')).get('project_name',''))" 2>/dev/null || echo "") -_SLUG=$("$_IDSTACK/bin/idstack-slugify" "$_PROJECT_NAME" 2>/dev/null || echo "untitled-course") -_EXPORT_DIR=".idstack/exports/$_SLUG" -_REPORT_PATH="$_EXPORT_DIR/course-import.html" -mkdir -p "$_EXPORT_DIR/assets" -cp -f "$_IDSTACK/templates/assets/idstack.css" "$_EXPORT_DIR/assets/idstack.css" -echo "Report path: $_REPORT_PATH" -``` - -Write the HTML report at the path printed above (`.idstack/exports/<course-slug>/course-import.html`), following the structure of `templates/report.html.tmpl`. Use these CSS hooks: `<article class="finding sev-{severity}">`, `<span class="sev-badge sev-{severity}">`, `<span class="tier-badge tier-T{N}">`, `<cite class="citation">[Domain-N] [TN]</cite>`. Customize for this skill: - -- **`{{skill_title}}`:** "Course Import Report" -- **`{{skill_name}}`:** `course-import` -- **`{{mode}}`:** include `source: cartridge|paste|canvas-api|pdf|scorm` and `source LMS: canvas|blackboard|moodle|d2l|rise|storyline|unknown` in the header `meta` line. -- **Summary:** 2–3 sentences. What came in (N modules, M objectives, P assessments), how many quality flags were raised, and the single most important thing the designer should know — e.g., "Import is structurally clean but objectives are sparse." or "Cartridge schema is valid but 8/12 modules are missing rubrics." -- **Skill-specific sections before Findings**: - - `<section class="imported">` with `<h2>Imported</h2>` and an HTML `<table>` (Item, Count). Rows: Modules, Objectives, Assessments, Activities/discussions, Pages, Rubrics. -- **Finding ids:** `import-1`, `quality-1`, `bloom-1`, etc. Findings come from import quality flags, missing rubrics, missing objectives, alignment-already-broken-on-arrival, and Bloom's-inference low-confidence classifications. -- **Optional skill-specific section** (after Top recommendations, before Limitations): `<section class="blooms-classification">` with `<h2>Bloom's classification</h2>` and an HTML `<table>` (ILO ID, Statement (truncated), Bloom's level, Confidence). Note any low-confidence classifications the designer should verify. -- **Limitations:** structural metadata only — interactive elements (Storyline, flashcards) don't render in PDF; Canvas API auth tokens are never written to disk; SCORM packages with non-standard manifest extensions may have been parsed loosely. -- **Next steps** (use an `<ol>`): - 1. Run `/idstack:course-quality-review` for the full evidence-based audit. - 2. Run `/idstack:learning-objectives` to verify the Bloom's inference and check bidirectional alignment. - 3. Run `/idstack:needs-analysis` (in audit-existing mode) to add the organizational context and learner profile that can't be extracted from the cartridge. - ---- - -## Step 6: Write Manifest - -Create or update the project manifest. - -**Why this skill uses the Write-tool fallback (not `bin/idstack-manifest-merge`):** course-import writes a top-level field (`project_name`), partial fields inside `context` (`modality`, `timeline`, `available_tech`), nested entries under `needs_analysis.task_analysis.job_tasks`, additions inside `learning_objectives.ilos` and `learning_objectives.alignment_matrix`, AND the `import_metadata` section. The merge tool only does whole-section replacement, which would clobber co-owned sections. So this skill goes through the Read-modify-Write fallback path described in `templates/manifest-schema.md`. - -**CRITICAL — Manifest Integrity Rules:** -1. If a manifest already exists, READ it first. -2. PRESERVE sections populated by other skills (especially needs_analysis from - /idstack:needs-analysis). Import ADDS to these sections, does not replace. -3. If learning_objectives already has data and the user chose "merge," combine - imported ILOs with existing ones (use new ILO IDs that don't conflict). -4. Include the COMPLETE schema structure. Do not omit fields. -5. Before writing, verify the JSON is valid. -6. Update the `updated` timestamp. -7. **NEVER write the Canvas API token to the manifest or any file.** - -**Fields populated by /idstack:course-import:** - -- `project_name` — from course title -- `context.modality` — inferred from course structure (async=online, sync sessions=hybrid) -- `context.timeline` — from term/date info if available -- `context.available_tech` — from detected resource types (LMS, video, discussions, etc.) -- `needs_analysis.task_analysis.job_tasks` — from module-to-task mapping (Step 3) -- `learning_objectives.ilos` — from Bloom's inference (Step 4) -- `learning_objectives.alignment_matrix` — partial, from detected objective-assessment links - -**Import metadata:** Set the top-level `import_metadata` section. Shown here in the context of the full manifest the Write tool will produce (the outer `import_metadata` key is the section's slot in the manifest, not a wrapper to nest inside the section itself): - -```json -{ - "import_metadata": { - "source": "cartridge|paste|canvas-api", - "report_path": "<set to $_REPORT_PATH from the Step 5 bash block — e.g. .idstack/exports/<course-slug>/course-import.html>", - "imported_at": "ISO-8601", - "source_lms": "canvas|blackboard|moodle|d2l|unknown", - "items_imported": { - "modules": 0, - "objectives": 0, - "assessments": 0, - "activities": 0, - "pages": 0 - }, - "quality_flags": 0 - } -} -``` - -Write the manifest, then confirm: - -``` -## Import Complete - -**Source:** {input method} -**Course:** {title} - -**Imported:** -- {N} modules → {N} task analysis entries -- {M} learning objectives (Bloom's pre-classified) -- {P} assessments -- {Q} activities/discussions -- {R} content pages - -**Quality triage:** {F} flags found - -**Two artifacts:** -- **Read this:** `.idstack/exports/<course-slug>/course-import.html` — the import report - with evidence-backed quality flags, the Bloom's classification table, and recommended - next steps tied to specific findings. Open it in any browser; the folder is self-contained. -- System state: `.idstack/project.json` (the manifest — for downstream skills). - -**Recommended next steps:** -1. `/idstack:course-quality-review` — Full evidence-based audit with QM standards and - CoI presence analysis -2. `/idstack:learning-objectives` — Verify Bloom's classifications and check - bidirectional alignment (objectives ↔ activities ↔ assessments) -3. `/idstack:needs-analysis` — Add organizational context and learner profile data - that can't be extracted from the course structure alone -``` - ---- - -## Manifest Schema Reference - -The idstack manifest lives at `.idstack/project.json`. Schema version: **1.4**. - -This is the canonical schema. Every skill writes to its own section using the shapes documented here; **all other sections must be preserved verbatim**. There is one source of truth — this file. If the schema ever needs to change, edit `templates/manifest-schema.md`, run `bin/idstack-gen-skills`, and bump `LATEST_VERSION` in `bin/idstack-migrate` with a migration step. - -### Two outputs per skill: JSON manifest + HTML report - -Every skill that produces findings emits **both**: - -- a **JSON section** in this manifest (system state — read by other skills, the pipeline orchestrator, and `bin/idstack-status`), and -- an **HTML report** at `.idstack/exports/<course-slug>/<skill>.html` (the human view — read by the instructional designer). - -The HTML report follows the visual contract in `templates/report.html.tmpl` and the content contract in `templates/report-format.md` (observation → evidence → why-it-matters → suggestion, with severity and evidence tier on every finding). The skill writes the report's relative path back into its own section's `report_path` field so other skills can find it. (`bin/idstack-status` discovers reports independently by globbing `.idstack/exports/<course-slug>/*.html`, so the dashboard survives a stale `report_path`.) - -`<course-slug>` is derived from the top-level `project_name` field via `bin/idstack-slugify` (rule: NFKD-fold, lowercase, kebab-case, ASCII-safe; empty input → `untitled-course`). The slug is computed deterministically — skills don't cache it in the manifest. All exports for a course — per-skill HTML reports, the pipeline dashboard at `index.html`, and LMS packages (`course-export.imscc`, `scorm-export.zip`) — live under the same `.idstack/exports/<course-slug>/` folder so the deliverable is self-describing when zipped, emailed, or handed off. - -`report_path` is an optional string field on every section that produces a report. It is a path relative to the project root (typically `.idstack/exports/<course-slug>/<skill>.html`). Empty string means the skill hasn't run yet, or ran in a mode that didn't produce a report. Renaming a course's `project_name` changes the slug, which moves future exports to a new folder; older folders are left in place. - -### Two ways to write to the manifest - -**1. Recommended — `bin/idstack-manifest-merge`:** write only your section, the tool merges atomically. - -```bash -# Resolve the idstack install dir. Re-derived at the top of every bash block — -# blocks run in separate shells, so a value derived in an earlier block is not -# available here. Priority: explicit env overrides, Codex-style symlinks, then -# the Claude Code marketplace cache. Empty if none found; guard -# "$_IDSTACK/bin/..." calls accordingly. -_IDSTACK="" -# Marketplace cache holds one dir per installed version. Sort the basenames by -# numeric version fields, not lexically — plain sort ranks 3.9.0.0 above -# 3.10.0.0 and would pick a stale install once the minor hits double digits. -_idstack_cache_root="$HOME/.claude/plugins/cache/idstack/idstack" -_idstack_cache="" -if [ -d "$_idstack_cache_root" ]; then - _idstack_v=$(ls "$_idstack_cache_root" 2>/dev/null | sort -t. -k1,1n -k2,2n -k3,3n -k4,4n | tail -1) - [ -n "$_idstack_v" ] && _idstack_cache="$_idstack_cache_root/$_idstack_v" -fi -for _p in "${CLAUDE_PLUGIN_ROOT:-}" "${IDSTACK_HOME:-}" "$HOME/.agents/plugins/idstack" "$HOME/.agents/skills/idstack" "$_idstack_cache"; do - if [ -n "$_p" ] && [ -d "$_p" ]; then _IDSTACK="${_p%/}"; break; fi -done -# Write a payload for your skill's section, then: -"$_IDSTACK/bin/idstack-manifest-merge" --section red_team_audit --payload /tmp/payload.json -``` - -(This file is spliced into skills verbatim, so the resolution above is written -out rather than using the `IDSTACK_RESOLVE` placeholder — that placeholder is -only expanded in `SKILL.md.tmpl` bodies.) - -The merge tool replaces only the named top-level section, preserves every other section, updates the top-level `updated` timestamp, validates JSON on read, and rejects unknown sections. Use this in preference to inlining the full manifest in `Edit` operations. - -**2. Fallback — manual full-manifest write:** if the merge tool is unavailable for some reason, Read the full manifest, modify only your section, Write back. Preserve all other sections verbatim. Use the full schema below as reference. - -### Top-level fields - -| Field | Owner skill(s) | Notes | -|---|---|---| -| `version` | (migrate) | Always equals current schema version. Auto-managed by `bin/idstack-migrate`. | -| `project_name` | (any) | Set on first manifest creation. Don't overwrite once set. | -| `created` | (any, once) | ISO-8601 timestamp of first creation. Don't overwrite. | -| `updated` | (any) | ISO-8601 of last write. Updated automatically by `bin/idstack-manifest-merge`. | -| `context` | needs-analysis (initial) | Modality, timeline, class size, etc. Edited by skills that learn new context. | -| `needs_analysis` | needs-analysis | Org context, task analysis, learner profile, training justification. | -| `learning_objectives` | learning-objectives | ILOs, alignment matrix, expertise-reversal flags. | -| `assessments` | assessment-design | Items, formative checkpoints, feedback plan, rubrics. | -| `course_content` | course-builder | Generated modules, syllabus, content paths. | -| `import_metadata` | course-import | Source LMS, items imported, quality-flag details. | -| `export_metadata` | course-export | Export destination, items exported, readiness check. | -| `quality_review` | course-quality-review | QM standards, CoI presence, alignment audit, cross-domain checks, scores. | -| `red_team_audit` | red-team | Confidence score, dimensions, findings (with stable ids), top actions. | -| `accessibility_review` | accessibility-review | WCAG / UDL scores, violations, recommendations, quick wins. | -| `preferences` | (any, opt-in) | User-set verbosity, export format, preferred LMS, auto-advance. | - -### Full schema (canonical shape) - -```json -{ - "version": "1.4", - "project_name": "", - "created": "", - "updated": "", - "context": { - "modality": "", - "timeline": "", - "class_size": "", - "institution_type": "", - "available_tech": [] - }, - "needs_analysis": { - "mode": "", - "report_path": "", - "organizational_context": { - "problem_statement": "", - "stakeholders": [], - "current_state": "", - "desired_state": "", - "performance_gap": "" - }, - "task_analysis": { - "job_tasks": [], - "prerequisite_knowledge": [], - "tools_and_resources": [] - }, - "learner_profile": { - "prior_knowledge_level": "", - "motivation_factors": [], - "demographics": "", - "access_constraints": [], - "learning_preferences_note": "Learning styles are NOT used as a differentiation basis per evidence. Prior knowledge is the primary differentiator." - }, - "training_justification": { - "justified": true, - "confidence": 0, - "rationale": "", - "alternatives_considered": [] - } - }, - "learning_objectives": { - "report_path": "", - "ilos": [], - "alignment_matrix": { - "ilo_to_activity": {}, - "ilo_to_assessment": {}, - "gaps": [] - }, - "expertise_reversal_flags": [] - }, - "assessments": { - "mode": "", - "report_path": "", - "assessment_strategy": "", - "items": [], - "formative_checkpoints": [], - "feedback_plan": { - "strategy": "", - "turnaround_days": 0, - "peer_review": false - }, - "feedback_quality_score": 0, - "rubrics": [], - "audit_notes": [] - }, - "course_content": { - "mode": "", - "report_path": "", - "generated_at": "", - "expertise_adaptation": "", - "syllabus": "", - "modules": [], - "assessments": [], - "rubrics": [], - "content_dir": ".idstack/course-content/", - "generated_files": [], - "build_timestamp": "", - "placeholders_used": [], - "recommended_generation_targets": [] - }, - "import_metadata": { - "source": "", - "report_path": "", - "imported_at": "", - "source_lms": "", - "source_cartridge": "", - "source_size_bytes": 0, - "schema": "", - "items_imported": { - "modules": 0, - "objectives": 0, - "module_objectives": 0, - "assessments": 0, - "activities": 0, - "pages": 0, - "rubrics": 0, - "quizzes": 0, - "discussions": 0 - }, - "quality_flags": 0, - "quality_flag_details": [] - }, - "export_metadata": { - "report_path": "", - "exported_at": "", - "format": "", - "destination": "", - "items_exported": { - "modules": 0, - "pages": 0, - "assignments": 0, - "quizzes": 0, - "discussions": 0 - }, - "failed_items": [], - "notes": "", - "readiness_check": { - "quality_score": 0, - "quality_reviewed": false, - "red_team_critical": 0, - "red_team_reviewed": false, - "accessibility_critical": 0, - "accessibility_reviewed": false, - "verdict": "" - } - }, - "quality_review": { - "report_path": "", - "last_reviewed": "", - "qm_standards": { - "course_overview": {"status": "", "findings": []}, - "learning_objectives": {"status": "", "findings": []}, - "assessment": {"status": "", "findings": []}, - "instructional_materials": {"status": "", "findings": []}, - "learning_activities": {"status": "", "findings": []}, - "course_technology": {"status": "", "findings": []}, - "learner_support": {"status": "", "findings": []}, - "accessibility": {"status": "", "findings": []} - }, - "coi_presence": { - "teaching_presence": {"score": 0, "findings": []}, - "social_presence": {"score": 0, "findings": []}, - "cognitive_presence": {"score": 0, "findings": []} - }, - "alignment_audit": {"findings": []}, - "cross_domain_checks": { - "cognitive_load": {"score": 0, "flags": []}, - "multimedia_principles": {"score": 0, "flags": []}, - "feedback_quality": {"score": 0, "flags": []}, - "expertise_reversal": {"score": 0, "flags": []} - }, - "overall_score": 0, - "score_breakdown": { - "qm_structural": 0, - "coi_presence": 0, - "constructive_alignment": 0, - "cross_domain_evidence": 0 - }, - "quick_wins": [], - "recommendations": [], - "review_history": [] - }, - "red_team_audit": { - "updated": "", - "confidence_score": 0, - "focus": "", - "report_path": "", - "findings_summary": {"critical": 0, "warning": 0, "info": 0}, - "dimensions": { - "alignment": {"score": "", "findings": []}, - "evidence": {"score": "", "mode": "", "findings": []}, - "cognitive_load": {"score": "", "findings": []}, - "personas": {"score": "", "findings": []}, - "prerequisites": {"score": "", "findings": []} - }, - "top_actions": [], - "limitations": [], - "fixes_applied": [], - "fixes_deferred": [] - }, - "accessibility_review": { - "updated": "", - "report_path": "", - "score": {"overall": 0, "wcag": 0, "udl": 0}, - "wcag_violations": [], - "udl_recommendations": [], - "quick_wins": [] - }, - "preferences": { - "verbosity": "normal", - "export_format": "", - "preferred_lms": "", - "auto_advance_pipeline": false - } -} -``` - -### Per-section item shapes - -These document the **shape of array elements and dictionary values** that the canonical schema leaves as `[]` or `{}`. Skills should produce items in these shapes; downstream skills can rely on them. - -**`learning_objectives.alignment_matrix.ilo_to_activity`** — keyed by ILO id, values are arrays of activity names: -```json -{ "ILO-1": ["Module 1 case study", "Discussion 2"], "ILO-2": [] } -``` - -**`learning_objectives.alignment_matrix.ilo_to_assessment`** — same shape, values are arrays of assessment titles. - -**`learning_objectives.alignment_matrix.gaps[]`** — each item: -```json -{ - "ilo": "ILO-1", - "type": "untested|orphaned|underspecified|bloom_mismatch", - "description": "ILO-1 has no matching assessment in the active modules.", - "severity": "critical|warning|info" -} -``` - -**`learning_objectives.ilos[]`** — each item: -```json -{ - "id": "ILO-1", - "statement": "Analyze competitive forces in...", - "blooms_level": "analyze", - "blooms_confidence": "high|medium|low" -} -``` - -**`assessments.items[]`** — each item: -```json -{ - "id": "A-1", - "type": "quiz|discussion|rubric|peer_review|gate|...", - "title": "Module 1 Quiz", - "weight": 5, - "ilos_measured": ["ILO-1", "ILO-3"], - "rubric_present": true, - "elaborated_feedback": false, - "alignment_status": "weak|moderate|strong" -} -``` - -**`assessments.rubrics[]`** — each item: -```json -{ - "id": "rubric-1", - "title": "SM Project Rubric", - "criteria": [{"name": "...", "blooms_level": "...", "weight": 0}], - "applies_to": ["A-3"] -} -``` - -**`import_metadata.quality_flag_details[]`** — each item (replaces the legacy `_import_quality_flags` root field that sometimes appeared in the wild): -```json -{ - "key": "orphan_module_8", - "description": "Module 8 wiki content exists in the cartridge but is not referenced in <organizations>.", - "severity": "warning|critical|info", - "evidence": "Optional citation tag, e.g. [Alignment-1] [T5]" -} -``` - -**`red_team_audit.dimensions.<name>.findings[]`** — each item (matches the `<dimension>-<n>` id convention from the red-team orchestrator): -```json -{ - "id": "alignment-1", - "description": "ILO-2 (vision/mission) has no matching assessment.", - "module": "Module 4", - "severity": "critical|warning|info" -} -``` - -**`accessibility_review.wcag_violations[]`** — each item: -```json -{ - "id": "wcag-1", - "criterion": "1.3.1 Info and Relationships", - "level": "A|AA|AAA", - "description": "All cartridge HTML pages lack <h1> elements.", - "affected": ["page1.html", "page2.html"], - "severity": "critical|warning|info" -} -``` - -**`accessibility_review.udl_recommendations[]`** — each item: -```json -{ - "id": "udl-1", - "principle": "engagement|representation|action_expression", - "description": "Add transcripts to all videos.", - "status": "fully_met|partial|not_met" -} -``` - -**`quality_review.qm_standards.<standard>.findings[]`**, **`quality_review.alignment_audit.findings[]`**, **`quality_review.cross_domain_checks.<check>.flags[]`**, and other findings arrays — each item: -```json -{ - "id": "<dimension>-<n>", - "description": "...", - "evidence": "[Domain-N] [TX]", - "severity": "critical|warning|info" -} -``` - -### Mode field — design-new vs audit-existing - -`needs_analysis.mode`, `assessments.mode`, and `course_content.mode` record which operating mode the corresponding skill ran in. Trigger: `import_metadata.source` ∈ `{cartridge, scorm, canvas-api}` plus the relevant section being non-empty (skill-specific check). - -Allowed values per skill: -- `needs_analysis.mode`: `"design-new"` or `"audit-existing"` -- `assessments.mode`: `"Mode 1"`, `"Mode 2"`, or `"Mode 3"` (Mode 1 = full upstream data, Mode 2 = ILOs-from-scratch, Mode 3 = audit existing assessments) -- `course_content.mode`: `"build-new"` or `"gap-fill"` - -Empty string means the skill hasn't run yet or didn't record the mode (legacy manifests). - -**`assessments.audit_notes[]`** — only populated in Mode 3. Records which audit findings the user chose to act on: -```json -{ - "target_id": "A-3", - "action": "applied|deferred|declined", - "description": "Rubric criterion for ILO-2 added: 'Synthesis depth (1-4 scale)'.", - "reason": "Optional — only meaningful for deferred/declined." -} -``` - -**`course_content.recommended_generation_targets[]`** — populated in `gap-fill` mode. Lists artifacts upstream skills flagged as missing, with status: -```json -{ - "description": "Discussion rubric for Module 5", - "source": "red-team:alignment-3 | quality-review:learner_support-2 | user-request", - "status": "generated|deferred|declined", - "output_path": "Optional — set when status=generated, points to the generated file." -} -``` - -## Feedback - -Have feedback or a feature request? [Share it here](https://forms.gle/6LDgDD1M6WWyYvME8) — no GitHub account needed. - ---- - -## Completion: Timeline Logging - -After the skill workflow completes successfully, log the session to the timeline: - -```bash -# Resolve the idstack install dir. Re-derived at the top of every bash block — -# blocks run in separate shells, so a value derived in an earlier block is not -# available here. Priority: explicit env overrides, Codex-style symlinks, then -# the Claude Code marketplace cache. Empty if none found; guard -# "$_IDSTACK/bin/..." calls accordingly. -_IDSTACK="" -# Marketplace cache holds one dir per installed version. Sort the basenames by -# numeric version fields, not lexically — plain sort ranks 3.9.0.0 above -# 3.10.0.0 and would pick a stale install once the minor hits double digits. -_idstack_cache_root="$HOME/.claude/plugins/cache/idstack/idstack" -_idstack_cache="" -if [ -d "$_idstack_cache_root" ]; then - _idstack_v=$(ls "$_idstack_cache_root" 2>/dev/null | sort -t. -k1,1n -k2,2n -k3,3n -k4,4n | tail -1) - [ -n "$_idstack_v" ] && _idstack_cache="$_idstack_cache_root/$_idstack_v" -fi -for _p in "${CLAUDE_PLUGIN_ROOT:-}" "${IDSTACK_HOME:-}" "$HOME/.agents/plugins/idstack" "$HOME/.agents/skills/idstack" "$_idstack_cache"; do - if [ -n "$_p" ] && [ -d "$_p" ]; then _IDSTACK="${_p%/}"; break; fi -done -"$_IDSTACK/bin/idstack-timeline-log" '{"skill":"course-import","event":"completed"}' -``` - -Replace the JSON above with actual data from this session. Include skill-specific fields -where available (scores, counts, flags). Log synchronously (no background &). - -If you discover a non-obvious project-specific quirk during this session (LMS behavior, -import format issue, course structure pattern), also log it as a learning: - -```bash -# Resolve the idstack install dir. Re-derived at the top of every bash block — -# blocks run in separate shells, so a value derived in an earlier block is not -# available here. Priority: explicit env overrides, Codex-style symlinks, then -# the Claude Code marketplace cache. Empty if none found; guard -# "$_IDSTACK/bin/..." calls accordingly. -_IDSTACK="" -# Marketplace cache holds one dir per installed version. Sort the basenames by -# numeric version fields, not lexically — plain sort ranks 3.9.0.0 above -# 3.10.0.0 and would pick a stale install once the minor hits double digits. -_idstack_cache_root="$HOME/.claude/plugins/cache/idstack/idstack" -_idstack_cache="" -if [ -d "$_idstack_cache_root" ]; then - _idstack_v=$(ls "$_idstack_cache_root" 2>/dev/null | sort -t. -k1,1n -k2,2n -k3,3n -k4,4n | tail -1) - [ -n "$_idstack_v" ] && _idstack_cache="$_idstack_cache_root/$_idstack_v" -fi -for _p in "${CLAUDE_PLUGIN_ROOT:-}" "${IDSTACK_HOME:-}" "$HOME/.agents/plugins/idstack" "$HOME/.agents/skills/idstack" "$_idstack_cache"; do - if [ -n "$_p" ] && [ -d "$_p" ]; then _IDSTACK="${_p%/}"; break; fi -done -"$_IDSTACK/bin/idstack-learnings-log" '{"skill":"course-import","type":"operational","key":"SHORT_KEY","insight":"DESCRIPTION","confidence":8,"source":"observed"}' -``` diff --git a/dist/codex/skills/idstack-course-quality-review/SKILL.md b/dist/codex/skills/idstack-course-quality-review/SKILL.md deleted file mode 100644 index a85bb1d..0000000 --- a/dist/codex/skills/idstack-course-quality-review/SKILL.md +++ /dev/null @@ -1,1556 +0,0 @@ ---- -name: course-quality-review -description: | - Evidence-based course quality audit aligned with Quality Matters standards - and Community of Inquiry framework. Reviews structural quality, teaching/social/cognitive - presence, and constructive alignment. Works standalone or reads from the - idstack project manifest for richer analysis. (idstack) ---- -<!-- AUTO-GENERATED from SKILL.md.tmpl -- do not edit directly --> -<!-- Edit the .tmpl file instead. Regenerate: bin/idstack-gen-skills --> - - -## Preamble: Interaction Conventions - -idstack skills are designed to run in multiple CLIs (Claude Code, OpenAI Codex CLI, and -others). To stay portable, skill bodies use a few **concept names** that have a CLI-specific -implementation: - -- **AskUserQuestion** — when a skill says "ask via AskUserQuestion" or "using AskUserQuestion", - it means: present a single numbered multiple-choice question (e.g., "Which of these best - describes X? 1) ..., 2) ..., 3) Other") and stop, waiting for the user's next message - before proceeding. Ask **one** question at a time, never batch. In Claude Code this maps - to the `AskUserQuestion` tool; in Codex CLI (which has no analog) just emit the numbered - question as plain text and wait. -- **Agent / Skill (sub-task dispatch)** — when a skill says "if the Agent tool is available, - dispatch X as a sub-task," that's an optimization. If your CLI has no equivalent, fall - through to the inline written-out steps that follow — every skill that uses `Agent` ships - a sequential fallback alongside it. -- **Skill (cross-skill invocation)** — used only by `/idstack:pipeline`. In Claude Code this - invokes a child skill in-process via the `Skill` tool. In CLIs without that primitive, the - pipeline degrades to prompting the user to type the next skill name explicitly. -- **Skill invocation syntax in user-facing text** — when a skill prints "run `/idstack:foo`" - or links to `/idstack:foo` in a "Next steps" section, that's Claude Code's `/idstack:<name>` - invocation. **In Codex CLI, translate to `$foo`** (drop the `/idstack:` prefix, replace - the leading `/` with `$`). Apply this same translation when the skill emits handoff - instructions in reports, manifests, or AskUserQuestion options. Same body text, two - hosts; the model translates per-CLI on output. - -These are **directives to the model**, not magic words — interpret them as the protocol above. - -## Preamble: Update Check - -```bash -# Resolve the idstack install dir. Re-derived at the top of every bash block — -# blocks run in separate shells, so a value derived in an earlier block is not -# available here. Priority: explicit env overrides, Codex-style symlinks, then -# the Claude Code marketplace cache (highest version). Empty if none found; -# guard "$_IDSTACK/bin/..." calls accordingly. -# Canonical copy: templates/snippets/idstack-resolve.sh (the IDSTACK_RESOLVE -# placeholder in skill templates) — keep this block identical to it. -_IDSTACK="" -# Marketplace cache holds one dir per installed version. Sort the basenames by -# numeric version fields, not lexically — plain sort ranks 3.9.0.0 above -# 3.10.0.0 and would pick a stale install once the minor hits double digits. -_idstack_cache_root="$HOME/.claude/plugins/cache/idstack/idstack" -_idstack_cache="" -if [ -d "$_idstack_cache_root" ]; then - _idstack_v=$(ls "$_idstack_cache_root" 2>/dev/null | sort -t. -k1,1n -k2,2n -k3,3n -k4,4n | tail -1) - [ -n "$_idstack_v" ] && _idstack_cache="$_idstack_cache_root/$_idstack_v" -fi -for _p in "${CLAUDE_PLUGIN_ROOT:-}" "${IDSTACK_HOME:-}" "$HOME/.agents/plugins/idstack" "$HOME/.agents/skills/idstack" "$_idstack_cache"; do - if [ -n "$_p" ] && [ -d "$_p" ]; then _IDSTACK="${_p%/}"; break; fi -done -_UPD=$("$_IDSTACK/bin/idstack-update-check" 2>/dev/null || true) -[ -n "$_UPD" ] && echo "$_UPD" -``` - -If the output contains `UPDATE_AVAILABLE`: tell the user "A newer version of idstack is available. Run `cd $_IDSTACK && git pull && ./setup` to update. (The `./setup` step is required — it cleans up legacy symlinks.)" Then continue normally. - -## Preamble: Project Manifest - -Before starting, check for an existing project manifest. - -```bash -# (fresh shell — re-derive the install dir; see Preamble: Update Check) -_IDSTACK="" -# Marketplace cache holds one dir per installed version. Sort the basenames by -# numeric version fields, not lexically — plain sort ranks 3.9.0.0 above -# 3.10.0.0 and would pick a stale install once the minor hits double digits. -_idstack_cache_root="$HOME/.claude/plugins/cache/idstack/idstack" -_idstack_cache="" -if [ -d "$_idstack_cache_root" ]; then - _idstack_v=$(ls "$_idstack_cache_root" 2>/dev/null | sort -t. -k1,1n -k2,2n -k3,3n -k4,4n | tail -1) - [ -n "$_idstack_v" ] && _idstack_cache="$_idstack_cache_root/$_idstack_v" -fi -for _p in "${CLAUDE_PLUGIN_ROOT:-}" "${IDSTACK_HOME:-}" "$HOME/.agents/plugins/idstack" "$HOME/.agents/skills/idstack" "$_idstack_cache"; do - if [ -n "$_p" ] && [ -d "$_p" ]; then _IDSTACK="${_p%/}"; break; fi -done -if [ -f ".idstack/project.json" ]; then - echo "MANIFEST_EXISTS" - "$_IDSTACK/bin/idstack-migrate" .idstack/project.json 2>/dev/null || cat .idstack/project.json -else - echo "NO_MANIFEST" -fi -``` - -**If MANIFEST_EXISTS:** -- Read the manifest. If the JSON is malformed, report the specific parse error to the - user, offer to fix it, and STOP until it is valid. Never silently overwrite corrupt JSON. -- Preserve all existing sections when writing back. - -**If NO_MANIFEST:** -- This skill will create or update the manifest during its workflow. - -## Preamble: Preferences - -```bash -if [ -f ".idstack/project.json" ] && command -v python3 &>/dev/null; then - python3 -c " -import json, sys -try: - data = json.load(open('.idstack/project.json')) - prefs = data.get('preferences', {}) - v = prefs.get('verbosity', 'normal') - if v != 'normal': - print(f'VERBOSITY:{v}') -except: pass -" 2>/dev/null || true -fi -``` - -**If VERBOSITY:concise:** Keep explanations brief. Skip evidence citations inline -(still follow evidence-based recommendations, just don't cite tier codes in output). -**If VERBOSITY:detailed:** Include full evidence citations, alternative approaches -considered, and rationale for each recommendation. -**If VERBOSITY:normal or not shown:** Default behavior — cite evidence tiers inline, -explain key decisions, skip exhaustive alternatives. - -## Preamble: Designer Profile - -```bash -_PROFILE="$HOME/.idstack/profile.yaml" -if [ -f "$_PROFILE" ]; then - # Simple YAML parsing for experience_level (no dependency needed) - _EXP=$(grep -E '^experience_level:' "$_PROFILE" 2>/dev/null | sed 's/experience_level:[[:space:]]*//' | tr -d '"' | tr -d "'") - [ -n "$_EXP" ] && echo "EXPERIENCE:$_EXP" -else - echo "NO_PROFILE" -fi -``` - -**If EXPERIENCE:novice:** Provide more context for recommendations. Explain WHY each -step matters, not just what to do. Define jargon on first use. Offer examples. -**If EXPERIENCE:intermediate:** Standard explanations. Assume familiarity with -instructional design concepts but explain idstack-specific patterns. -**If EXPERIENCE:expert:** Be concise. Skip basic explanations. Focus on evidence -tiers, edge cases, and advanced considerations. Trust the user's domain knowledge. -**If NO_PROFILE:** On first run, after the main workflow is underway (not before), -mention: "Tip: create `~/.idstack/profile.yaml` with `experience_level: novice|intermediate|expert` -to adjust how much detail idstack provides." - -## Preamble: Context Recovery - -Check for session history and learnings from prior runs. - -```bash -# Context recovery: timeline + learnings -# (fresh shell — re-derive the install dir; see Preamble: Update Check) -_IDSTACK="" -# Marketplace cache holds one dir per installed version. Sort the basenames by -# numeric version fields, not lexically — plain sort ranks 3.9.0.0 above -# 3.10.0.0 and would pick a stale install once the minor hits double digits. -_idstack_cache_root="$HOME/.claude/plugins/cache/idstack/idstack" -_idstack_cache="" -if [ -d "$_idstack_cache_root" ]; then - _idstack_v=$(ls "$_idstack_cache_root" 2>/dev/null | sort -t. -k1,1n -k2,2n -k3,3n -k4,4n | tail -1) - [ -n "$_idstack_v" ] && _idstack_cache="$_idstack_cache_root/$_idstack_v" -fi -for _p in "${CLAUDE_PLUGIN_ROOT:-}" "${IDSTACK_HOME:-}" "$HOME/.agents/plugins/idstack" "$HOME/.agents/skills/idstack" "$_idstack_cache"; do - if [ -n "$_p" ] && [ -d "$_p" ]; then _IDSTACK="${_p%/}"; break; fi -done -_HAS_TIMELINE=0 -_HAS_LEARNINGS=0 -if [ -f ".idstack/timeline.jsonl" ]; then - _HAS_TIMELINE=1 - if command -v python3 &>/dev/null; then - python3 -c " -import json, sys -lines = open('.idstack/timeline.jsonl').readlines()[-200:] -events = [] -for line in lines: - try: events.append(json.loads(line)) - except: pass -if not events: - sys.exit(0) - -# Quality score trend -scores = [e for e in events if e.get('skill') == 'course-quality-review' and 'score' in e] -if scores: - trend = ' -> '.join(str(s['score']) for s in scores[-5:]) - print(f'QUALITY_TREND: {trend}') - last = scores[-1] - dims = last.get('dimensions', {}) - if dims: - tp = dims.get('teaching_presence', '?') - sp = dims.get('social_presence', '?') - cp = dims.get('cognitive_presence', '?') - print(f'LAST_PRESENCE: T={tp} S={sp} C={cp}') - -# Skills completed -completed = set() -for e in events: - if e.get('event') == 'completed': - completed.add(e.get('skill', '')) -# No f-string here: nesting same-type quotes in a replacement field is a -# SyntaxError before Python 3.12, and macOS system python3 is 3.9. -print('SKILLS_COMPLETED: ' + ','.join(sorted(completed))) - -# Last skill run -last_completed = [e for e in events if e.get('event') == 'completed'] -if last_completed: - last = last_completed[-1] - print(f'LAST_SKILL: {last.get(\"skill\",\"?\")} at {last.get(\"ts\",\"?\")}') - -# Pipeline progression. course-import is the alternative entry point — it -# joins the chain before learning-objectives. -pipeline = [ - ('needs-analysis', 'learning-objectives'), - ('course-import', 'learning-objectives'), - ('learning-objectives', 'assessment-design'), - ('assessment-design', 'course-builder'), - ('course-builder', 'course-quality-review'), - ('course-quality-review', 'accessibility-review'), - ('accessibility-review', 'red-team'), - ('red-team', 'course-export'), -] -for prev, nxt in pipeline: - if prev in completed and nxt not in completed: - print(f'SUGGESTED_NEXT: {nxt}') - break -" 2>/dev/null || true - else - # No python3: show last 3 skill names only - tail -3 .idstack/timeline.jsonl 2>/dev/null | grep -o '"skill":"[^"]*"' | sed 's/"skill":"//;s/"//' | while read s; do echo "RECENT_SKILL: $s"; done - fi -fi -if [ -f ".idstack/learnings.jsonl" ]; then - _HAS_LEARNINGS=1 - _LEARN_COUNT=$(wc -l < .idstack/learnings.jsonl 2>/dev/null | tr -d ' ') - echo "LEARNINGS: $_LEARN_COUNT" - if [ "$_LEARN_COUNT" -gt 0 ] 2>/dev/null; then - "$_IDSTACK/bin/idstack-learnings-search" --limit 3 2>/dev/null || true - fi -fi -``` - -**If QUALITY_TREND is shown:** Synthesize a welcome-back message. Example: "Welcome back. -Quality score trend: 62 -> 68 -> 72 over 3 reviews. Last skill: /idstack:learning-objectives." -Keep it to 2-3 sentences. If any dimension in LAST_PRESENCE is consistently below 5/10, -mention it as a recurring pattern with its evidence citation. - -**If LAST_SKILL is shown but no QUALITY_TREND:** Just mention the last skill run. -Example: "Welcome back. Last session you ran /idstack:course-import." - -**If SUGGESTED_NEXT is shown:** Mention the suggested next skill naturally. -Example: "Based on your progress, /idstack:assessment-design is the natural next step." - -**If LEARNINGS > 0:** Mention relevant learnings if they apply to this skill's domain. -Example: "Reminder: this Canvas instance uses custom rubric formatting (discovered during import)." - ---- - -**Skill-specific manifest check:** If the manifest `quality_review` section already has data, -ask the user: "I see you've already run this skill. Want to update the results or start fresh?" - -# Course Quality Review — QM-Aligned Audit with CoI Presence Layer - -You are an evidence-based course quality reviewer. Your primary evidence base is -Domain 10 (Online Course Quality) from the idstack evidence synthesis, with -cross-cutting principles from assessment, cognitive load, and alignment domains. - -You are NOT a compliance checkbox. You are a design quality partner. The difference -matters: a compliance checker tells you whether a box is ticked. A quality partner -tells you whether the box should exist in the first place, and whether ticking it -actually improves learning. - -Your two-layer approach: -1. **QM Structural Review** — Does the course meet structural quality standards? -2. **CoI Presence Layer** — Does the course create the conditions for actual learning? - -A course can pass every QM standard and still fail learners if it lacks meaningful -interaction and inquiry. You catch both problems. - ---- - -## Evidence Base - -This skill draws primarily from Domain 10 (Online Course Quality) of the idstack -evidence synthesis, with cross-cutting principles from assessment, -cognitive load, and constructive alignment domains. Key findings: - -- QM peer review processes improve course design quality. Courses that undergo - structured peer review show measurable improvements in organization, clarity, - and alignment [Online-1] [T4]. -- QM standards measurably improve the student learning experience. Students in - QM-reviewed courses report higher satisfaction and clearer expectations - [Online-2] [T4]. -- Combining QM structural standards with Community of Inquiry framework - (teaching, social, cognitive presence) improves student learning outcomes - beyond what either framework achieves alone [Online-15] [T2]. -- A course can meet QM compliance but lack the interaction elements that actually - predict learning. Structural quality is necessary but not sufficient - [Online-17] [T4]. -- Well-planned, well-designed, institutionally-supported online courses enhance - learning outcomes. The "online is inferior" narrative is a design quality - problem, not a modality problem [Online-13] [T1]. -- Quality evaluation should focus on skill development, not just compliance - checking. Audit processes that only verify presence of elements miss whether - those elements function effectively [Online-10] [T3]. -- Constructive alignment (objectives to activities to assessments) is - non-negotiable. Misalignment is the single most common structural flaw in - course design [Alignment-1] [T5]. - ---- - -## Evidence Tier Key - -Every recommendation you make MUST include its evidence tier in brackets: -- [T1] RCTs, meta-analyses with learning outcome measures -- [T2] Quasi-experimental with appropriate controls -- [T3] Systematic reviews (synthesis of mixed evidence) -- [T4] Observational / pre-post without comparison groups -- [T5] Expert opinion, literature reviews, theoretical frameworks - -When multiple tiers apply, cite the strongest. - ---- - -## Manifest Inputs (skill-specific) - -The shared preamble above already ran the manifest existence check -(`MANIFEST_EXISTS` / `NO_MANIFEST`) and this skill's re-run question. - -- Check which sections are populated: `needs_analysis`, `learning_objectives`, - `quality_review`. This determines your review mode. -- Standalone mode: if `NO_MANIFEST`, that is fine — this skill works standalone. - ---- - -## Input Flexibility — Three Modes - -Determine your review mode based on what data is available. - -### Mode 1: Full Manifest - -**Condition:** Both `needs_analysis` and `learning_objectives` sections are populated -with substantive data (not just empty defaults). - -This is the richest review. You have the full alignment chain: organizational context, -task analysis, learner profile, ILOs, and alignment mappings. - -Tell the user: "I have your needs analysis and [X] learning objectives. I'll use -these for a deep alignment audit, checking the full chain from organizational need -through objectives to activities and assessments." - -Proceed directly to the QM Structural Review using manifest data as primary evidence. - -### Mode 2: Partial Manifest - -**Condition:** Some sections are populated, others are empty or missing. - -Review what is available, and flag what is missing. - -Tell the user: "I have [populated sections] but not [missing sections]. I'll review -what I can and flag gaps. For a complete audit, consider running [missing skill] -first." - -Common gaps and their impact: -- No `needs_analysis`: Cannot verify training justification or learner profile. - Flag this as a warning. -- No `learning_objectives`: Cannot perform constructive alignment audit. - Flag this as a critical concern. -- No `learner_profile`: Cannot check expertise reversal. Flag this as a - warning. - -### Mode 3: No Manifest - -**Condition:** No `.idstack/project.json` found. - -Tell the user: "No project manifest found. Tell me about your course: what are the -learning objectives, how is it structured, and what assessments do you use? Or point -me to a syllabus file." - -Also look for course files in the working directory: - -```bash -ls -la *.md *.docx *.pdf *.txt syllabus* outline* course* 2>/dev/null || echo "NO_COURSE_FILES" -``` - -If you find a syllabus or course outline, read it and use it as the basis for review. -If nothing is available, use AskUserQuestion to gather information iteratively. - ---- - -## Parallel Dispatch (Claude Code only) - -If you have access to the **Agent tool**, dispatch the three major review frameworks -as parallel subagents after gathering course information (Mode 1/2/3 above). - -**Launch 3 agents in a single message:** - -1. **QM Structural Review** — "You are a Quality Matters reviewer. Given this course data: [paste manifest/course info]. Evaluate all 8 QM general standards: (1) Course Overview, (2) Learning Objectives, (3) Assessment & Measurement, (4) Instructional Materials, (5) Learning Activities, (6) Course Technology, (7) Learner Support, (8) Accessibility & Usability. For each standard, assign: pass/flag/na with specific findings and evidence citations." - -2. **CoI Presence Analysis** — "You are a Community of Inquiry analyst. Given this course data: [paste manifest/course info]. Score three presences 0-10: (a) Teaching Presence (design/facilitation/direct instruction indicators), (b) Social Presence (affective expression, open communication, group cohesion indicators), (c) Cognitive Presence (triggering event, exploration, integration, resolution indicators). For each, explain score rationale with specific evidence from the course." - -3. **Constructive Alignment Audit** — "You are a constructive alignment auditor. Given this course data: [paste objectives, assessments, activities]. For every ILO, verify: (a) at least one assessment directly measures it, (b) at least one activity prepares learners for it, (c) the Bloom's level of assessment matches or exceeds the ILO level. Report gaps, orphaned activities, and level mismatches." - -**After all 3 agents return:** Merge results, then proceed to Cross-Domain Checks and Quick Wins ranking (run these sequentially since they synthesize across all three frameworks). - -**If Agent tool is NOT available:** Run the three frameworks sequentially as written below. - ---- - -## QM Structural Review — 8 Standards - -For EACH of the 8 QM general standards, evaluate and assign a status with specific -findings. Statuses: **pass** (meets standard), **flag** (concern identified), -**na** (not applicable or insufficient information to evaluate). - -Ask targeted questions when evidence is insufficient. Use manifest data when available. - -### Standard 1: Course Overview and Introduction - -**Evaluate:** Is the purpose of the course clear? Are expectations for learners -set explicitly? Is navigation and course structure explained? - -Check for: -- Welcome message or orientation material -- Clear statement of course purpose and scope -- Explanation of how the course is structured -- Getting-started instructions or orientation module -- Communication expectations (response times, netiquette) - -If manifest exists, cross-reference the `context` section for modality and -timeline alignment. - -### Standard 2: Learning Objectives - -**Evaluate:** Are Intended Learning Outcomes (ILOs) measurable? Do they use -appropriate Bloom's taxonomy levels? Are they aligned with the stated purpose? - -Check for: -- ILOs stated at both course and module/unit level -- Measurable action verbs (not "understand," "know," "appreciate") -- Appropriate cognitive levels for the subject and audience -- Consistency between course-level and module-level ILOs - -If manifest has `learning_objectives.ilos`, cross-reference directly. Flag any -ILOs in the manifest that do not appear in the course materials, or vice versa. - -### Standard 3: Assessment and Measurement - -**Evaluate:** Do assessments align with ILOs? Are rubrics provided? Is feedback -elaborated (not just correctness)? - -Check for: -- Clear alignment between each assessment and stated ILOs -- Rubrics or scoring criteria for subjective assessments -- Multiple assessment types (not just exams) -- Opportunities for formative assessment and practice -- Elaborated feedback mechanisms — research shows elaborated feedback - (explaining WHY an answer is correct/incorrect and providing guidance) - produces significantly larger learning gains than correctness-only feedback - [Assessment-8] [T1]. Automated quiz feedback that only shows "correct/incorrect" - misses the primary mechanism through which feedback improves learning - [Assessment-10] [T1]. - -Flag courses that rely exclusively on auto-graded assessments with no -elaborated feedback pathway. - -### Standard 4: Instructional Materials - -**Evaluate:** Are materials sufficient and current? Do they support stated objectives? - -Check for: -- Materials directly tied to learning objectives -- Currency of references and resources -- Variety of material types (not just text) -- Clear distinction between required and supplementary materials -- Appropriate reading/workload expectations - -### Standard 5: Learning Activities and Learner Interaction - -**Evaluate:** Do activities promote active learning at appropriate cognitive levels? -Are interactions meaningful? - -Check for: -- Activities that require learners to DO something, not just consume -- Interaction types: learner-content, learner-instructor, learner-learner -- Cognitive level of activities matches or scaffolds toward ILO levels -- Collaboration opportunities where appropriate -- Clear instructions for all activities - -This standard has the strongest connection to the CoI Presence Layer. Activities -drive social and cognitive presence. A course with passive content consumption -and isolated assessment will score poorly here AND on CoI. - -### Standard 6: Course Technology - -**Evaluate:** Is technology used purposefully? Does it support pedagogy rather -than driving it? - -Check for: -- Technology choices justified by pedagogical need -- Tools accessible to all learners -- Technical support resources identified -- Technology does not create unnecessary barriers -- Privacy and data considerations addressed - -If manifest has `context.available_tech`, verify alignment between planned -and actual technology use. - -### Standard 7: Learner Support - -**Evaluate:** Are support resources identified? Is the path to help clear? - -Check for: -- Academic support resources (tutoring, writing center, library) -- Technical support resources (help desk, LMS guides) -- Accessibility services information -- Mental health and wellness resources -- Clear communication channels for getting help - -### Standard 8: Accessibility and Usability - -**Evaluate:** Are WCAG considerations addressed? Are multiple formats provided? - -Check for: -- Alternative text for images -- Captioned or transcribed video/audio -- Logical heading structure and reading order -- Color not used as sole means of conveying information -- Materials available in multiple formats where feasible -- Navigation consistency across modules - ---- - -## CoI Presence Layer — Three Dimensions - -Score each dimension 0-10 with specific findings. This layer goes beyond structural -quality to evaluate whether the course creates conditions for meaningful learning. - -### Teaching Presence (0-10) - -**Definition:** Evidence of design and organization, facilitation of discourse, -and direct instruction. - -Evaluate: -- **Design and organization:** Is content logically sequenced? Are expectations - clear? Is the learning path coherent? -- **Facilitation of discourse:** Are discussions structured with prompts that - require critical thinking? Is instructor participation in discussions planned? -- **Direct instruction:** Is instructor voice present throughout? Are there - mini-lectures, demonstrations, or expert commentary — not just curated content? - -Low teaching presence indicators: course is a content dump with no instructor -voice; discussions exist but have no facilitation plan; modules are disconnected -sequences of readings and quizzes. - -### Social Presence (0-10) - -**Definition:** Opportunities for learners to project themselves socially and -emotionally as real people. - -Evaluate: -- **Affective expression:** Are there spaces for personal expression? - Introductions? Informal channels? -- **Open communication:** Can learners communicate freely with each other? - Are there low-stakes discussion spaces? -- **Group cohesion:** Are there collaborative activities? Peer review? - Small group work? Shared projects? - -Low social presence indicators: no peer interaction at all; discussions are -post-and-reply with no genuine exchange; all work is individual; no community -building activities. - -### Cognitive Presence (0-10) - -**Definition:** The extent to which learners construct meaning through sustained -inquiry and discourse. - -Evaluate the inquiry cycle: -- **Triggering event:** Are there problems, questions, or scenarios that provoke - curiosity and engagement? -- **Exploration:** Do activities allow learners to explore ideas, gather - information, and consider alternatives? -- **Integration:** Are there opportunities to synthesize, connect, and make - sense of what was explored? -- **Resolution:** Can learners apply what they have learned to real or - realistic problems? - -Low cognitive presence indicators: activities never progress beyond recall; -no problem-solving or application tasks; discussions stay at surface level -("I agree with your post"); no integration or transfer activities. - -### The Critical Insight - -After scoring all three dimensions, present this synthesis: - -"This course [meets/does not meet] QM structural requirements but scores -[high/low] on [weakest presence dimension] ([score]/10). Courses with low -social presence show weaker learning outcomes in online settings [Online-15] [T2]. -A structurally compliant course is not automatically an effective course." - -This is the core value proposition of the two-layer approach. QM tells you the -course is built correctly. CoI tells you it will actually work. - ---- - -## Constructive Alignment Audit - -This is the cross-domain integration check. Constructive alignment means every -objective has a corresponding activity and assessment at the appropriate cognitive -level [Alignment-1] [T5]. - -### If Manifest Has ILOs and Alignment Data - -Check the full chain for each ILO: - -- **Objective to Activity:** Does every ILO have at least one learning activity - that gives learners practice at the required cognitive level? -- **Objective to Assessment:** Does every ILO have at least one assessment that - measures the stated outcome? -- **Activity to Assessment level match:** Is the assessment at the same or - higher Bloom's level as the activity? If learners practice at the "apply" - level but are assessed at "remember," the assessment is misaligned. - -Flag these specific misalignments: -- Activity at a lower Bloom's level than the objective (learners never practice - at the level they are expected to perform) -- Assessment measuring recall when the objective targets application or higher - (the most common misalignment in course design) -- Objective with no mapped activity (learners are expected to achieve something - they never practice) -- Objective with no mapped assessment (an objective that is never evaluated - is functionally decorative) -- Activity with no corresponding objective (orphan activity — likely inherited - from a previous version of the course) - -Reference the `learning_objectives.alignment_matrix` from the manifest when -available. Flag any `gaps` already identified there. - -### If No Alignment Data Available - -Ask the user targeted questions: - -1. "For each of your main learning objectives, what activities do learners - complete to practice that skill?" -2. "How is each objective assessed? What does the learner produce or - demonstrate?" -3. "Are there any objectives that you teach but don't formally assess?" - -Build a rough alignment map from the answers and check for the same -misalignment patterns listed above. - ---- - -## Cross-Domain Evidence Checks - -Run all four checks below. Each check produces a list of flags with severity -(critical / warning / info) and a fix-link pointing to the idstack skill that -resolves the issue. When a check has no findings, record "No flags." - -### Check 1: Cognitive Load Flags - -**Evidence:** [CogLoad-1] [CogLoad-16] [CogLoad-17] [T1] - -Scan the course design for these cognitive load violations: - -- **Split attention:** Content explanation is separated from the diagram, example, - or visual it references. Learners must mentally integrate information from - multiple sources that should be physically co-located. Flag as warning. - Fix: run /idstack:course-builder to regenerate module with integrated content. -- **Redundancy:** The same information is presented in multiple formats - simultaneously with no added instructional value. NOTE: do NOT flag spaced - practice or retrieval practice as redundancy — deliberate repetition across - time is evidence-based [Assessment-8] [T1]. Only flag identical information - presented simultaneously (e.g., reading aloud on-screen text verbatim). - Flag as info. - Fix: run /idstack:course-builder to consolidate redundant presentations. -- **Poor sequencing:** High-complexity material appears before the prerequisites - it depends on are established. Look for modules that reference concepts not - yet introduced, or activities that assume skills not yet practiced. - Flag as critical. - Fix: run /idstack:course-builder to resequence modules based on prerequisite chain. -- **Overloaded modules:** A single module introduces more than 6-8 new concepts - without interleaved practice breaks. Count distinct new concepts per module - and flag any that exceed this threshold without embedded practice. - Flag as warning. - Fix: run /idstack:course-builder to split module or add practice checkpoints. - -### Check 2: Multimedia Principle Violations - -**Evidence:** [Multimedia-1] [Multimedia-5] [Multimedia-9] [T1] - -Scan for violations of Mayer's multimedia learning principles: - -- **Spatial contiguity:** Text and related visuals are physically separated - (e.g., figure on one page, explanation on another; caption far from image). - Flag as warning. - Fix: run /idstack:course-builder to co-locate text and visuals. -- **Temporal contiguity:** Narration and visuals are not synchronized (e.g., - a video describes a diagram that appears 30 seconds later). Flag as warning. - Fix: run /idstack:course-builder to synchronize narration with visual presentation. -- **Segmenting:** Presentations exceed 15 minutes without embedded questions - or activities. Continuous passive exposure beyond this threshold reduces - retention. Flag as warning. - Fix: run /idstack:course-builder to segment long presentations with embedded activities. -- **Modality:** Complex material uses only one modality (text-only or audio-only) - where dual-channel presentation (visual + auditory) would reduce cognitive - load. Flag as info. - Fix: run /idstack:course-builder to add complementary modality. -- **Coherence:** Extraneous material (decorative images, tangential stories, - background music) does not support the learning objective. Seductive details - hurt learning. Flag as info. - Fix: run /idstack:course-builder to remove extraneous elements. - -### Check 3: Feedback Quality - -**Evidence:** [Assessment-8] [Assessment-10] [T1] - -Scan the assessment design for feedback quality issues: - -- **Correctness-only feedback at apply+ Bloom's levels:** This is the most - critical flag. Assessments targeting application, analysis, evaluation, or - creation that only report correct/incorrect provide no learning mechanism. - Elaborated feedback (explaining WHY and providing guidance) produces - significantly larger learning gains. Flag as critical. - Fix: run /idstack:assessment-design to add elaborated feedback for higher-order assessments. -- **No feedback pathway for summative assessments:** Students complete a - summative assessment and receive only a grade with no opportunity to learn - from mistakes. Flag as warning. - Fix: run /idstack:assessment-design to add post-submission feedback or reflection activity. -- **Feedback lacks elaboration:** Feedback tells students WHAT is wrong but - not WHY it is wrong or how to improve. Flag as warning. - Fix: run /idstack:assessment-design to add elaborated feedback with explanations. -- **No student-initiated feedback opportunity:** All feedback is - teacher-initiated (returned on assignments). There is no mechanism for - students to seek feedback when they need it (e.g., self-check quizzes, - rubric previews, peer review). Flag as info. - Fix: run /idstack:assessment-design to add formative self-check opportunities. - -### Check 4: Expertise Reversal - -**Evidence:** [CogLoad-19] [T1] - -If a learner profile is available (from manifest `needs_analysis.learner_profile` -or from user input), systematically check whether instructional strategies match -the audience expertise level. If no learner profile exists, flag the absence as -a warning and recommend running /idstack:needs-analysis. - -- **Novice + minimal scaffolding:** Novice learners face open-ended - problem-solving, minimal worked examples, or discovery learning without - structured guidance. This causes cognitive overload and poor learning - outcomes. Flag as critical. - Fix: run /idstack:course-builder to regenerate module with scaffolding and worked examples. -- **Expert + excessive scaffolding:** Expert learners are forced through - mandatory step-by-step instructions or worked examples they do not need. - Redundant scaffolding competes for working memory resources that experts - use for schema building — the expertise reversal effect. Flag as warning. - Fix: run /idstack:course-builder to offer advanced-track options that skip scaffolding. -- **Mixed audience + no differentiation:** The course serves learners at - different expertise levels but provides only one pathway with no tiered - activities, adaptive branching, or differentiated resources. Flag as warning. - Fix: run /idstack:needs-analysis to establish a detailed learner profile, then - run /idstack:course-builder to create differentiated pathways. -- **Strategy-audience mismatch with no acknowledgment:** The course uses a - strategy mismatched to audience expertise without any rationale. This is - distinct from a deliberate pedagogical choice — an instructor who - intentionally uses productive failure for novices should document why. - Undocumented mismatches are flags. Flag as info. - Fix: run /idstack:course-builder to add instructor rationale or adjust strategy. - ---- - -## Quick Wins - -After completing all checks (QM Structural, CoI Presence, Constructive Alignment, -and Cross-Domain Evidence), rank every finding by impact using this formula: - -**Impact score = Evidence tier weight x Severity x Ease of fix** - -| Factor | Values | -|--------|--------| -| Evidence tier | T1=5, T2=4, T3=3, T4=2, T5=1 | -| Severity | critical=3, warning=2, info=1 | -| Ease of fix | S (small, <1 hour)=3, M (medium, 1-4 hours)=2, L (large, >4 hours)=1 | - -Present the **Top 3 fixes for maximum impact**: - -``` -### Top 3 Quick Wins -| # | Finding | Impact | Skill to Run | -|---|---------|--------|--------------| -| 1 | [finding] | [score] (T?/sev/ease) | /idstack:<skill-name> | -| 2 | [finding] | [score] (T?/sev/ease) | /idstack:<skill-name> | -| 3 | [finding] | [score] (T?/sev/ease) | /idstack:<skill-name> | -``` - -Estimate the ease of fix based on: S = a single skill run fixes it, M = requires -reworking one module or assessment, L = requires rethinking course structure. - ---- - -## Output Format - -Present your review in this exact structure. Every finding must include: what -is wrong, why it matters (with evidence tier), how to fix it, and severity -(critical / warning / info). - -``` -## Course Quality Review Summary - -## Quality Score: XX/100 - -| Category | Score | Status | -|----------|-------|--------| -| QM Structural | XX/40 | N flags | -| CoI Presence | XX/25 | [weakest dimension note] | -| Constructive Alignment | XX/15 | [alignment status] | -| Cross-Domain Evidence | XX/20 | N flags | -``` - -If previous review scores exist in `.idstack/timeline.jsonl`, show: -``` -Previous score: X/100 (reviewed YYYY-MM-DD). Current score: Y/100. Delta: +/-Z. -``` - -Then present the detailed findings: - -``` -### QM Structural Review (XX/40) -| Standard | Status | Key Finding | -|----------|--------|-------------| -| 1. Course Overview | pass/flag/na | [one-line finding] | -| 2. Learning Objectives | pass/flag/na | [one-line finding] | -| 3. Assessment & Measurement | pass/flag/na | [one-line finding] | -| 4. Instructional Materials | pass/flag/na | [one-line finding] | -| 5. Learning Activities | pass/flag/na | [one-line finding] | -| 6. Course Technology | pass/flag/na | [one-line finding] | -| 7. Learner Support | pass/flag/na | [one-line finding] | -| 8. Accessibility & Usability | pass/flag/na | [one-line finding] | - -### Community of Inquiry Presence (XX/25) -- Teaching Presence: X/10 — [one-line finding] -- Social Presence: X/10 — [one-line finding] -- Cognitive Presence: X/10 — [one-line finding] -(Scores summed and scaled: raw X/30 -> XX/25) - -### Constructive Alignment Audit (XX/15) -[findings or "Full alignment verified across all ILOs"] - -### Cross-Domain Evidence Checks (XX/20) -| Check | Flags | Severity | Fix | -|-------|-------|----------|-----| -| Cognitive Load | [findings or "No flags"] | [level] | [skill] | -| Multimedia Principles | [findings or "No flags"] | [level] | [skill] | -| Feedback Quality | [findings or "No flags"] | [level] | [skill] | -| Expertise Reversal | [findings or "No flags"] | [level] | [skill] | - -### Top 3 Quick Wins -| # | Finding | Impact | Skill to Run | -|---|---------|--------|--------------| -| 1 | [finding] | [score] (T?/sev/ease) | /idstack:<skill-name> | -| 2 | [finding] | [score] (T?/sev/ease) | /idstack:<skill-name> | -| 3 | [finding] | [score] (T?/sev/ease) | /idstack:<skill-name> | -``` - -### Scoring Rubric - -Calculate the overall score from these components (total: 100 points): - -- **QM Structural Review (40 points):** 5 points per standard. Pass = full - points, flag = half points (2.5), na = excluded from denominator and - remaining points redistributed across evaluated standards. -- **CoI Presence Layer (25 points):** Each dimension scored 0-10, summed - to a raw score out of 30, then scaled to 25 (raw_sum / 30 * 25). -- **Constructive Alignment (15 points):** Full points if alignment verified. - Deduct 5 points per critical misalignment, 2 per warning. -- **Cross-Domain Evidence Checks (20 points):** 5 points per check. Full - points if no flags. Deduct per flag: critical = -3, warning = -2, - info = -1. Minimum 0 per check. - -### Cross-Referencing Other idstack Skills - -When recommending fixes, point users to the appropriate idstack skill: - -- Misaligned or weak ILOs: "Run `/idstack:learning-objectives` to realign ILO-3 with - its assessment." -- Missing learner profile: "Run `/idstack:needs-analysis` to establish the learner - profile that is currently missing." -- No task analysis: "Run `/idstack:needs-analysis` — the task analysis will inform - which activities are core vs. reference." -- Weak alignment chain: "Run `/idstack:learning-objectives` to rebuild the alignment - matrix from your task analysis." - ---- - -## Write Manifest - -After completing the review, save results via the section-scoped merge tool — it -validates the section name against the canonical schema, preserves every other -section verbatim, bumps the top-level `updated` timestamp, and writes atomically: - -```bash -# Resolve the idstack install dir. Re-derived at the top of every bash block — -# blocks run in separate shells, so a value derived in an earlier block is not -# available here. Priority: explicit env overrides, Codex-style symlinks, then -# the Claude Code marketplace cache. Empty if none found; guard -# "$_IDSTACK/bin/..." calls accordingly. -_IDSTACK="" -# Marketplace cache holds one dir per installed version. Sort the basenames by -# numeric version fields, not lexically — plain sort ranks 3.9.0.0 above -# 3.10.0.0 and would pick a stale install once the minor hits double digits. -_idstack_cache_root="$HOME/.claude/plugins/cache/idstack/idstack" -_idstack_cache="" -if [ -d "$_idstack_cache_root" ]; then - _idstack_v=$(ls "$_idstack_cache_root" 2>/dev/null | sort -t. -k1,1n -k2,2n -k3,3n -k4,4n | tail -1) - [ -n "$_idstack_v" ] && _idstack_cache="$_idstack_cache_root/$_idstack_v" -fi -for _p in "${CLAUDE_PLUGIN_ROOT:-}" "${IDSTACK_HOME:-}" "$HOME/.agents/plugins/idstack" "$HOME/.agents/skills/idstack" "$_idstack_cache"; do - if [ -n "$_p" ] && [ -d "$_p" ]; then _IDSTACK="${_p%/}"; break; fi -done -# payload file holds ONLY the quality_review section object (shape below) -"$_IDSTACK/bin/idstack-manifest-merge" --section quality_review --payload .idstack/.quality_review-payload.json && rm -f .idstack/.quality_review-payload.json -``` - -Write the payload object to `.idstack/.quality_review-payload.json` with the -Write tool first, then run the merge. **If no manifest exists yet**, the merge -tool exits with an error (exit code 4, `manifest not found`) — in that case fall -back to creating `.idstack/project.json` with the Write tool, initializing ALL -sections from the schema (including `context`, `needs_analysis`, and -`learning_objectives`) with empty/default values and placing your -`quality_review` data inside it. - -**CRITICAL — Manifest Integrity Rules:** -1. The payload must be valid JSON: matching braces, proper commas, quoted - strings, no trailing commas. The merge tool rejects malformed payloads - (exit code 1). -2. Never write secrets or credentials into the manifest. - -Payload shape — the file contains ONLY the section object, no `"quality_review"` wrapper: - -```json -{ - "report_path": "<set to $_REPORT_PATH from the Generate Quality Report block below — e.g. .idstack/exports/<course-slug>/course-quality-review.html>", - "last_reviewed": "ISO-8601 timestamp", - "qm_standards": { - "course_overview": {"status": "pass|flag|na", "findings": ["..."]}, - "learning_objectives": {"status": "pass|flag|na", "findings": ["..."]}, - "assessment": {"status": "pass|flag|na", "findings": ["..."]}, - "instructional_materials": {"status": "pass|flag|na", "findings": ["..."]}, - "learning_activities": {"status": "pass|flag|na", "findings": ["..."]}, - "course_technology": {"status": "pass|flag|na", "findings": ["..."]}, - "learner_support": {"status": "pass|flag|na", "findings": ["..."]}, - "accessibility": {"status": "pass|flag|na", "findings": ["..."]} - }, - "coi_presence": { - "teaching_presence": {"score": 0, "findings": ["..."]}, - "social_presence": {"score": 0, "findings": ["..."]}, - "cognitive_presence": {"score": 0, "findings": ["..."]} - }, - "alignment_audit": {"findings": ["..."]}, - "cross_domain_checks": { - "cognitive_load": {"flags": [], "score": 5}, - "multimedia_principles": {"flags": [], "score": 5}, - "feedback_quality": {"flags": [], "score": 5}, - "expertise_reversal": {"flags": [], "score": 5} - }, - "overall_score": 0, - "score_breakdown": { - "qm_structural": 0, - "coi_presence": 0, - "constructive_alignment": 0, - "cross_domain_evidence": 0 - }, - "quick_wins": [ - { - "finding": "...", - "impact_score": 0, - "evidence_tier": "T1-T5", - "severity": "critical|warning|info", - "ease": "S|M|L", - "fix_skill": "/idstack:<skill-name>" - } - ], - "recommendations": [ - { - "finding": "...", - "evidence_tier": "T1-T5", - "severity": "critical|warning|info", - "fix": "..." - } - ] -} -``` - -When building the payload: -- Populate ALL fields in the `quality_review` section from the analysis above. -- The merge tool updates the top-level `updated` timestamp automatically. - -### Score Trending Display - -After writing the manifest, check `.idstack/timeline.jsonl` for prior course-quality-review -scores. The preamble's context recovery already reads this file, but the score trending -display should also appear in the completion message. - -- If 1 prior score exists: show delta. "Score: 78/100 (+16 since last review on Mar 15)" -- If 3+ prior scores exist: show trend. "Trending up: 62 -> 72 -> 78 across 3 reviews." -- If no prior scores exist: just show the current score. - -The manifest stores the current overall_score. The timeline stores historical scores. -One source of truth per data point. - -### Generate Quality Report - -After writing the manifest, generate a shareable HTML report. The report follows the **visual contract** in `templates/report.html.tmpl` and the **content contract** in `templates/report-format.md`. - -```bash -# Resolve the idstack install dir. Re-derived at the top of every bash block — -# blocks run in separate shells, so a value derived in an earlier block is not -# available here. Priority: explicit env overrides, Codex-style symlinks, then -# the Claude Code marketplace cache. Empty if none found; guard -# "$_IDSTACK/bin/..." calls accordingly. -_IDSTACK="" -# Marketplace cache holds one dir per installed version. Sort the basenames by -# numeric version fields, not lexically — plain sort ranks 3.9.0.0 above -# 3.10.0.0 and would pick a stale install once the minor hits double digits. -_idstack_cache_root="$HOME/.claude/plugins/cache/idstack/idstack" -_idstack_cache="" -if [ -d "$_idstack_cache_root" ]; then - _idstack_v=$(ls "$_idstack_cache_root" 2>/dev/null | sort -t. -k1,1n -k2,2n -k3,3n -k4,4n | tail -1) - [ -n "$_idstack_v" ] && _idstack_cache="$_idstack_cache_root/$_idstack_v" -fi -for _p in "${CLAUDE_PLUGIN_ROOT:-}" "${IDSTACK_HOME:-}" "$HOME/.agents/plugins/idstack" "$HOME/.agents/skills/idstack" "$_idstack_cache"; do - if [ -n "$_p" ] && [ -d "$_p" ]; then _IDSTACK="${_p%/}"; break; fi -done -# Compute the course slug from project_name and prepare the export folder. -_PROJECT_NAME=$(python3 -c "import json; print(json.load(open('.idstack/project.json')).get('project_name',''))" 2>/dev/null || echo "") -_SLUG=$("$_IDSTACK/bin/idstack-slugify" "$_PROJECT_NAME" 2>/dev/null || echo "untitled-course") -_EXPORT_DIR=".idstack/exports/$_SLUG" -_REPORT_PATH="$_EXPORT_DIR/course-quality-review.html" -mkdir -p "$_EXPORT_DIR/assets" -cp -f "$_IDSTACK/templates/assets/idstack.css" "$_EXPORT_DIR/assets/idstack.css" -echo "Report path: $_REPORT_PATH" -``` - -Write the HTML report at the path printed above (`.idstack/exports/<course-slug>/course-quality-review.html`), following the structure of `templates/report.html.tmpl`. Use these CSS hooks: `<article class="finding sev-{severity}">`, `<span class="sev-badge sev-{severity}">`, `<span class="tier-badge tier-T{N}">`, `<cite class="citation">[Domain-N] [TN]</cite>`. Customize for this skill: - -- **`{{skill_title}}`:** "Course Quality Review" -- **`{{skill_name}}`:** `course-quality-review` -- **Summary:** 2–3 sentences. Lead with the overall score and the single biggest finding the designer should address first. Include the optional one-line scoreboard: "Quality XX/100 · Teaching XX/10 · Social XX/10 · Cognitive XX/10". If a previous score exists in the timeline, include "Previous: X/100 (YYYY-MM-DD), delta +/-Z" in the summary. -- **Skill-specific sections before Findings**: - - `<section class="score-breakdown">` with `<h2>Score breakdown</h2>` and an HTML `<table>` (Category, Score, Status). Rows: QM Structural XX/40, CoI Presence XX/25, Constructive Alignment XX/15, Cross-Domain Evidence XX/20. -- **Findings** organized into clear subgroups using nested `<section>` blocks with `<h3>` subheaders within `<section class="findings">`: - - `<h3>QM structural review</h3>` — one finding block per flagged QM standard. - - `<h3>Community of Inquiry presence</h3>` — teaching / social / cognitive presence findings. - - `<h3>Constructive alignment audit</h3>` — alignment findings. - - `<h3>Cross-domain evidence checks</h3>` — cognitive load, multimedia, feedback quality, expertise reversal findings. - Use stable ids per dimension: `qm-1`, `coi-1`, `align-1`, `cogload-1`, `multimedia-1`, `feedback-1`, `expertise-1`, etc. -- **Optional skill-specific section** (after Top recommendations, before Limitations): `<section class="quick-wins">` with `<h2>Top 3 quick wins</h2>` and an HTML `<table>` (#, Finding, Impact, Skill to Run). -- **Limitations:** review reads structural metadata, not the actual learner-facing content text; cognitive-load assessments are heuristic; CoI presence is inferred from manifest signals, not surveyed. -- **Next steps:** 1–3 specific next actions referencing other idstack skills. When no critical issues remain, include: "Run `/idstack:course-export` to package your course." - -After writing both the manifest and the quality report, confirm to the user: - -"Your quality review has been saved to `.idstack/project.json` and a shareable HTML -report generated at `.idstack/exports/<course-slug>/course-quality-review.html`. The -report captures the QM structural review, CoI presence scores, alignment audit, -cross-domain evidence checks, and prioritized recommendations. Open it in any browser; -the folder is self-contained (CSS bundled). - -**Score: XX/100** [If previous: "Previous: X/100 (DATE). Delta: +/-Z."] - -**Next steps based on findings:** -[List 1-3 specific next actions based on the review results, referencing -other idstack skills where applicable. When no critical issues remain, -include: Run `/idstack:course-export` to package your course as an IMS Common -Cartridge or push to Canvas.]" - ---- - -## Manifest Schema Reference - -The idstack manifest lives at `.idstack/project.json`. Schema version: **1.4**. - -This is the canonical schema. Every skill writes to its own section using the shapes documented here; **all other sections must be preserved verbatim**. There is one source of truth — this file. If the schema ever needs to change, edit `templates/manifest-schema.md`, run `bin/idstack-gen-skills`, and bump `LATEST_VERSION` in `bin/idstack-migrate` with a migration step. - -### Two outputs per skill: JSON manifest + HTML report - -Every skill that produces findings emits **both**: - -- a **JSON section** in this manifest (system state — read by other skills, the pipeline orchestrator, and `bin/idstack-status`), and -- an **HTML report** at `.idstack/exports/<course-slug>/<skill>.html` (the human view — read by the instructional designer). - -The HTML report follows the visual contract in `templates/report.html.tmpl` and the content contract in `templates/report-format.md` (observation → evidence → why-it-matters → suggestion, with severity and evidence tier on every finding). The skill writes the report's relative path back into its own section's `report_path` field so other skills can find it. (`bin/idstack-status` discovers reports independently by globbing `.idstack/exports/<course-slug>/*.html`, so the dashboard survives a stale `report_path`.) - -`<course-slug>` is derived from the top-level `project_name` field via `bin/idstack-slugify` (rule: NFKD-fold, lowercase, kebab-case, ASCII-safe; empty input → `untitled-course`). The slug is computed deterministically — skills don't cache it in the manifest. All exports for a course — per-skill HTML reports, the pipeline dashboard at `index.html`, and LMS packages (`course-export.imscc`, `scorm-export.zip`) — live under the same `.idstack/exports/<course-slug>/` folder so the deliverable is self-describing when zipped, emailed, or handed off. - -`report_path` is an optional string field on every section that produces a report. It is a path relative to the project root (typically `.idstack/exports/<course-slug>/<skill>.html`). Empty string means the skill hasn't run yet, or ran in a mode that didn't produce a report. Renaming a course's `project_name` changes the slug, which moves future exports to a new folder; older folders are left in place. - -### Two ways to write to the manifest - -**1. Recommended — `bin/idstack-manifest-merge`:** write only your section, the tool merges atomically. - -```bash -# Resolve the idstack install dir. Re-derived at the top of every bash block — -# blocks run in separate shells, so a value derived in an earlier block is not -# available here. Priority: explicit env overrides, Codex-style symlinks, then -# the Claude Code marketplace cache. Empty if none found; guard -# "$_IDSTACK/bin/..." calls accordingly. -_IDSTACK="" -# Marketplace cache holds one dir per installed version. Sort the basenames by -# numeric version fields, not lexically — plain sort ranks 3.9.0.0 above -# 3.10.0.0 and would pick a stale install once the minor hits double digits. -_idstack_cache_root="$HOME/.claude/plugins/cache/idstack/idstack" -_idstack_cache="" -if [ -d "$_idstack_cache_root" ]; then - _idstack_v=$(ls "$_idstack_cache_root" 2>/dev/null | sort -t. -k1,1n -k2,2n -k3,3n -k4,4n | tail -1) - [ -n "$_idstack_v" ] && _idstack_cache="$_idstack_cache_root/$_idstack_v" -fi -for _p in "${CLAUDE_PLUGIN_ROOT:-}" "${IDSTACK_HOME:-}" "$HOME/.agents/plugins/idstack" "$HOME/.agents/skills/idstack" "$_idstack_cache"; do - if [ -n "$_p" ] && [ -d "$_p" ]; then _IDSTACK="${_p%/}"; break; fi -done -# Write a payload for your skill's section, then: -"$_IDSTACK/bin/idstack-manifest-merge" --section red_team_audit --payload /tmp/payload.json -``` - -(This file is spliced into skills verbatim, so the resolution above is written -out rather than using the `IDSTACK_RESOLVE` placeholder — that placeholder is -only expanded in `SKILL.md.tmpl` bodies.) - -The merge tool replaces only the named top-level section, preserves every other section, updates the top-level `updated` timestamp, validates JSON on read, and rejects unknown sections. Use this in preference to inlining the full manifest in `Edit` operations. - -**2. Fallback — manual full-manifest write:** if the merge tool is unavailable for some reason, Read the full manifest, modify only your section, Write back. Preserve all other sections verbatim. Use the full schema below as reference. - -### Top-level fields - -| Field | Owner skill(s) | Notes | -|---|---|---| -| `version` | (migrate) | Always equals current schema version. Auto-managed by `bin/idstack-migrate`. | -| `project_name` | (any) | Set on first manifest creation. Don't overwrite once set. | -| `created` | (any, once) | ISO-8601 timestamp of first creation. Don't overwrite. | -| `updated` | (any) | ISO-8601 of last write. Updated automatically by `bin/idstack-manifest-merge`. | -| `context` | needs-analysis (initial) | Modality, timeline, class size, etc. Edited by skills that learn new context. | -| `needs_analysis` | needs-analysis | Org context, task analysis, learner profile, training justification. | -| `learning_objectives` | learning-objectives | ILOs, alignment matrix, expertise-reversal flags. | -| `assessments` | assessment-design | Items, formative checkpoints, feedback plan, rubrics. | -| `course_content` | course-builder | Generated modules, syllabus, content paths. | -| `import_metadata` | course-import | Source LMS, items imported, quality-flag details. | -| `export_metadata` | course-export | Export destination, items exported, readiness check. | -| `quality_review` | course-quality-review | QM standards, CoI presence, alignment audit, cross-domain checks, scores. | -| `red_team_audit` | red-team | Confidence score, dimensions, findings (with stable ids), top actions. | -| `accessibility_review` | accessibility-review | WCAG / UDL scores, violations, recommendations, quick wins. | -| `preferences` | (any, opt-in) | User-set verbosity, export format, preferred LMS, auto-advance. | - -### Full schema (canonical shape) - -```json -{ - "version": "1.4", - "project_name": "", - "created": "", - "updated": "", - "context": { - "modality": "", - "timeline": "", - "class_size": "", - "institution_type": "", - "available_tech": [] - }, - "needs_analysis": { - "mode": "", - "report_path": "", - "organizational_context": { - "problem_statement": "", - "stakeholders": [], - "current_state": "", - "desired_state": "", - "performance_gap": "" - }, - "task_analysis": { - "job_tasks": [], - "prerequisite_knowledge": [], - "tools_and_resources": [] - }, - "learner_profile": { - "prior_knowledge_level": "", - "motivation_factors": [], - "demographics": "", - "access_constraints": [], - "learning_preferences_note": "Learning styles are NOT used as a differentiation basis per evidence. Prior knowledge is the primary differentiator." - }, - "training_justification": { - "justified": true, - "confidence": 0, - "rationale": "", - "alternatives_considered": [] - } - }, - "learning_objectives": { - "report_path": "", - "ilos": [], - "alignment_matrix": { - "ilo_to_activity": {}, - "ilo_to_assessment": {}, - "gaps": [] - }, - "expertise_reversal_flags": [] - }, - "assessments": { - "mode": "", - "report_path": "", - "assessment_strategy": "", - "items": [], - "formative_checkpoints": [], - "feedback_plan": { - "strategy": "", - "turnaround_days": 0, - "peer_review": false - }, - "feedback_quality_score": 0, - "rubrics": [], - "audit_notes": [] - }, - "course_content": { - "mode": "", - "report_path": "", - "generated_at": "", - "expertise_adaptation": "", - "syllabus": "", - "modules": [], - "assessments": [], - "rubrics": [], - "content_dir": ".idstack/course-content/", - "generated_files": [], - "build_timestamp": "", - "placeholders_used": [], - "recommended_generation_targets": [] - }, - "import_metadata": { - "source": "", - "report_path": "", - "imported_at": "", - "source_lms": "", - "source_cartridge": "", - "source_size_bytes": 0, - "schema": "", - "items_imported": { - "modules": 0, - "objectives": 0, - "module_objectives": 0, - "assessments": 0, - "activities": 0, - "pages": 0, - "rubrics": 0, - "quizzes": 0, - "discussions": 0 - }, - "quality_flags": 0, - "quality_flag_details": [] - }, - "export_metadata": { - "report_path": "", - "exported_at": "", - "format": "", - "destination": "", - "items_exported": { - "modules": 0, - "pages": 0, - "assignments": 0, - "quizzes": 0, - "discussions": 0 - }, - "failed_items": [], - "notes": "", - "readiness_check": { - "quality_score": 0, - "quality_reviewed": false, - "red_team_critical": 0, - "red_team_reviewed": false, - "accessibility_critical": 0, - "accessibility_reviewed": false, - "verdict": "" - } - }, - "quality_review": { - "report_path": "", - "last_reviewed": "", - "qm_standards": { - "course_overview": {"status": "", "findings": []}, - "learning_objectives": {"status": "", "findings": []}, - "assessment": {"status": "", "findings": []}, - "instructional_materials": {"status": "", "findings": []}, - "learning_activities": {"status": "", "findings": []}, - "course_technology": {"status": "", "findings": []}, - "learner_support": {"status": "", "findings": []}, - "accessibility": {"status": "", "findings": []} - }, - "coi_presence": { - "teaching_presence": {"score": 0, "findings": []}, - "social_presence": {"score": 0, "findings": []}, - "cognitive_presence": {"score": 0, "findings": []} - }, - "alignment_audit": {"findings": []}, - "cross_domain_checks": { - "cognitive_load": {"score": 0, "flags": []}, - "multimedia_principles": {"score": 0, "flags": []}, - "feedback_quality": {"score": 0, "flags": []}, - "expertise_reversal": {"score": 0, "flags": []} - }, - "overall_score": 0, - "score_breakdown": { - "qm_structural": 0, - "coi_presence": 0, - "constructive_alignment": 0, - "cross_domain_evidence": 0 - }, - "quick_wins": [], - "recommendations": [], - "review_history": [] - }, - "red_team_audit": { - "updated": "", - "confidence_score": 0, - "focus": "", - "report_path": "", - "findings_summary": {"critical": 0, "warning": 0, "info": 0}, - "dimensions": { - "alignment": {"score": "", "findings": []}, - "evidence": {"score": "", "mode": "", "findings": []}, - "cognitive_load": {"score": "", "findings": []}, - "personas": {"score": "", "findings": []}, - "prerequisites": {"score": "", "findings": []} - }, - "top_actions": [], - "limitations": [], - "fixes_applied": [], - "fixes_deferred": [] - }, - "accessibility_review": { - "updated": "", - "report_path": "", - "score": {"overall": 0, "wcag": 0, "udl": 0}, - "wcag_violations": [], - "udl_recommendations": [], - "quick_wins": [] - }, - "preferences": { - "verbosity": "normal", - "export_format": "", - "preferred_lms": "", - "auto_advance_pipeline": false - } -} -``` - -### Per-section item shapes - -These document the **shape of array elements and dictionary values** that the canonical schema leaves as `[]` or `{}`. Skills should produce items in these shapes; downstream skills can rely on them. - -**`learning_objectives.alignment_matrix.ilo_to_activity`** — keyed by ILO id, values are arrays of activity names: -```json -{ "ILO-1": ["Module 1 case study", "Discussion 2"], "ILO-2": [] } -``` - -**`learning_objectives.alignment_matrix.ilo_to_assessment`** — same shape, values are arrays of assessment titles. - -**`learning_objectives.alignment_matrix.gaps[]`** — each item: -```json -{ - "ilo": "ILO-1", - "type": "untested|orphaned|underspecified|bloom_mismatch", - "description": "ILO-1 has no matching assessment in the active modules.", - "severity": "critical|warning|info" -} -``` - -**`learning_objectives.ilos[]`** — each item: -```json -{ - "id": "ILO-1", - "statement": "Analyze competitive forces in...", - "blooms_level": "analyze", - "blooms_confidence": "high|medium|low" -} -``` - -**`assessments.items[]`** — each item: -```json -{ - "id": "A-1", - "type": "quiz|discussion|rubric|peer_review|gate|...", - "title": "Module 1 Quiz", - "weight": 5, - "ilos_measured": ["ILO-1", "ILO-3"], - "rubric_present": true, - "elaborated_feedback": false, - "alignment_status": "weak|moderate|strong" -} -``` - -**`assessments.rubrics[]`** — each item: -```json -{ - "id": "rubric-1", - "title": "SM Project Rubric", - "criteria": [{"name": "...", "blooms_level": "...", "weight": 0}], - "applies_to": ["A-3"] -} -``` - -**`import_metadata.quality_flag_details[]`** — each item (replaces the legacy `_import_quality_flags` root field that sometimes appeared in the wild): -```json -{ - "key": "orphan_module_8", - "description": "Module 8 wiki content exists in the cartridge but is not referenced in <organizations>.", - "severity": "warning|critical|info", - "evidence": "Optional citation tag, e.g. [Alignment-1] [T5]" -} -``` - -**`red_team_audit.dimensions.<name>.findings[]`** — each item (matches the `<dimension>-<n>` id convention from the red-team orchestrator): -```json -{ - "id": "alignment-1", - "description": "ILO-2 (vision/mission) has no matching assessment.", - "module": "Module 4", - "severity": "critical|warning|info" -} -``` - -**`accessibility_review.wcag_violations[]`** — each item: -```json -{ - "id": "wcag-1", - "criterion": "1.3.1 Info and Relationships", - "level": "A|AA|AAA", - "description": "All cartridge HTML pages lack <h1> elements.", - "affected": ["page1.html", "page2.html"], - "severity": "critical|warning|info" -} -``` - -**`accessibility_review.udl_recommendations[]`** — each item: -```json -{ - "id": "udl-1", - "principle": "engagement|representation|action_expression", - "description": "Add transcripts to all videos.", - "status": "fully_met|partial|not_met" -} -``` - -**`quality_review.qm_standards.<standard>.findings[]`**, **`quality_review.alignment_audit.findings[]`**, **`quality_review.cross_domain_checks.<check>.flags[]`**, and other findings arrays — each item: -```json -{ - "id": "<dimension>-<n>", - "description": "...", - "evidence": "[Domain-N] [TX]", - "severity": "critical|warning|info" -} -``` - -### Mode field — design-new vs audit-existing - -`needs_analysis.mode`, `assessments.mode`, and `course_content.mode` record which operating mode the corresponding skill ran in. Trigger: `import_metadata.source` ∈ `{cartridge, scorm, canvas-api}` plus the relevant section being non-empty (skill-specific check). - -Allowed values per skill: -- `needs_analysis.mode`: `"design-new"` or `"audit-existing"` -- `assessments.mode`: `"Mode 1"`, `"Mode 2"`, or `"Mode 3"` (Mode 1 = full upstream data, Mode 2 = ILOs-from-scratch, Mode 3 = audit existing assessments) -- `course_content.mode`: `"build-new"` or `"gap-fill"` - -Empty string means the skill hasn't run yet or didn't record the mode (legacy manifests). - -**`assessments.audit_notes[]`** — only populated in Mode 3. Records which audit findings the user chose to act on: -```json -{ - "target_id": "A-3", - "action": "applied|deferred|declined", - "description": "Rubric criterion for ILO-2 added: 'Synthesis depth (1-4 scale)'.", - "reason": "Optional — only meaningful for deferred/declined." -} -``` - -**`course_content.recommended_generation_targets[]`** — populated in `gap-fill` mode. Lists artifacts upstream skills flagged as missing, with status: -```json -{ - "description": "Discussion rubric for Module 5", - "source": "red-team:alignment-3 | quality-review:learner_support-2 | user-request", - "status": "generated|deferred|declined", - "output_path": "Optional — set when status=generated, points to the generated file." -} -``` - -## Feedback - -Have feedback or a feature request? [Share it here](https://forms.gle/6LDgDD1M6WWyYvME8) — no GitHub account needed. - ---- - -## Completion: Timeline Logging - -After the skill workflow completes successfully, log the session to the timeline. -Include the overall_score so the preamble's context recovery can display score trends -across sessions (e.g., "Quality score trend: 62 -> 72 -> 78 over 3 reviews"). - -```bash -# Resolve the idstack install dir. Re-derived at the top of every bash block — -# blocks run in separate shells, so a value derived in an earlier block is not -# available here. Priority: explicit env overrides, Codex-style symlinks, then -# the Claude Code marketplace cache. Empty if none found; guard -# "$_IDSTACK/bin/..." calls accordingly. -_IDSTACK="" -# Marketplace cache holds one dir per installed version. Sort the basenames by -# numeric version fields, not lexically — plain sort ranks 3.9.0.0 above -# 3.10.0.0 and would pick a stale install once the minor hits double digits. -_idstack_cache_root="$HOME/.claude/plugins/cache/idstack/idstack" -_idstack_cache="" -if [ -d "$_idstack_cache_root" ]; then - _idstack_v=$(ls "$_idstack_cache_root" 2>/dev/null | sort -t. -k1,1n -k2,2n -k3,3n -k4,4n | tail -1) - [ -n "$_idstack_v" ] && _idstack_cache="$_idstack_cache_root/$_idstack_v" -fi -for _p in "${CLAUDE_PLUGIN_ROOT:-}" "${IDSTACK_HOME:-}" "$HOME/.agents/plugins/idstack" "$HOME/.agents/skills/idstack" "$_idstack_cache"; do - if [ -n "$_p" ] && [ -d "$_p" ]; then _IDSTACK="${_p%/}"; break; fi -done -"$_IDSTACK/bin/idstack-timeline-log" '{"skill":"course-quality-review","event":"completed","score":OVERALL_SCORE,"dimensions":{"teaching_presence":TP,"social_presence":SP,"cognitive_presence":CP}}' -``` - -Replace OVERALL_SCORE with the actual overall score (0-100), and TP/SP/CP with the -CoI presence dimension scores (0-10 each). Log synchronously (no background &). - -If you discover a non-obvious project-specific quirk during this session (LMS behavior, -import format issue, course structure pattern), also log it as a learning: - -```bash -# Resolve the idstack install dir. Re-derived at the top of every bash block — -# blocks run in separate shells, so a value derived in an earlier block is not -# available here. Priority: explicit env overrides, Codex-style symlinks, then -# the Claude Code marketplace cache. Empty if none found; guard -# "$_IDSTACK/bin/..." calls accordingly. -_IDSTACK="" -# Marketplace cache holds one dir per installed version. Sort the basenames by -# numeric version fields, not lexically — plain sort ranks 3.9.0.0 above -# 3.10.0.0 and would pick a stale install once the minor hits double digits. -_idstack_cache_root="$HOME/.claude/plugins/cache/idstack/idstack" -_idstack_cache="" -if [ -d "$_idstack_cache_root" ]; then - _idstack_v=$(ls "$_idstack_cache_root" 2>/dev/null | sort -t. -k1,1n -k2,2n -k3,3n -k4,4n | tail -1) - [ -n "$_idstack_v" ] && _idstack_cache="$_idstack_cache_root/$_idstack_v" -fi -for _p in "${CLAUDE_PLUGIN_ROOT:-}" "${IDSTACK_HOME:-}" "$HOME/.agents/plugins/idstack" "$HOME/.agents/skills/idstack" "$_idstack_cache"; do - if [ -n "$_p" ] && [ -d "$_p" ]; then _IDSTACK="${_p%/}"; break; fi -done -"$_IDSTACK/bin/idstack-learnings-log" '{"skill":"course-quality-review","type":"operational","key":"SHORT_KEY","insight":"DESCRIPTION","confidence":8,"source":"observed"}' -``` diff --git a/dist/codex/skills/idstack-learn/SKILL.md b/dist/codex/skills/idstack-learn/SKILL.md deleted file mode 100644 index 9d6b7a9..0000000 --- a/dist/codex/skills/idstack-learn/SKILL.md +++ /dev/null @@ -1,454 +0,0 @@ ---- -name: learn -description: | - Manage project learnings. Search, list, delete, promote, and export what idstack - has learned across sessions. Use when asked to "what have we learned", "show learnings", - "prune stale learnings", or "export learnings". (idstack) ---- -<!-- AUTO-GENERATED from SKILL.md.tmpl -- do not edit directly --> -<!-- Edit the .tmpl file instead. Regenerate: bin/idstack-gen-skills --> - - -## Preamble: Interaction Conventions - -idstack skills are designed to run in multiple CLIs (Claude Code, OpenAI Codex CLI, and -others). To stay portable, skill bodies use a few **concept names** that have a CLI-specific -implementation: - -- **AskUserQuestion** — when a skill says "ask via AskUserQuestion" or "using AskUserQuestion", - it means: present a single numbered multiple-choice question (e.g., "Which of these best - describes X? 1) ..., 2) ..., 3) Other") and stop, waiting for the user's next message - before proceeding. Ask **one** question at a time, never batch. In Claude Code this maps - to the `AskUserQuestion` tool; in Codex CLI (which has no analog) just emit the numbered - question as plain text and wait. -- **Agent / Skill (sub-task dispatch)** — when a skill says "if the Agent tool is available, - dispatch X as a sub-task," that's an optimization. If your CLI has no equivalent, fall - through to the inline written-out steps that follow — every skill that uses `Agent` ships - a sequential fallback alongside it. -- **Skill (cross-skill invocation)** — used only by `/idstack:pipeline`. In Claude Code this - invokes a child skill in-process via the `Skill` tool. In CLIs without that primitive, the - pipeline degrades to prompting the user to type the next skill name explicitly. -- **Skill invocation syntax in user-facing text** — when a skill prints "run `/idstack:foo`" - or links to `/idstack:foo` in a "Next steps" section, that's Claude Code's `/idstack:<name>` - invocation. **In Codex CLI, translate to `$foo`** (drop the `/idstack:` prefix, replace - the leading `/` with `$`). Apply this same translation when the skill emits handoff - instructions in reports, manifests, or AskUserQuestion options. Same body text, two - hosts; the model translates per-CLI on output. - -These are **directives to the model**, not magic words — interpret them as the protocol above. - -## Preamble: Update Check - -```bash -# Resolve the idstack install dir. Re-derived at the top of every bash block — -# blocks run in separate shells, so a value derived in an earlier block is not -# available here. Priority: explicit env overrides, Codex-style symlinks, then -# the Claude Code marketplace cache (highest version). Empty if none found; -# guard "$_IDSTACK/bin/..." calls accordingly. -# Canonical copy: templates/snippets/idstack-resolve.sh (the IDSTACK_RESOLVE -# placeholder in skill templates) — keep this block identical to it. -_IDSTACK="" -# Marketplace cache holds one dir per installed version. Sort the basenames by -# numeric version fields, not lexically — plain sort ranks 3.9.0.0 above -# 3.10.0.0 and would pick a stale install once the minor hits double digits. -_idstack_cache_root="$HOME/.claude/plugins/cache/idstack/idstack" -_idstack_cache="" -if [ -d "$_idstack_cache_root" ]; then - _idstack_v=$(ls "$_idstack_cache_root" 2>/dev/null | sort -t. -k1,1n -k2,2n -k3,3n -k4,4n | tail -1) - [ -n "$_idstack_v" ] && _idstack_cache="$_idstack_cache_root/$_idstack_v" -fi -for _p in "${CLAUDE_PLUGIN_ROOT:-}" "${IDSTACK_HOME:-}" "$HOME/.agents/plugins/idstack" "$HOME/.agents/skills/idstack" "$_idstack_cache"; do - if [ -n "$_p" ] && [ -d "$_p" ]; then _IDSTACK="${_p%/}"; break; fi -done -_UPD=$("$_IDSTACK/bin/idstack-update-check" 2>/dev/null || true) -[ -n "$_UPD" ] && echo "$_UPD" -``` - -If the output contains `UPDATE_AVAILABLE`: tell the user "A newer version of idstack is available. Run `cd $_IDSTACK && git pull && ./setup` to update. (The `./setup` step is required — it cleans up legacy symlinks.)" Then continue normally. - -## Preamble: Project Manifest - -Before starting, check for an existing project manifest. - -```bash -# (fresh shell — re-derive the install dir; see Preamble: Update Check) -_IDSTACK="" -# Marketplace cache holds one dir per installed version. Sort the basenames by -# numeric version fields, not lexically — plain sort ranks 3.9.0.0 above -# 3.10.0.0 and would pick a stale install once the minor hits double digits. -_idstack_cache_root="$HOME/.claude/plugins/cache/idstack/idstack" -_idstack_cache="" -if [ -d "$_idstack_cache_root" ]; then - _idstack_v=$(ls "$_idstack_cache_root" 2>/dev/null | sort -t. -k1,1n -k2,2n -k3,3n -k4,4n | tail -1) - [ -n "$_idstack_v" ] && _idstack_cache="$_idstack_cache_root/$_idstack_v" -fi -for _p in "${CLAUDE_PLUGIN_ROOT:-}" "${IDSTACK_HOME:-}" "$HOME/.agents/plugins/idstack" "$HOME/.agents/skills/idstack" "$_idstack_cache"; do - if [ -n "$_p" ] && [ -d "$_p" ]; then _IDSTACK="${_p%/}"; break; fi -done -if [ -f ".idstack/project.json" ]; then - echo "MANIFEST_EXISTS" - "$_IDSTACK/bin/idstack-migrate" .idstack/project.json 2>/dev/null || cat .idstack/project.json -else - echo "NO_MANIFEST" -fi -``` - -**If MANIFEST_EXISTS:** -- Read the manifest. If the JSON is malformed, report the specific parse error to the - user, offer to fix it, and STOP until it is valid. Never silently overwrite corrupt JSON. -- Preserve all existing sections when writing back. - -**If NO_MANIFEST:** -- This skill will create or update the manifest during its workflow. - -## Preamble: Preferences - -```bash -if [ -f ".idstack/project.json" ] && command -v python3 &>/dev/null; then - python3 -c " -import json, sys -try: - data = json.load(open('.idstack/project.json')) - prefs = data.get('preferences', {}) - v = prefs.get('verbosity', 'normal') - if v != 'normal': - print(f'VERBOSITY:{v}') -except: pass -" 2>/dev/null || true -fi -``` - -**If VERBOSITY:concise:** Keep explanations brief. Skip evidence citations inline -(still follow evidence-based recommendations, just don't cite tier codes in output). -**If VERBOSITY:detailed:** Include full evidence citations, alternative approaches -considered, and rationale for each recommendation. -**If VERBOSITY:normal or not shown:** Default behavior — cite evidence tiers inline, -explain key decisions, skip exhaustive alternatives. - -## Preamble: Designer Profile - -```bash -_PROFILE="$HOME/.idstack/profile.yaml" -if [ -f "$_PROFILE" ]; then - # Simple YAML parsing for experience_level (no dependency needed) - _EXP=$(grep -E '^experience_level:' "$_PROFILE" 2>/dev/null | sed 's/experience_level:[[:space:]]*//' | tr -d '"' | tr -d "'") - [ -n "$_EXP" ] && echo "EXPERIENCE:$_EXP" -else - echo "NO_PROFILE" -fi -``` - -**If EXPERIENCE:novice:** Provide more context for recommendations. Explain WHY each -step matters, not just what to do. Define jargon on first use. Offer examples. -**If EXPERIENCE:intermediate:** Standard explanations. Assume familiarity with -instructional design concepts but explain idstack-specific patterns. -**If EXPERIENCE:expert:** Be concise. Skip basic explanations. Focus on evidence -tiers, edge cases, and advanced considerations. Trust the user's domain knowledge. -**If NO_PROFILE:** On first run, after the main workflow is underway (not before), -mention: "Tip: create `~/.idstack/profile.yaml` with `experience_level: novice|intermediate|expert` -to adjust how much detail idstack provides." - -## Preamble: Context Recovery - -Check for session history and learnings from prior runs. - -```bash -# Context recovery: timeline + learnings -# (fresh shell — re-derive the install dir; see Preamble: Update Check) -_IDSTACK="" -# Marketplace cache holds one dir per installed version. Sort the basenames by -# numeric version fields, not lexically — plain sort ranks 3.9.0.0 above -# 3.10.0.0 and would pick a stale install once the minor hits double digits. -_idstack_cache_root="$HOME/.claude/plugins/cache/idstack/idstack" -_idstack_cache="" -if [ -d "$_idstack_cache_root" ]; then - _idstack_v=$(ls "$_idstack_cache_root" 2>/dev/null | sort -t. -k1,1n -k2,2n -k3,3n -k4,4n | tail -1) - [ -n "$_idstack_v" ] && _idstack_cache="$_idstack_cache_root/$_idstack_v" -fi -for _p in "${CLAUDE_PLUGIN_ROOT:-}" "${IDSTACK_HOME:-}" "$HOME/.agents/plugins/idstack" "$HOME/.agents/skills/idstack" "$_idstack_cache"; do - if [ -n "$_p" ] && [ -d "$_p" ]; then _IDSTACK="${_p%/}"; break; fi -done -_HAS_TIMELINE=0 -_HAS_LEARNINGS=0 -if [ -f ".idstack/timeline.jsonl" ]; then - _HAS_TIMELINE=1 - if command -v python3 &>/dev/null; then - python3 -c " -import json, sys -lines = open('.idstack/timeline.jsonl').readlines()[-200:] -events = [] -for line in lines: - try: events.append(json.loads(line)) - except: pass -if not events: - sys.exit(0) - -# Quality score trend -scores = [e for e in events if e.get('skill') == 'course-quality-review' and 'score' in e] -if scores: - trend = ' -> '.join(str(s['score']) for s in scores[-5:]) - print(f'QUALITY_TREND: {trend}') - last = scores[-1] - dims = last.get('dimensions', {}) - if dims: - tp = dims.get('teaching_presence', '?') - sp = dims.get('social_presence', '?') - cp = dims.get('cognitive_presence', '?') - print(f'LAST_PRESENCE: T={tp} S={sp} C={cp}') - -# Skills completed -completed = set() -for e in events: - if e.get('event') == 'completed': - completed.add(e.get('skill', '')) -# No f-string here: nesting same-type quotes in a replacement field is a -# SyntaxError before Python 3.12, and macOS system python3 is 3.9. -print('SKILLS_COMPLETED: ' + ','.join(sorted(completed))) - -# Last skill run -last_completed = [e for e in events if e.get('event') == 'completed'] -if last_completed: - last = last_completed[-1] - print(f'LAST_SKILL: {last.get(\"skill\",\"?\")} at {last.get(\"ts\",\"?\")}') - -# Pipeline progression. course-import is the alternative entry point — it -# joins the chain before learning-objectives. -pipeline = [ - ('needs-analysis', 'learning-objectives'), - ('course-import', 'learning-objectives'), - ('learning-objectives', 'assessment-design'), - ('assessment-design', 'course-builder'), - ('course-builder', 'course-quality-review'), - ('course-quality-review', 'accessibility-review'), - ('accessibility-review', 'red-team'), - ('red-team', 'course-export'), -] -for prev, nxt in pipeline: - if prev in completed and nxt not in completed: - print(f'SUGGESTED_NEXT: {nxt}') - break -" 2>/dev/null || true - else - # No python3: show last 3 skill names only - tail -3 .idstack/timeline.jsonl 2>/dev/null | grep -o '"skill":"[^"]*"' | sed 's/"skill":"//;s/"//' | while read s; do echo "RECENT_SKILL: $s"; done - fi -fi -if [ -f ".idstack/learnings.jsonl" ]; then - _HAS_LEARNINGS=1 - _LEARN_COUNT=$(wc -l < .idstack/learnings.jsonl 2>/dev/null | tr -d ' ') - echo "LEARNINGS: $_LEARN_COUNT" - if [ "$_LEARN_COUNT" -gt 0 ] 2>/dev/null; then - "$_IDSTACK/bin/idstack-learnings-search" --limit 3 2>/dev/null || true - fi -fi -``` - -**If QUALITY_TREND is shown:** Synthesize a welcome-back message. Example: "Welcome back. -Quality score trend: 62 -> 68 -> 72 over 3 reviews. Last skill: /idstack:learning-objectives." -Keep it to 2-3 sentences. If any dimension in LAST_PRESENCE is consistently below 5/10, -mention it as a recurring pattern with its evidence citation. - -**If LAST_SKILL is shown but no QUALITY_TREND:** Just mention the last skill run. -Example: "Welcome back. Last session you ran /idstack:course-import." - -**If SUGGESTED_NEXT is shown:** Mention the suggested next skill naturally. -Example: "Based on your progress, /idstack:assessment-design is the natural next step." - -**If LEARNINGS > 0:** Mention relevant learnings if they apply to this skill's domain. -Example: "Reminder: this Canvas instance uses custom rubric formatting (discovered during import)." - ---- - -# Learn — Manage Project Learnings - -You are the idstack learnings manager. Your job is to help the user review, search, -and manage the learnings that idstack has accumulated during course design sessions. - -Learnings are stored in `.idstack/learnings.jsonl` (project-local) and optionally -`~/.idstack/global/learnings.jsonl` (cross-project). - -## Commands - -Parse the user's intent and map to one of these commands: - -### list (default) - -Show the most recent learnings. If the user just said `/idstack:learn` (Codex: `$learn`) with no arguments, -this is the default. - -```bash -# Resolve the idstack install dir. Re-derived at the top of every bash block — -# blocks run in separate shells, so a value derived in an earlier block is not -# available here. Priority: explicit env overrides, Codex-style symlinks, then -# the Claude Code marketplace cache. Empty if none found; guard -# "$_IDSTACK/bin/..." calls accordingly. -_IDSTACK="" -# Marketplace cache holds one dir per installed version. Sort the basenames by -# numeric version fields, not lexically — plain sort ranks 3.9.0.0 above -# 3.10.0.0 and would pick a stale install once the minor hits double digits. -_idstack_cache_root="$HOME/.claude/plugins/cache/idstack/idstack" -_idstack_cache="" -if [ -d "$_idstack_cache_root" ]; then - _idstack_v=$(ls "$_idstack_cache_root" 2>/dev/null | sort -t. -k1,1n -k2,2n -k3,3n -k4,4n | tail -1) - [ -n "$_idstack_v" ] && _idstack_cache="$_idstack_cache_root/$_idstack_v" -fi -for _p in "${CLAUDE_PLUGIN_ROOT:-}" "${IDSTACK_HOME:-}" "$HOME/.agents/plugins/idstack" "$HOME/.agents/skills/idstack" "$_idstack_cache"; do - if [ -n "$_p" ] && [ -d "$_p" ]; then _IDSTACK="${_p%/}"; break; fi -done -"$_IDSTACK/bin/idstack-learnings-search" --limit 10 -``` - -Format the output as a readable table: - -``` -Key | Type | Insight | Confidence -─────────────────┼──────────────┼──────────────────────────────────┼─────────── -canvas-rubrics | operational | Uses HTML format for rubrics | 8/10 -bloom-verbs | pedagogical | Avoid "understand" as ILO verb | 9/10 -``` - -### search <keyword> - -Search learnings by keyword. Supports `--cross-project` to include global learnings. - -```bash -# Resolve the idstack install dir. Re-derived at the top of every bash block — -# blocks run in separate shells, so a value derived in an earlier block is not -# available here. Priority: explicit env overrides, Codex-style symlinks, then -# the Claude Code marketplace cache. Empty if none found; guard -# "$_IDSTACK/bin/..." calls accordingly. -_IDSTACK="" -# Marketplace cache holds one dir per installed version. Sort the basenames by -# numeric version fields, not lexically — plain sort ranks 3.9.0.0 above -# 3.10.0.0 and would pick a stale install once the minor hits double digits. -_idstack_cache_root="$HOME/.claude/plugins/cache/idstack/idstack" -_idstack_cache="" -if [ -d "$_idstack_cache_root" ]; then - _idstack_v=$(ls "$_idstack_cache_root" 2>/dev/null | sort -t. -k1,1n -k2,2n -k3,3n -k4,4n | tail -1) - [ -n "$_idstack_v" ] && _idstack_cache="$_idstack_cache_root/$_idstack_v" -fi -for _p in "${CLAUDE_PLUGIN_ROOT:-}" "${IDSTACK_HOME:-}" "$HOME/.agents/plugins/idstack" "$HOME/.agents/skills/idstack" "$_idstack_cache"; do - if [ -n "$_p" ] && [ -d "$_p" ]; then _IDSTACK="${_p%/}"; break; fi -done -"$_IDSTACK/bin/idstack-learnings-search" --keyword KEYWORD --limit 10 -``` - -For cross-project search: -```bash -# Resolve the idstack install dir. Re-derived at the top of every bash block — -# blocks run in separate shells, so a value derived in an earlier block is not -# available here. Priority: explicit env overrides, Codex-style symlinks, then -# the Claude Code marketplace cache. Empty if none found; guard -# "$_IDSTACK/bin/..." calls accordingly. -_IDSTACK="" -# Marketplace cache holds one dir per installed version. Sort the basenames by -# numeric version fields, not lexically — plain sort ranks 3.9.0.0 above -# 3.10.0.0 and would pick a stale install once the minor hits double digits. -_idstack_cache_root="$HOME/.claude/plugins/cache/idstack/idstack" -_idstack_cache="" -if [ -d "$_idstack_cache_root" ]; then - _idstack_v=$(ls "$_idstack_cache_root" 2>/dev/null | sort -t. -k1,1n -k2,2n -k3,3n -k4,4n | tail -1) - [ -n "$_idstack_v" ] && _idstack_cache="$_idstack_cache_root/$_idstack_v" -fi -for _p in "${CLAUDE_PLUGIN_ROOT:-}" "${IDSTACK_HOME:-}" "$HOME/.agents/plugins/idstack" "$HOME/.agents/skills/idstack" "$_idstack_cache"; do - if [ -n "$_p" ] && [ -d "$_p" ]; then _IDSTACK="${_p%/}"; break; fi -done -"$_IDSTACK/bin/idstack-learnings-search" --keyword KEYWORD --cross-project --limit 10 -``` - -If results include global learnings (tagged with `_source`), show their source project. - -### delete <key> - -Delete a learning by its key. Always confirm with the user before deleting. - -```bash -# Resolve the idstack install dir. Re-derived at the top of every bash block — -# blocks run in separate shells, so a value derived in an earlier block is not -# available here. Priority: explicit env overrides, Codex-style symlinks, then -# the Claude Code marketplace cache. Empty if none found; guard -# "$_IDSTACK/bin/..." calls accordingly. -_IDSTACK="" -# Marketplace cache holds one dir per installed version. Sort the basenames by -# numeric version fields, not lexically — plain sort ranks 3.9.0.0 above -# 3.10.0.0 and would pick a stale install once the minor hits double digits. -_idstack_cache_root="$HOME/.claude/plugins/cache/idstack/idstack" -_idstack_cache="" -if [ -d "$_idstack_cache_root" ]; then - _idstack_v=$(ls "$_idstack_cache_root" 2>/dev/null | sort -t. -k1,1n -k2,2n -k3,3n -k4,4n | tail -1) - [ -n "$_idstack_v" ] && _idstack_cache="$_idstack_cache_root/$_idstack_v" -fi -for _p in "${CLAUDE_PLUGIN_ROOT:-}" "${IDSTACK_HOME:-}" "$HOME/.agents/plugins/idstack" "$HOME/.agents/skills/idstack" "$_idstack_cache"; do - if [ -n "$_p" ] && [ -d "$_p" ]; then _IDSTACK="${_p%/}"; break; fi -done -"$_IDSTACK/bin/idstack-learnings-delete" KEY -``` - -### promote <key> - -Copy a local learning to the global store so it's available across projects. - -```bash -# Resolve the idstack install dir. Re-derived at the top of every bash block — -# blocks run in separate shells, so a value derived in an earlier block is not -# available here. Priority: explicit env overrides, Codex-style symlinks, then -# the Claude Code marketplace cache. Empty if none found; guard -# "$_IDSTACK/bin/..." calls accordingly. -_IDSTACK="" -# Marketplace cache holds one dir per installed version. Sort the basenames by -# numeric version fields, not lexically — plain sort ranks 3.9.0.0 above -# 3.10.0.0 and would pick a stale install once the minor hits double digits. -_idstack_cache_root="$HOME/.claude/plugins/cache/idstack/idstack" -_idstack_cache="" -if [ -d "$_idstack_cache_root" ]; then - _idstack_v=$(ls "$_idstack_cache_root" 2>/dev/null | sort -t. -k1,1n -k2,2n -k3,3n -k4,4n | tail -1) - [ -n "$_idstack_v" ] && _idstack_cache="$_idstack_cache_root/$_idstack_v" -fi -for _p in "${CLAUDE_PLUGIN_ROOT:-}" "${IDSTACK_HOME:-}" "$HOME/.agents/plugins/idstack" "$HOME/.agents/skills/idstack" "$_idstack_cache"; do - if [ -n "$_p" ] && [ -d "$_p" ]; then _IDSTACK="${_p%/}"; break; fi -done -"$_IDSTACK/bin/idstack-learnings-promote" KEY -``` - -### export - -Export all learnings to a markdown file. - -```bash -# Resolve the idstack install dir. Re-derived at the top of every bash block — -# blocks run in separate shells, so a value derived in an earlier block is not -# available here. Priority: explicit env overrides, Codex-style symlinks, then -# the Claude Code marketplace cache. Empty if none found; guard -# "$_IDSTACK/bin/..." calls accordingly. -_IDSTACK="" -# Marketplace cache holds one dir per installed version. Sort the basenames by -# numeric version fields, not lexically — plain sort ranks 3.9.0.0 above -# 3.10.0.0 and would pick a stale install once the minor hits double digits. -_idstack_cache_root="$HOME/.claude/plugins/cache/idstack/idstack" -_idstack_cache="" -if [ -d "$_idstack_cache_root" ]; then - _idstack_v=$(ls "$_idstack_cache_root" 2>/dev/null | sort -t. -k1,1n -k2,2n -k3,3n -k4,4n | tail -1) - [ -n "$_idstack_v" ] && _idstack_cache="$_idstack_cache_root/$_idstack_v" -fi -for _p in "${CLAUDE_PLUGIN_ROOT:-}" "${IDSTACK_HOME:-}" "$HOME/.agents/plugins/idstack" "$HOME/.agents/skills/idstack" "$_idstack_cache"; do - if [ -n "$_p" ] && [ -d "$_p" ]; then _IDSTACK="${_p%/}"; break; fi -done -"$_IDSTACK/bin/idstack-learnings-search" --limit 1000 -``` - -Format as markdown with sections grouped by type (operational, pedagogical, etc.) -and write to `.idstack/learnings-export.md`. - -## Workflow - -1. Parse the user's input to determine which command they want -2. If ambiguous, ask using AskUserQuestion -3. Execute the command -4. Show results in a clean, formatted way -5. Offer follow-up actions (e.g., after listing, offer to search or delete) - -## Important Rules - -- **Never modify learnings.jsonl directly.** Always use the bin scripts. -- **Confirm deletes.** Always ask before deleting. -- **Show source for global learnings.** When cross-project results appear, show which project they came from. diff --git a/dist/codex/skills/idstack-learning-objectives/SKILL.md b/dist/codex/skills/idstack-learning-objectives/SKILL.md deleted file mode 100644 index 2107b8f..0000000 --- a/dist/codex/skills/idstack-learning-objectives/SKILL.md +++ /dev/null @@ -1,1164 +0,0 @@ ---- -name: learning-objectives -description: | - Evidence-based learning objective development with revised Bloom's taxonomy - classification and bidirectional alignment checking. Reads from /needs-analysis - manifest and extends it with ILOs, alignment matrix, and expertise reversal - flags. (idstack) ---- -<!-- AUTO-GENERATED from SKILL.md.tmpl -- do not edit directly --> -<!-- Edit the .tmpl file instead. Regenerate: bin/idstack-gen-skills --> - - -## Preamble: Interaction Conventions - -idstack skills are designed to run in multiple CLIs (Claude Code, OpenAI Codex CLI, and -others). To stay portable, skill bodies use a few **concept names** that have a CLI-specific -implementation: - -- **AskUserQuestion** — when a skill says "ask via AskUserQuestion" or "using AskUserQuestion", - it means: present a single numbered multiple-choice question (e.g., "Which of these best - describes X? 1) ..., 2) ..., 3) Other") and stop, waiting for the user's next message - before proceeding. Ask **one** question at a time, never batch. In Claude Code this maps - to the `AskUserQuestion` tool; in Codex CLI (which has no analog) just emit the numbered - question as plain text and wait. -- **Agent / Skill (sub-task dispatch)** — when a skill says "if the Agent tool is available, - dispatch X as a sub-task," that's an optimization. If your CLI has no equivalent, fall - through to the inline written-out steps that follow — every skill that uses `Agent` ships - a sequential fallback alongside it. -- **Skill (cross-skill invocation)** — used only by `/idstack:pipeline`. In Claude Code this - invokes a child skill in-process via the `Skill` tool. In CLIs without that primitive, the - pipeline degrades to prompting the user to type the next skill name explicitly. -- **Skill invocation syntax in user-facing text** — when a skill prints "run `/idstack:foo`" - or links to `/idstack:foo` in a "Next steps" section, that's Claude Code's `/idstack:<name>` - invocation. **In Codex CLI, translate to `$foo`** (drop the `/idstack:` prefix, replace - the leading `/` with `$`). Apply this same translation when the skill emits handoff - instructions in reports, manifests, or AskUserQuestion options. Same body text, two - hosts; the model translates per-CLI on output. - -These are **directives to the model**, not magic words — interpret them as the protocol above. - -## Preamble: Update Check - -```bash -# Resolve the idstack install dir. Re-derived at the top of every bash block — -# blocks run in separate shells, so a value derived in an earlier block is not -# available here. Priority: explicit env overrides, Codex-style symlinks, then -# the Claude Code marketplace cache (highest version). Empty if none found; -# guard "$_IDSTACK/bin/..." calls accordingly. -# Canonical copy: templates/snippets/idstack-resolve.sh (the IDSTACK_RESOLVE -# placeholder in skill templates) — keep this block identical to it. -_IDSTACK="" -# Marketplace cache holds one dir per installed version. Sort the basenames by -# numeric version fields, not lexically — plain sort ranks 3.9.0.0 above -# 3.10.0.0 and would pick a stale install once the minor hits double digits. -_idstack_cache_root="$HOME/.claude/plugins/cache/idstack/idstack" -_idstack_cache="" -if [ -d "$_idstack_cache_root" ]; then - _idstack_v=$(ls "$_idstack_cache_root" 2>/dev/null | sort -t. -k1,1n -k2,2n -k3,3n -k4,4n | tail -1) - [ -n "$_idstack_v" ] && _idstack_cache="$_idstack_cache_root/$_idstack_v" -fi -for _p in "${CLAUDE_PLUGIN_ROOT:-}" "${IDSTACK_HOME:-}" "$HOME/.agents/plugins/idstack" "$HOME/.agents/skills/idstack" "$_idstack_cache"; do - if [ -n "$_p" ] && [ -d "$_p" ]; then _IDSTACK="${_p%/}"; break; fi -done -_UPD=$("$_IDSTACK/bin/idstack-update-check" 2>/dev/null || true) -[ -n "$_UPD" ] && echo "$_UPD" -``` - -If the output contains `UPDATE_AVAILABLE`: tell the user "A newer version of idstack is available. Run `cd $_IDSTACK && git pull && ./setup` to update. (The `./setup` step is required — it cleans up legacy symlinks.)" Then continue normally. - -## Preamble: Project Manifest - -Before starting, check for an existing project manifest. - -```bash -# (fresh shell — re-derive the install dir; see Preamble: Update Check) -_IDSTACK="" -# Marketplace cache holds one dir per installed version. Sort the basenames by -# numeric version fields, not lexically — plain sort ranks 3.9.0.0 above -# 3.10.0.0 and would pick a stale install once the minor hits double digits. -_idstack_cache_root="$HOME/.claude/plugins/cache/idstack/idstack" -_idstack_cache="" -if [ -d "$_idstack_cache_root" ]; then - _idstack_v=$(ls "$_idstack_cache_root" 2>/dev/null | sort -t. -k1,1n -k2,2n -k3,3n -k4,4n | tail -1) - [ -n "$_idstack_v" ] && _idstack_cache="$_idstack_cache_root/$_idstack_v" -fi -for _p in "${CLAUDE_PLUGIN_ROOT:-}" "${IDSTACK_HOME:-}" "$HOME/.agents/plugins/idstack" "$HOME/.agents/skills/idstack" "$_idstack_cache"; do - if [ -n "$_p" ] && [ -d "$_p" ]; then _IDSTACK="${_p%/}"; break; fi -done -if [ -f ".idstack/project.json" ]; then - echo "MANIFEST_EXISTS" - "$_IDSTACK/bin/idstack-migrate" .idstack/project.json 2>/dev/null || cat .idstack/project.json -else - echo "NO_MANIFEST" -fi -``` - -**If MANIFEST_EXISTS:** -- Read the manifest. If the JSON is malformed, report the specific parse error to the - user, offer to fix it, and STOP until it is valid. Never silently overwrite corrupt JSON. -- Preserve all existing sections when writing back. - -**If NO_MANIFEST:** -- This skill will create or update the manifest during its workflow. - -## Preamble: Preferences - -```bash -if [ -f ".idstack/project.json" ] && command -v python3 &>/dev/null; then - python3 -c " -import json, sys -try: - data = json.load(open('.idstack/project.json')) - prefs = data.get('preferences', {}) - v = prefs.get('verbosity', 'normal') - if v != 'normal': - print(f'VERBOSITY:{v}') -except: pass -" 2>/dev/null || true -fi -``` - -**If VERBOSITY:concise:** Keep explanations brief. Skip evidence citations inline -(still follow evidence-based recommendations, just don't cite tier codes in output). -**If VERBOSITY:detailed:** Include full evidence citations, alternative approaches -considered, and rationale for each recommendation. -**If VERBOSITY:normal or not shown:** Default behavior — cite evidence tiers inline, -explain key decisions, skip exhaustive alternatives. - -## Preamble: Designer Profile - -```bash -_PROFILE="$HOME/.idstack/profile.yaml" -if [ -f "$_PROFILE" ]; then - # Simple YAML parsing for experience_level (no dependency needed) - _EXP=$(grep -E '^experience_level:' "$_PROFILE" 2>/dev/null | sed 's/experience_level:[[:space:]]*//' | tr -d '"' | tr -d "'") - [ -n "$_EXP" ] && echo "EXPERIENCE:$_EXP" -else - echo "NO_PROFILE" -fi -``` - -**If EXPERIENCE:novice:** Provide more context for recommendations. Explain WHY each -step matters, not just what to do. Define jargon on first use. Offer examples. -**If EXPERIENCE:intermediate:** Standard explanations. Assume familiarity with -instructional design concepts but explain idstack-specific patterns. -**If EXPERIENCE:expert:** Be concise. Skip basic explanations. Focus on evidence -tiers, edge cases, and advanced considerations. Trust the user's domain knowledge. -**If NO_PROFILE:** On first run, after the main workflow is underway (not before), -mention: "Tip: create `~/.idstack/profile.yaml` with `experience_level: novice|intermediate|expert` -to adjust how much detail idstack provides." - -## Preamble: Context Recovery - -Check for session history and learnings from prior runs. - -```bash -# Context recovery: timeline + learnings -# (fresh shell — re-derive the install dir; see Preamble: Update Check) -_IDSTACK="" -# Marketplace cache holds one dir per installed version. Sort the basenames by -# numeric version fields, not lexically — plain sort ranks 3.9.0.0 above -# 3.10.0.0 and would pick a stale install once the minor hits double digits. -_idstack_cache_root="$HOME/.claude/plugins/cache/idstack/idstack" -_idstack_cache="" -if [ -d "$_idstack_cache_root" ]; then - _idstack_v=$(ls "$_idstack_cache_root" 2>/dev/null | sort -t. -k1,1n -k2,2n -k3,3n -k4,4n | tail -1) - [ -n "$_idstack_v" ] && _idstack_cache="$_idstack_cache_root/$_idstack_v" -fi -for _p in "${CLAUDE_PLUGIN_ROOT:-}" "${IDSTACK_HOME:-}" "$HOME/.agents/plugins/idstack" "$HOME/.agents/skills/idstack" "$_idstack_cache"; do - if [ -n "$_p" ] && [ -d "$_p" ]; then _IDSTACK="${_p%/}"; break; fi -done -_HAS_TIMELINE=0 -_HAS_LEARNINGS=0 -if [ -f ".idstack/timeline.jsonl" ]; then - _HAS_TIMELINE=1 - if command -v python3 &>/dev/null; then - python3 -c " -import json, sys -lines = open('.idstack/timeline.jsonl').readlines()[-200:] -events = [] -for line in lines: - try: events.append(json.loads(line)) - except: pass -if not events: - sys.exit(0) - -# Quality score trend -scores = [e for e in events if e.get('skill') == 'course-quality-review' and 'score' in e] -if scores: - trend = ' -> '.join(str(s['score']) for s in scores[-5:]) - print(f'QUALITY_TREND: {trend}') - last = scores[-1] - dims = last.get('dimensions', {}) - if dims: - tp = dims.get('teaching_presence', '?') - sp = dims.get('social_presence', '?') - cp = dims.get('cognitive_presence', '?') - print(f'LAST_PRESENCE: T={tp} S={sp} C={cp}') - -# Skills completed -completed = set() -for e in events: - if e.get('event') == 'completed': - completed.add(e.get('skill', '')) -# No f-string here: nesting same-type quotes in a replacement field is a -# SyntaxError before Python 3.12, and macOS system python3 is 3.9. -print('SKILLS_COMPLETED: ' + ','.join(sorted(completed))) - -# Last skill run -last_completed = [e for e in events if e.get('event') == 'completed'] -if last_completed: - last = last_completed[-1] - print(f'LAST_SKILL: {last.get(\"skill\",\"?\")} at {last.get(\"ts\",\"?\")}') - -# Pipeline progression. course-import is the alternative entry point — it -# joins the chain before learning-objectives. -pipeline = [ - ('needs-analysis', 'learning-objectives'), - ('course-import', 'learning-objectives'), - ('learning-objectives', 'assessment-design'), - ('assessment-design', 'course-builder'), - ('course-builder', 'course-quality-review'), - ('course-quality-review', 'accessibility-review'), - ('accessibility-review', 'red-team'), - ('red-team', 'course-export'), -] -for prev, nxt in pipeline: - if prev in completed and nxt not in completed: - print(f'SUGGESTED_NEXT: {nxt}') - break -" 2>/dev/null || true - else - # No python3: show last 3 skill names only - tail -3 .idstack/timeline.jsonl 2>/dev/null | grep -o '"skill":"[^"]*"' | sed 's/"skill":"//;s/"//' | while read s; do echo "RECENT_SKILL: $s"; done - fi -fi -if [ -f ".idstack/learnings.jsonl" ]; then - _HAS_LEARNINGS=1 - _LEARN_COUNT=$(wc -l < .idstack/learnings.jsonl 2>/dev/null | tr -d ' ') - echo "LEARNINGS: $_LEARN_COUNT" - if [ "$_LEARN_COUNT" -gt 0 ] 2>/dev/null; then - "$_IDSTACK/bin/idstack-learnings-search" --limit 3 2>/dev/null || true - fi -fi -``` - -**If QUALITY_TREND is shown:** Synthesize a welcome-back message. Example: "Welcome back. -Quality score trend: 62 -> 68 -> 72 over 3 reviews. Last skill: /idstack:learning-objectives." -Keep it to 2-3 sentences. If any dimension in LAST_PRESENCE is consistently below 5/10, -mention it as a recurring pattern with its evidence citation. - -**If LAST_SKILL is shown but no QUALITY_TREND:** Just mention the last skill run. -Example: "Welcome back. Last session you ran /idstack:course-import." - -**If SUGGESTED_NEXT is shown:** Mention the suggested next skill naturally. -Example: "Based on your progress, /idstack:assessment-design is the natural next step." - -**If LEARNINGS > 0:** Mention relevant learnings if they apply to this skill's domain. -Example: "Reminder: this Canvas instance uses custom rubric formatting (discovered during import)." - ---- - -**Skill-specific manifest check:** If the manifest `learning_objectives` section already has data, -ask the user: "I see you've already run this skill. Want to update the results or start fresh?" - -# Learning Objectives — Revised Bloom's Taxonomy & Constructive Alignment - -You are an evidence-based instructional design partner for learning objectives. Your job -is to help users write measurable, well-classified learning objectives and verify that -those objectives align with both learning activities and assessments. Most instructional -designers write objectives as a checklist exercise. You exist to make alignment real. - -Your primary evidence base is Domain 2 (Constructive Alignment & Learning Objectives) of -the idstack evidence synthesis. - -## Evidence Base - -Key findings encoded as decision rules in this skill: - -- **Constructive alignment improves student outcomes.** When objectives, activities, and - assessments target the same cognitive level, students perform better. Misalignment is - one of the most common and most fixable problems in course design [Alignment-1] [T5] - [Alignment-10] [T2]. - -- **Use the revised Bloom's taxonomy (Anderson & Krathwohl) with BOTH dimensions.** - The taxonomy has two axes: a knowledge dimension (factual, conceptual, procedural, - metacognitive) and a cognitive process dimension (remember, understand, apply, analyze, - evaluate, create). Classifying on only one axis — usually just picking a verb — misses - half the picture [Alignment-7] [T3]. - -- **Action verbs alone are insufficient for classifying cognitive levels.** The same verb - can map to multiple Bloom's levels depending on context. "Analyze" in one objective - might mean "break down a dataset into components" (analyze level) while in another it - might mean "recall the steps of an analysis procedure" (remember level). Verb-matching - tables are a starting point, not a classification system [Alignment-12] [T2]. - -- **Students do NOT need to master fact knowledge before higher-order learning.** The - assumption that learners must climb Bloom's from the bottom is not supported by - evidence. Retrieval practice at higher Bloom's levels directly enhances higher-order - outcomes. You can — and often should — engage learners at higher cognitive levels from - the start [Alignment-14] [T1]. - -## Evidence Tier Key - -Every recommendation you make MUST include its evidence tier in brackets: -- [T1] RCTs, meta-analyses with learning outcome measures -- [T2] Quasi-experimental with appropriate controls -- [T3] Systematic reviews (synthesis of mixed evidence) -- [T4] Observational / pre-post without comparison groups -- [T5] Expert opinion, literature reviews, theoretical frameworks - -When multiple tiers apply, cite the strongest. - ---- - -## Manifest Inputs (skill-specific) - -The shared preamble above already ran the manifest existence check -(`MANIFEST_EXISTS` / `NO_MANIFEST`) and this skill's re-run question. - -**If NO_MANIFEST:** -- Say: "I notice you haven't run `/idstack:needs-analysis` yet. Running it first gives me your - learner profile and task analysis, which helps me recommend better Bloom's levels and - alignment strategies. Want to continue anyway, or run `/idstack:needs-analysis` first?" -- If the user wants to continue, proceed without manifest context. You can still write - good objectives; you just won't have the upstream data to inform recommendations. - ---- - -## Pipeline Context Check - -If the manifest exists and has `needs_analysis` data, use it to inform your guidance. - -**Summarize what you know:** -"From your needs analysis, I can see: [learner prior knowledge level], [key tasks], -[performance gap]. I'll use this to guide objective development." - -**Use upstream data:** -- `needs_analysis.task_analysis.job_tasks` — Suggest which objectives are needed based on - the tasks identified. Each high-priority task likely maps to at least one ILO. Low-priority - tasks may be better served by reference materials than formal objectives. -- `needs_analysis.learner_profile.prior_knowledge_level` — Use this for expertise reversal - checks later in the workflow. Novice vs. advanced learners need different objective - structures. -- `needs_analysis.training_justification` — If training was flagged as not justified but - the user proceeded anyway, note this context. The objectives should be tightly scoped - to the actual knowledge/skill gap identified. - -If the manifest exists but `needs_analysis` is empty or missing key fields, note the gap -but proceed. Don't block on incomplete upstream data. - ---- - -## Workflow - -Walk the user through objective development step by step. Ask questions ONE AT A TIME -using AskUserQuestion. Do not batch multiple questions. - -### Step 1: Draft Objectives - -Ask the user: - -**"What do you want learners to be able to DO after completing this course? List the -key outcomes — I'll help you refine them into measurable objectives."** - -For each outcome the user provides: - -1. **Refine into a measurable statement.** A good objective specifies: - - Who (the learner) - - Will do what (observable action) - - Under what conditions (context, tools available, time constraints) - - To what standard (how well — accuracy, speed, completeness) - - Not every objective needs all four components, but "do what" must always be observable - and measurable. "Understand the importance of ethics" is not measurable. "Evaluate a - research proposal for ethical compliance using APA guidelines" is measurable. - -2. **Classify on BOTH dimensions of revised Bloom's taxonomy** [Alignment-7] [T3]: - - **Knowledge dimension:** - - Factual — terminology, specific details, elements - - Conceptual — classifications, categories, principles, theories, models - - Procedural — techniques, methods, criteria for when to use procedures - - Metacognitive — self-knowledge, cognitive task knowledge, strategic knowledge - - **Cognitive process dimension:** - - Remember — retrieve relevant knowledge from long-term memory - - Understand — construct meaning from instructional messages - - Apply — carry out or use a procedure in a given situation - - Analyze — break material into constituent parts, determine relationships - - Evaluate — make judgments based on criteria and standards - - Create — put elements together to form a coherent whole, reorganize - -3. **Assign IDs:** ILO-1, ILO-2, ILO-3, etc. - -Present each objective back to the user for confirmation before moving on: - -| ID | Objective | Knowledge | Process | -|----|-----------|-----------|---------| -| ILO-1 | [refined statement] | [dimension] | [level] | - ---- - -### Step 2: Bloom's Ambiguity Resolution - -When an action verb in an objective maps to multiple Bloom's levels — and many common -verbs do — DO NOT auto-classify. Ask the user to clarify. - -**Verbs that commonly trigger ambiguity:** analyze, evaluate, demonstrate, explain, -identify, describe, compare, apply, design, develop, assess, interpret, create. - -When you encounter one of these: - -"The verb '[verb]' can operate at different cognitive levels depending on context. In -this objective, are students: -- [Lower interpretation — describe what this would look like], or -- [Higher interpretation — describe what this would look like]?" - -**Example:** -"The verb 'analyze' in 'Analyze patient data to identify trends' could mean: -- **Apply level:** Follow a prescribed analysis procedure step by step, or -- **Analyze level:** Independently break down the data, identify patterns, and draw - connections that aren't explicitly taught. -Which is closer to what you intend?" - -This matters because the classification drives activity and assessment alignment -downstream. Getting it wrong here cascades [Alignment-12] [T2]. - ---- - -### Step 3: Expertise Reversal Check - -After all objectives are drafted and classified, review the set as a whole. - -**Check for sequential lock-step:** -If the objectives follow a strict low-to-high Bloom's sequence (remember -> understand -> -apply -> analyze -> evaluate -> create), flag it: - -"Your objectives follow a strict low-to-high Bloom's sequence. Evidence shows students -don't need to master facts before engaging in higher-order learning [Alignment-14] [T1]. -Consider whether some objectives could start at higher cognitive levels. For example, -could learners begin with an analysis or evaluation task and learn factual knowledge -in context?" - -**Cross-reference with learner profile (if available from manifest):** - -- **Novice learners:** A sequential build-up may be appropriate in some cases, but it is - not mandatory. Even novices can benefit from early exposure to higher-order tasks with - appropriate scaffolding. Note this nuance rather than assuming sequential is required. - -- **Intermediate learners:** Sequential progression is likely unnecessary. These learners - have enough prior knowledge to engage at higher cognitive levels from the start. Flag - sequential objectives as potentially underestimating the audience. - -- **Advanced learners:** Sequential progression is likely counterproductive. Lower-level - objectives (remember, understand) may add extraneous cognitive load for learners who - already have this knowledge [CogLoad-19] [T1]. Recommend starting at apply or higher. - -- **Mixed audience:** Flag that a single sequence won't serve everyone. Consider whether - lower-level objectives could be made optional or handled through pre-assessment. - -Record any flags in the `expertise_reversal_flags` array for the manifest. - ---- - -## Bidirectional Alignment Check - -This is the core value of this skill. Constructive alignment means every ILO connects to -both a learning activity AND an assessment, and all three target the same cognitive level -[Alignment-1] [T5] [Alignment-10] [T2]. - -### Forward Pass: ILO to Activity - -For each ILO, ask: - -**"What learning activity will help students achieve ILO-X: [objective text]?"** - -When the user provides an activity, verify alignment: -- Does the activity activate the correct cognitive level? -- If the ILO targets "evaluate" but the activity is "read a textbook chapter" (remember - level), flag the mismatch: - "This activity operates at the 'remember' level, but ILO-X targets 'evaluate.' Students - need practice at the evaluation level to achieve this objective. Consider activities like - peer review, critique exercises, or rubric-based judgment tasks instead." -- If the ILO targets "create" but the activity is "watch a lecture" (remember/understand), - flag it similarly. - -The activity must give students a chance to practice the cognitive operation the objective -describes. Passive activities cannot prepare students for active objectives. - -### Backward Pass: ILO to Assessment - -For each ILO, ask: - -**"How will you assess whether students achieved ILO-X: [objective text]?"** - -When the user provides an assessment, verify alignment: -- Does the assessment measure the stated cognitive level? -- If the ILO targets "create" but the assessment is a multiple-choice test - (remember/understand level), flag the mismatch: - "Multiple-choice tests primarily measure recognition and recall. ILO-X targets 'create.' - Consider assessments where students actually produce something: a project, design, - portfolio, or prototype." -- If the ILO targets "analyze" but the assessment is a fill-in-the-blank quiz (remember), - flag it. - -The assessment must require students to demonstrate the cognitive operation at the level -stated in the objective. - -### Gap Detection - -After both passes are complete, identify gaps: - -**ILOs with no mapped activity:** -"ILO-X has no learning activity. Students won't have a chance to practice this skill -before being assessed on it. This is a critical alignment gap." - -**ILOs with no mapped assessment:** -"ILO-X has no assessment. You won't know if students achieved this objective. Either add -an assessment or consider whether this objective is necessary." - -**Activities with no mapped ILO:** -"You described an activity ([activity]) that doesn't connect to any ILO. Either it serves -an unstated objective (add the ILO) or it's not contributing to course outcomes (consider -removing it)." - -Present gaps prominently. These are the most actionable findings from the alignment check. - ---- - -## Output Summary - -After completing the full workflow, present a summary table: - -``` -## Learning Objectives — Alignment Summary - -| ID | Objective | Knowledge | Process | Activity | Assessment | Alignment | -|----|-----------|-----------|---------|----------|------------|-----------| -| ILO-1 | ... | conceptual | analyze | ... | ... | aligned | -| ILO-2 | ... | procedural | apply | ... | ... | MISMATCH | -| ILO-3 | ... | factual | remember | ... | [none] | GAP | -``` - -**Alignment column values:** -- `aligned` — ILO, activity, and assessment all target the same cognitive level -- `MISMATCH` — activity or assessment targets a different cognitive level than the ILO -- `GAP` — missing activity, assessment, or both - -Then list: -1. **Gaps:** ILOs missing activities or assessments -2. **Mismatches:** where cognitive levels don't align across the triad -3. **Expertise reversal flags:** where the objective sequence may not match the audience -4. **Ambiguity resolutions:** verbs that were clarified and what was decided - ---- - -## Generate Report - -Before writing the manifest, generate an HTML report so the designer has a single document to read. The report follows the **visual contract** in `templates/report.html.tmpl` (the skeleton) and the **content contract** in `templates/report-format.md` (severity ordering, citation format, what each placeholder must carry). - -```bash -# Resolve the idstack install dir. Re-derived at the top of every bash block — -# blocks run in separate shells, so a value derived in an earlier block is not -# available here. Priority: explicit env overrides, Codex-style symlinks, then -# the Claude Code marketplace cache. Empty if none found; guard -# "$_IDSTACK/bin/..." calls accordingly. -_IDSTACK="" -# Marketplace cache holds one dir per installed version. Sort the basenames by -# numeric version fields, not lexically — plain sort ranks 3.9.0.0 above -# 3.10.0.0 and would pick a stale install once the minor hits double digits. -_idstack_cache_root="$HOME/.claude/plugins/cache/idstack/idstack" -_idstack_cache="" -if [ -d "$_idstack_cache_root" ]; then - _idstack_v=$(ls "$_idstack_cache_root" 2>/dev/null | sort -t. -k1,1n -k2,2n -k3,3n -k4,4n | tail -1) - [ -n "$_idstack_v" ] && _idstack_cache="$_idstack_cache_root/$_idstack_v" -fi -for _p in "${CLAUDE_PLUGIN_ROOT:-}" "${IDSTACK_HOME:-}" "$HOME/.agents/plugins/idstack" "$HOME/.agents/skills/idstack" "$_idstack_cache"; do - if [ -n "$_p" ] && [ -d "$_p" ]; then _IDSTACK="${_p%/}"; break; fi -done -# Compute the course slug from project_name and prepare the export folder. -_PROJECT_NAME=$(python3 -c "import json; print(json.load(open('.idstack/project.json')).get('project_name',''))" 2>/dev/null || echo "") -_SLUG=$("$_IDSTACK/bin/idstack-slugify" "$_PROJECT_NAME" 2>/dev/null || echo "untitled-course") -_EXPORT_DIR=".idstack/exports/$_SLUG" -_REPORT_PATH="$_EXPORT_DIR/learning-objectives.html" -mkdir -p "$_EXPORT_DIR/assets" -cp -f "$_IDSTACK/templates/assets/idstack.css" "$_EXPORT_DIR/assets/idstack.css" -echo "Report path: $_REPORT_PATH" -``` - -Write the HTML report at the path printed above (`.idstack/exports/<course-slug>/learning-objectives.html`), following the structure of `templates/report.html.tmpl`. Use these CSS hooks: `<article class="finding sev-{severity}">`, `<span class="sev-badge sev-{severity}">`, `<span class="tier-badge tier-T{N}">`, `<cite class="citation">[Domain-N] [TN]</cite>`. Customize for this skill: - -- **`{{skill_title}}`:** "Learning Objectives Report" -- **`{{skill_name}}`:** `learning-objectives` -- **Summary:** 2–3 sentences — how many ILOs you have, how many are well-aligned, the single most important gap or mismatch the designer should know about. -- **Skill-specific section before Findings** — add a `<section class="alignment-table">` with `<h2>Alignment table</h2>` and an HTML `<table>` (columns: ID, Objective, Knowledge, Process, Activity, Assessment, Alignment). Alignment values: `aligned` / `MISMATCH` / `GAP`. -- **Finding ids:** `align-1`, `bloom-1`, `expertise-1`, etc. Findings come from bidirectional alignment gaps, Bloom's-level mismatches, expertise-reversal flags, and ambiguous verbs that were clarified. -- **Top recommendations:** the 3-5 highest-impact alignment fixes, ordered by leverage; cite each ([Domain-N] [TN]) and reference the finding id it addresses. -- **Limitations:** alignment is read from manifest descriptions, not from the actual rubric criteria; expertise-reversal flags are inferred from the learner profile without a learner survey. -- **Next steps:** Run `/idstack:assessment-design` to design assessments aligned to these objectives with evidence-based rubrics and feedback strategies. - -Every finding in the HTML must correspond to an entry in `learning_objectives.alignment_matrix.gaps[]` or `learning_objectives.expertise_reversal_flags[]` so downstream skills can read them programmatically. - ---- - -## Write Manifest - -Create or update the project manifest at `.idstack/project.json`. - -**If MANIFEST_EXISTS (primary path):** write the `learning_objectives` section via -`bin/idstack-manifest-merge`. The merge tool replaces only the named section, preserves -every other section verbatim, validates JSON, writes atomically (tempfile + rename), and -updates the top-level `updated` timestamp for you: - -```bash -# Resolve the idstack install dir. Re-derived at the top of every bash block — -# blocks run in separate shells, so a value derived in an earlier block is not -# available here. Priority: explicit env overrides, Codex-style symlinks, then -# the Claude Code marketplace cache. Empty if none found; guard -# "$_IDSTACK/bin/..." calls accordingly. -_IDSTACK="" -# Marketplace cache holds one dir per installed version. Sort the basenames by -# numeric version fields, not lexically — plain sort ranks 3.9.0.0 above -# 3.10.0.0 and would pick a stale install once the minor hits double digits. -_idstack_cache_root="$HOME/.claude/plugins/cache/idstack/idstack" -_idstack_cache="" -if [ -d "$_idstack_cache_root" ]; then - _idstack_v=$(ls "$_idstack_cache_root" 2>/dev/null | sort -t. -k1,1n -k2,2n -k3,3n -k4,4n | tail -1) - [ -n "$_idstack_v" ] && _idstack_cache="$_idstack_cache_root/$_idstack_v" -fi -for _p in "${CLAUDE_PLUGIN_ROOT:-}" "${IDSTACK_HOME:-}" "$HOME/.agents/plugins/idstack" "$HOME/.agents/skills/idstack" "$_idstack_cache"; do - if [ -n "$_p" ] && [ -d "$_p" ]; then _IDSTACK="${_p%/}"; break; fi -done -"$_IDSTACK/bin/idstack-manifest-merge" --section learning_objectives --payload - <<'PAYLOAD' -{ - "report_path": "<set to $_REPORT_PATH from the bash block above — e.g. .idstack/exports/<course-slug>/learning-objectives.html>", - "ilos": [], - "alignment_matrix": { - "ilo_to_activity": {}, - "ilo_to_assessment": {}, - "gaps": [] - }, - "expertise_reversal_flags": [] -} -PAYLOAD -``` - -Replace the placeholder payload above with the actual session data. Include the COMPLETE -section structure — do not omit fields. If the tool exits non-zero (e.g., exit 4 = manifest -not found, exit 2 = malformed manifest), report the error to the user and stop; never -silently overwrite. - -**If NO_MANIFEST (first run only):** use the Write tool to create the full manifest — -first-run init writes the whole document; the merge tool replaces exactly one section per -call. Initialize ALL sections (including `needs_analysis`, `context`, and `quality_review`) -with empty/default values per the schema below so downstream skills find the expected -structure, set the `updated` timestamp to the current time, and verify the JSON is valid -before writing. - -In both paths, set `learning_objectives.report_path` to the value of `$_REPORT_PATH` from -the bash block above — i.e., `.idstack/exports/<course-slug>/learning-objectives.html`. - -**Populate the `learning_objectives` section:** - -- `ilos`: Array of objective objects, each with: - - `id`: "ILO-1", "ILO-2", etc. - - `objective`: the measurable statement - - `knowledge_dimension`: factual | conceptual | procedural | metacognitive - - `cognitive_process`: remember | understand | apply | analyze | evaluate | create - -- `alignment_matrix`: - - `ilo_to_activity`: Object mapping ILO IDs to activity descriptions - - `ilo_to_assessment`: Object mapping ILO IDs to assessment descriptions - - `gaps`: Array of strings describing alignment gaps found - -- `expertise_reversal_flags`: Array of strings noting where objective sequencing may - conflict with the learner profile - -Write the manifest, then confirm to the user: - -"Your learning objectives are saved. Two artifacts: - -- **Read this:** `.idstack/exports/<course-slug>/learning-objectives.html` — the alignment - table, evidence-backed findings on gaps and mismatches, and a Bloom's-level expertise - read. Open it in any browser; the folder is self-contained. -- System state: `.idstack/project.json` (the manifest — for downstream skills). - -**Next step:** Run `/idstack:assessment-design` to design assessments aligned to your objectives -with evidence-based rubrics and feedback strategies." - ---- - -## Manifest Schema Reference - -The idstack manifest lives at `.idstack/project.json`. Schema version: **1.4**. - -This is the canonical schema. Every skill writes to its own section using the shapes documented here; **all other sections must be preserved verbatim**. There is one source of truth — this file. If the schema ever needs to change, edit `templates/manifest-schema.md`, run `bin/idstack-gen-skills`, and bump `LATEST_VERSION` in `bin/idstack-migrate` with a migration step. - -### Two outputs per skill: JSON manifest + HTML report - -Every skill that produces findings emits **both**: - -- a **JSON section** in this manifest (system state — read by other skills, the pipeline orchestrator, and `bin/idstack-status`), and -- an **HTML report** at `.idstack/exports/<course-slug>/<skill>.html` (the human view — read by the instructional designer). - -The HTML report follows the visual contract in `templates/report.html.tmpl` and the content contract in `templates/report-format.md` (observation → evidence → why-it-matters → suggestion, with severity and evidence tier on every finding). The skill writes the report's relative path back into its own section's `report_path` field so other skills can find it. (`bin/idstack-status` discovers reports independently by globbing `.idstack/exports/<course-slug>/*.html`, so the dashboard survives a stale `report_path`.) - -`<course-slug>` is derived from the top-level `project_name` field via `bin/idstack-slugify` (rule: NFKD-fold, lowercase, kebab-case, ASCII-safe; empty input → `untitled-course`). The slug is computed deterministically — skills don't cache it in the manifest. All exports for a course — per-skill HTML reports, the pipeline dashboard at `index.html`, and LMS packages (`course-export.imscc`, `scorm-export.zip`) — live under the same `.idstack/exports/<course-slug>/` folder so the deliverable is self-describing when zipped, emailed, or handed off. - -`report_path` is an optional string field on every section that produces a report. It is a path relative to the project root (typically `.idstack/exports/<course-slug>/<skill>.html`). Empty string means the skill hasn't run yet, or ran in a mode that didn't produce a report. Renaming a course's `project_name` changes the slug, which moves future exports to a new folder; older folders are left in place. - -### Two ways to write to the manifest - -**1. Recommended — `bin/idstack-manifest-merge`:** write only your section, the tool merges atomically. - -```bash -# Resolve the idstack install dir. Re-derived at the top of every bash block — -# blocks run in separate shells, so a value derived in an earlier block is not -# available here. Priority: explicit env overrides, Codex-style symlinks, then -# the Claude Code marketplace cache. Empty if none found; guard -# "$_IDSTACK/bin/..." calls accordingly. -_IDSTACK="" -# Marketplace cache holds one dir per installed version. Sort the basenames by -# numeric version fields, not lexically — plain sort ranks 3.9.0.0 above -# 3.10.0.0 and would pick a stale install once the minor hits double digits. -_idstack_cache_root="$HOME/.claude/plugins/cache/idstack/idstack" -_idstack_cache="" -if [ -d "$_idstack_cache_root" ]; then - _idstack_v=$(ls "$_idstack_cache_root" 2>/dev/null | sort -t. -k1,1n -k2,2n -k3,3n -k4,4n | tail -1) - [ -n "$_idstack_v" ] && _idstack_cache="$_idstack_cache_root/$_idstack_v" -fi -for _p in "${CLAUDE_PLUGIN_ROOT:-}" "${IDSTACK_HOME:-}" "$HOME/.agents/plugins/idstack" "$HOME/.agents/skills/idstack" "$_idstack_cache"; do - if [ -n "$_p" ] && [ -d "$_p" ]; then _IDSTACK="${_p%/}"; break; fi -done -# Write a payload for your skill's section, then: -"$_IDSTACK/bin/idstack-manifest-merge" --section red_team_audit --payload /tmp/payload.json -``` - -(This file is spliced into skills verbatim, so the resolution above is written -out rather than using the `IDSTACK_RESOLVE` placeholder — that placeholder is -only expanded in `SKILL.md.tmpl` bodies.) - -The merge tool replaces only the named top-level section, preserves every other section, updates the top-level `updated` timestamp, validates JSON on read, and rejects unknown sections. Use this in preference to inlining the full manifest in `Edit` operations. - -**2. Fallback — manual full-manifest write:** if the merge tool is unavailable for some reason, Read the full manifest, modify only your section, Write back. Preserve all other sections verbatim. Use the full schema below as reference. - -### Top-level fields - -| Field | Owner skill(s) | Notes | -|---|---|---| -| `version` | (migrate) | Always equals current schema version. Auto-managed by `bin/idstack-migrate`. | -| `project_name` | (any) | Set on first manifest creation. Don't overwrite once set. | -| `created` | (any, once) | ISO-8601 timestamp of first creation. Don't overwrite. | -| `updated` | (any) | ISO-8601 of last write. Updated automatically by `bin/idstack-manifest-merge`. | -| `context` | needs-analysis (initial) | Modality, timeline, class size, etc. Edited by skills that learn new context. | -| `needs_analysis` | needs-analysis | Org context, task analysis, learner profile, training justification. | -| `learning_objectives` | learning-objectives | ILOs, alignment matrix, expertise-reversal flags. | -| `assessments` | assessment-design | Items, formative checkpoints, feedback plan, rubrics. | -| `course_content` | course-builder | Generated modules, syllabus, content paths. | -| `import_metadata` | course-import | Source LMS, items imported, quality-flag details. | -| `export_metadata` | course-export | Export destination, items exported, readiness check. | -| `quality_review` | course-quality-review | QM standards, CoI presence, alignment audit, cross-domain checks, scores. | -| `red_team_audit` | red-team | Confidence score, dimensions, findings (with stable ids), top actions. | -| `accessibility_review` | accessibility-review | WCAG / UDL scores, violations, recommendations, quick wins. | -| `preferences` | (any, opt-in) | User-set verbosity, export format, preferred LMS, auto-advance. | - -### Full schema (canonical shape) - -```json -{ - "version": "1.4", - "project_name": "", - "created": "", - "updated": "", - "context": { - "modality": "", - "timeline": "", - "class_size": "", - "institution_type": "", - "available_tech": [] - }, - "needs_analysis": { - "mode": "", - "report_path": "", - "organizational_context": { - "problem_statement": "", - "stakeholders": [], - "current_state": "", - "desired_state": "", - "performance_gap": "" - }, - "task_analysis": { - "job_tasks": [], - "prerequisite_knowledge": [], - "tools_and_resources": [] - }, - "learner_profile": { - "prior_knowledge_level": "", - "motivation_factors": [], - "demographics": "", - "access_constraints": [], - "learning_preferences_note": "Learning styles are NOT used as a differentiation basis per evidence. Prior knowledge is the primary differentiator." - }, - "training_justification": { - "justified": true, - "confidence": 0, - "rationale": "", - "alternatives_considered": [] - } - }, - "learning_objectives": { - "report_path": "", - "ilos": [], - "alignment_matrix": { - "ilo_to_activity": {}, - "ilo_to_assessment": {}, - "gaps": [] - }, - "expertise_reversal_flags": [] - }, - "assessments": { - "mode": "", - "report_path": "", - "assessment_strategy": "", - "items": [], - "formative_checkpoints": [], - "feedback_plan": { - "strategy": "", - "turnaround_days": 0, - "peer_review": false - }, - "feedback_quality_score": 0, - "rubrics": [], - "audit_notes": [] - }, - "course_content": { - "mode": "", - "report_path": "", - "generated_at": "", - "expertise_adaptation": "", - "syllabus": "", - "modules": [], - "assessments": [], - "rubrics": [], - "content_dir": ".idstack/course-content/", - "generated_files": [], - "build_timestamp": "", - "placeholders_used": [], - "recommended_generation_targets": [] - }, - "import_metadata": { - "source": "", - "report_path": "", - "imported_at": "", - "source_lms": "", - "source_cartridge": "", - "source_size_bytes": 0, - "schema": "", - "items_imported": { - "modules": 0, - "objectives": 0, - "module_objectives": 0, - "assessments": 0, - "activities": 0, - "pages": 0, - "rubrics": 0, - "quizzes": 0, - "discussions": 0 - }, - "quality_flags": 0, - "quality_flag_details": [] - }, - "export_metadata": { - "report_path": "", - "exported_at": "", - "format": "", - "destination": "", - "items_exported": { - "modules": 0, - "pages": 0, - "assignments": 0, - "quizzes": 0, - "discussions": 0 - }, - "failed_items": [], - "notes": "", - "readiness_check": { - "quality_score": 0, - "quality_reviewed": false, - "red_team_critical": 0, - "red_team_reviewed": false, - "accessibility_critical": 0, - "accessibility_reviewed": false, - "verdict": "" - } - }, - "quality_review": { - "report_path": "", - "last_reviewed": "", - "qm_standards": { - "course_overview": {"status": "", "findings": []}, - "learning_objectives": {"status": "", "findings": []}, - "assessment": {"status": "", "findings": []}, - "instructional_materials": {"status": "", "findings": []}, - "learning_activities": {"status": "", "findings": []}, - "course_technology": {"status": "", "findings": []}, - "learner_support": {"status": "", "findings": []}, - "accessibility": {"status": "", "findings": []} - }, - "coi_presence": { - "teaching_presence": {"score": 0, "findings": []}, - "social_presence": {"score": 0, "findings": []}, - "cognitive_presence": {"score": 0, "findings": []} - }, - "alignment_audit": {"findings": []}, - "cross_domain_checks": { - "cognitive_load": {"score": 0, "flags": []}, - "multimedia_principles": {"score": 0, "flags": []}, - "feedback_quality": {"score": 0, "flags": []}, - "expertise_reversal": {"score": 0, "flags": []} - }, - "overall_score": 0, - "score_breakdown": { - "qm_structural": 0, - "coi_presence": 0, - "constructive_alignment": 0, - "cross_domain_evidence": 0 - }, - "quick_wins": [], - "recommendations": [], - "review_history": [] - }, - "red_team_audit": { - "updated": "", - "confidence_score": 0, - "focus": "", - "report_path": "", - "findings_summary": {"critical": 0, "warning": 0, "info": 0}, - "dimensions": { - "alignment": {"score": "", "findings": []}, - "evidence": {"score": "", "mode": "", "findings": []}, - "cognitive_load": {"score": "", "findings": []}, - "personas": {"score": "", "findings": []}, - "prerequisites": {"score": "", "findings": []} - }, - "top_actions": [], - "limitations": [], - "fixes_applied": [], - "fixes_deferred": [] - }, - "accessibility_review": { - "updated": "", - "report_path": "", - "score": {"overall": 0, "wcag": 0, "udl": 0}, - "wcag_violations": [], - "udl_recommendations": [], - "quick_wins": [] - }, - "preferences": { - "verbosity": "normal", - "export_format": "", - "preferred_lms": "", - "auto_advance_pipeline": false - } -} -``` - -### Per-section item shapes - -These document the **shape of array elements and dictionary values** that the canonical schema leaves as `[]` or `{}`. Skills should produce items in these shapes; downstream skills can rely on them. - -**`learning_objectives.alignment_matrix.ilo_to_activity`** — keyed by ILO id, values are arrays of activity names: -```json -{ "ILO-1": ["Module 1 case study", "Discussion 2"], "ILO-2": [] } -``` - -**`learning_objectives.alignment_matrix.ilo_to_assessment`** — same shape, values are arrays of assessment titles. - -**`learning_objectives.alignment_matrix.gaps[]`** — each item: -```json -{ - "ilo": "ILO-1", - "type": "untested|orphaned|underspecified|bloom_mismatch", - "description": "ILO-1 has no matching assessment in the active modules.", - "severity": "critical|warning|info" -} -``` - -**`learning_objectives.ilos[]`** — each item: -```json -{ - "id": "ILO-1", - "statement": "Analyze competitive forces in...", - "blooms_level": "analyze", - "blooms_confidence": "high|medium|low" -} -``` - -**`assessments.items[]`** — each item: -```json -{ - "id": "A-1", - "type": "quiz|discussion|rubric|peer_review|gate|...", - "title": "Module 1 Quiz", - "weight": 5, - "ilos_measured": ["ILO-1", "ILO-3"], - "rubric_present": true, - "elaborated_feedback": false, - "alignment_status": "weak|moderate|strong" -} -``` - -**`assessments.rubrics[]`** — each item: -```json -{ - "id": "rubric-1", - "title": "SM Project Rubric", - "criteria": [{"name": "...", "blooms_level": "...", "weight": 0}], - "applies_to": ["A-3"] -} -``` - -**`import_metadata.quality_flag_details[]`** — each item (replaces the legacy `_import_quality_flags` root field that sometimes appeared in the wild): -```json -{ - "key": "orphan_module_8", - "description": "Module 8 wiki content exists in the cartridge but is not referenced in <organizations>.", - "severity": "warning|critical|info", - "evidence": "Optional citation tag, e.g. [Alignment-1] [T5]" -} -``` - -**`red_team_audit.dimensions.<name>.findings[]`** — each item (matches the `<dimension>-<n>` id convention from the red-team orchestrator): -```json -{ - "id": "alignment-1", - "description": "ILO-2 (vision/mission) has no matching assessment.", - "module": "Module 4", - "severity": "critical|warning|info" -} -``` - -**`accessibility_review.wcag_violations[]`** — each item: -```json -{ - "id": "wcag-1", - "criterion": "1.3.1 Info and Relationships", - "level": "A|AA|AAA", - "description": "All cartridge HTML pages lack <h1> elements.", - "affected": ["page1.html", "page2.html"], - "severity": "critical|warning|info" -} -``` - -**`accessibility_review.udl_recommendations[]`** — each item: -```json -{ - "id": "udl-1", - "principle": "engagement|representation|action_expression", - "description": "Add transcripts to all videos.", - "status": "fully_met|partial|not_met" -} -``` - -**`quality_review.qm_standards.<standard>.findings[]`**, **`quality_review.alignment_audit.findings[]`**, **`quality_review.cross_domain_checks.<check>.flags[]`**, and other findings arrays — each item: -```json -{ - "id": "<dimension>-<n>", - "description": "...", - "evidence": "[Domain-N] [TX]", - "severity": "critical|warning|info" -} -``` - -### Mode field — design-new vs audit-existing - -`needs_analysis.mode`, `assessments.mode`, and `course_content.mode` record which operating mode the corresponding skill ran in. Trigger: `import_metadata.source` ∈ `{cartridge, scorm, canvas-api}` plus the relevant section being non-empty (skill-specific check). - -Allowed values per skill: -- `needs_analysis.mode`: `"design-new"` or `"audit-existing"` -- `assessments.mode`: `"Mode 1"`, `"Mode 2"`, or `"Mode 3"` (Mode 1 = full upstream data, Mode 2 = ILOs-from-scratch, Mode 3 = audit existing assessments) -- `course_content.mode`: `"build-new"` or `"gap-fill"` - -Empty string means the skill hasn't run yet or didn't record the mode (legacy manifests). - -**`assessments.audit_notes[]`** — only populated in Mode 3. Records which audit findings the user chose to act on: -```json -{ - "target_id": "A-3", - "action": "applied|deferred|declined", - "description": "Rubric criterion for ILO-2 added: 'Synthesis depth (1-4 scale)'.", - "reason": "Optional — only meaningful for deferred/declined." -} -``` - -**`course_content.recommended_generation_targets[]`** — populated in `gap-fill` mode. Lists artifacts upstream skills flagged as missing, with status: -```json -{ - "description": "Discussion rubric for Module 5", - "source": "red-team:alignment-3 | quality-review:learner_support-2 | user-request", - "status": "generated|deferred|declined", - "output_path": "Optional — set when status=generated, points to the generated file." -} -``` - -## Feedback - -Have feedback or a feature request? [Share it here](https://forms.gle/6LDgDD1M6WWyYvME8) — no GitHub account needed. - ---- - -## Completion: Timeline Logging - -After the skill workflow completes successfully, log the session to the timeline: - -```bash -# Resolve the idstack install dir. Re-derived at the top of every bash block — -# blocks run in separate shells, so a value derived in an earlier block is not -# available here. Priority: explicit env overrides, Codex-style symlinks, then -# the Claude Code marketplace cache. Empty if none found; guard -# "$_IDSTACK/bin/..." calls accordingly. -_IDSTACK="" -# Marketplace cache holds one dir per installed version. Sort the basenames by -# numeric version fields, not lexically — plain sort ranks 3.9.0.0 above -# 3.10.0.0 and would pick a stale install once the minor hits double digits. -_idstack_cache_root="$HOME/.claude/plugins/cache/idstack/idstack" -_idstack_cache="" -if [ -d "$_idstack_cache_root" ]; then - _idstack_v=$(ls "$_idstack_cache_root" 2>/dev/null | sort -t. -k1,1n -k2,2n -k3,3n -k4,4n | tail -1) - [ -n "$_idstack_v" ] && _idstack_cache="$_idstack_cache_root/$_idstack_v" -fi -for _p in "${CLAUDE_PLUGIN_ROOT:-}" "${IDSTACK_HOME:-}" "$HOME/.agents/plugins/idstack" "$HOME/.agents/skills/idstack" "$_idstack_cache"; do - if [ -n "$_p" ] && [ -d "$_p" ]; then _IDSTACK="${_p%/}"; break; fi -done -"$_IDSTACK/bin/idstack-timeline-log" '{"skill":"learning-objectives","event":"completed"}' -``` - -Replace the JSON above with actual data from this session. Include skill-specific fields -where available (scores, counts, flags). Log synchronously (no background &). - -If you discover a non-obvious project-specific quirk during this session (LMS behavior, -import format issue, course structure pattern), also log it as a learning: - -```bash -# Resolve the idstack install dir. Re-derived at the top of every bash block — -# blocks run in separate shells, so a value derived in an earlier block is not -# available here. Priority: explicit env overrides, Codex-style symlinks, then -# the Claude Code marketplace cache. Empty if none found; guard -# "$_IDSTACK/bin/..." calls accordingly. -_IDSTACK="" -# Marketplace cache holds one dir per installed version. Sort the basenames by -# numeric version fields, not lexically — plain sort ranks 3.9.0.0 above -# 3.10.0.0 and would pick a stale install once the minor hits double digits. -_idstack_cache_root="$HOME/.claude/plugins/cache/idstack/idstack" -_idstack_cache="" -if [ -d "$_idstack_cache_root" ]; then - _idstack_v=$(ls "$_idstack_cache_root" 2>/dev/null | sort -t. -k1,1n -k2,2n -k3,3n -k4,4n | tail -1) - [ -n "$_idstack_v" ] && _idstack_cache="$_idstack_cache_root/$_idstack_v" -fi -for _p in "${CLAUDE_PLUGIN_ROOT:-}" "${IDSTACK_HOME:-}" "$HOME/.agents/plugins/idstack" "$HOME/.agents/skills/idstack" "$_idstack_cache"; do - if [ -n "$_p" ] && [ -d "$_p" ]; then _IDSTACK="${_p%/}"; break; fi -done -"$_IDSTACK/bin/idstack-learnings-log" '{"skill":"learning-objectives","type":"operational","key":"SHORT_KEY","insight":"DESCRIPTION","confidence":8,"source":"observed"}' -``` diff --git a/dist/codex/skills/idstack-needs-analysis/SKILL.md b/dist/codex/skills/idstack-needs-analysis/SKILL.md deleted file mode 100644 index 539b96f..0000000 --- a/dist/codex/skills/idstack-needs-analysis/SKILL.md +++ /dev/null @@ -1,1114 +0,0 @@ ---- -name: needs-analysis -description: | - Evidence-based three-level needs assessment for instructional design. - Guides you through organizational, task, and learner analysis before - building a course. Creates a shared project manifest that downstream - skills (/learning-objectives, /course-quality-review) read and extend. (idstack) ---- -<!-- AUTO-GENERATED from SKILL.md.tmpl -- do not edit directly --> -<!-- Edit the .tmpl file instead. Regenerate: bin/idstack-gen-skills --> - - -## Preamble: Interaction Conventions - -idstack skills are designed to run in multiple CLIs (Claude Code, OpenAI Codex CLI, and -others). To stay portable, skill bodies use a few **concept names** that have a CLI-specific -implementation: - -- **AskUserQuestion** — when a skill says "ask via AskUserQuestion" or "using AskUserQuestion", - it means: present a single numbered multiple-choice question (e.g., "Which of these best - describes X? 1) ..., 2) ..., 3) Other") and stop, waiting for the user's next message - before proceeding. Ask **one** question at a time, never batch. In Claude Code this maps - to the `AskUserQuestion` tool; in Codex CLI (which has no analog) just emit the numbered - question as plain text and wait. -- **Agent / Skill (sub-task dispatch)** — when a skill says "if the Agent tool is available, - dispatch X as a sub-task," that's an optimization. If your CLI has no equivalent, fall - through to the inline written-out steps that follow — every skill that uses `Agent` ships - a sequential fallback alongside it. -- **Skill (cross-skill invocation)** — used only by `/idstack:pipeline`. In Claude Code this - invokes a child skill in-process via the `Skill` tool. In CLIs without that primitive, the - pipeline degrades to prompting the user to type the next skill name explicitly. -- **Skill invocation syntax in user-facing text** — when a skill prints "run `/idstack:foo`" - or links to `/idstack:foo` in a "Next steps" section, that's Claude Code's `/idstack:<name>` - invocation. **In Codex CLI, translate to `$foo`** (drop the `/idstack:` prefix, replace - the leading `/` with `$`). Apply this same translation when the skill emits handoff - instructions in reports, manifests, or AskUserQuestion options. Same body text, two - hosts; the model translates per-CLI on output. - -These are **directives to the model**, not magic words — interpret them as the protocol above. - -## Preamble: Update Check - -```bash -# Resolve the idstack install dir. Re-derived at the top of every bash block — -# blocks run in separate shells, so a value derived in an earlier block is not -# available here. Priority: explicit env overrides, Codex-style symlinks, then -# the Claude Code marketplace cache (highest version). Empty if none found; -# guard "$_IDSTACK/bin/..." calls accordingly. -# Canonical copy: templates/snippets/idstack-resolve.sh (the IDSTACK_RESOLVE -# placeholder in skill templates) — keep this block identical to it. -_IDSTACK="" -# Marketplace cache holds one dir per installed version. Sort the basenames by -# numeric version fields, not lexically — plain sort ranks 3.9.0.0 above -# 3.10.0.0 and would pick a stale install once the minor hits double digits. -_idstack_cache_root="$HOME/.claude/plugins/cache/idstack/idstack" -_idstack_cache="" -if [ -d "$_idstack_cache_root" ]; then - _idstack_v=$(ls "$_idstack_cache_root" 2>/dev/null | sort -t. -k1,1n -k2,2n -k3,3n -k4,4n | tail -1) - [ -n "$_idstack_v" ] && _idstack_cache="$_idstack_cache_root/$_idstack_v" -fi -for _p in "${CLAUDE_PLUGIN_ROOT:-}" "${IDSTACK_HOME:-}" "$HOME/.agents/plugins/idstack" "$HOME/.agents/skills/idstack" "$_idstack_cache"; do - if [ -n "$_p" ] && [ -d "$_p" ]; then _IDSTACK="${_p%/}"; break; fi -done -_UPD=$("$_IDSTACK/bin/idstack-update-check" 2>/dev/null || true) -[ -n "$_UPD" ] && echo "$_UPD" -``` - -If the output contains `UPDATE_AVAILABLE`: tell the user "A newer version of idstack is available. Run `cd $_IDSTACK && git pull && ./setup` to update. (The `./setup` step is required — it cleans up legacy symlinks.)" Then continue normally. - -## Preamble: Project Manifest - -Before starting, check for an existing project manifest. - -```bash -# (fresh shell — re-derive the install dir; see Preamble: Update Check) -_IDSTACK="" -# Marketplace cache holds one dir per installed version. Sort the basenames by -# numeric version fields, not lexically — plain sort ranks 3.9.0.0 above -# 3.10.0.0 and would pick a stale install once the minor hits double digits. -_idstack_cache_root="$HOME/.claude/plugins/cache/idstack/idstack" -_idstack_cache="" -if [ -d "$_idstack_cache_root" ]; then - _idstack_v=$(ls "$_idstack_cache_root" 2>/dev/null | sort -t. -k1,1n -k2,2n -k3,3n -k4,4n | tail -1) - [ -n "$_idstack_v" ] && _idstack_cache="$_idstack_cache_root/$_idstack_v" -fi -for _p in "${CLAUDE_PLUGIN_ROOT:-}" "${IDSTACK_HOME:-}" "$HOME/.agents/plugins/idstack" "$HOME/.agents/skills/idstack" "$_idstack_cache"; do - if [ -n "$_p" ] && [ -d "$_p" ]; then _IDSTACK="${_p%/}"; break; fi -done -if [ -f ".idstack/project.json" ]; then - echo "MANIFEST_EXISTS" - "$_IDSTACK/bin/idstack-migrate" .idstack/project.json 2>/dev/null || cat .idstack/project.json -else - echo "NO_MANIFEST" -fi -``` - -**If MANIFEST_EXISTS:** -- Read the manifest. If the JSON is malformed, report the specific parse error to the - user, offer to fix it, and STOP until it is valid. Never silently overwrite corrupt JSON. -- Preserve all existing sections when writing back. - -**If NO_MANIFEST:** -- This skill will create or update the manifest during its workflow. - -## Preamble: Preferences - -```bash -if [ -f ".idstack/project.json" ] && command -v python3 &>/dev/null; then - python3 -c " -import json, sys -try: - data = json.load(open('.idstack/project.json')) - prefs = data.get('preferences', {}) - v = prefs.get('verbosity', 'normal') - if v != 'normal': - print(f'VERBOSITY:{v}') -except: pass -" 2>/dev/null || true -fi -``` - -**If VERBOSITY:concise:** Keep explanations brief. Skip evidence citations inline -(still follow evidence-based recommendations, just don't cite tier codes in output). -**If VERBOSITY:detailed:** Include full evidence citations, alternative approaches -considered, and rationale for each recommendation. -**If VERBOSITY:normal or not shown:** Default behavior — cite evidence tiers inline, -explain key decisions, skip exhaustive alternatives. - -## Preamble: Designer Profile - -```bash -_PROFILE="$HOME/.idstack/profile.yaml" -if [ -f "$_PROFILE" ]; then - # Simple YAML parsing for experience_level (no dependency needed) - _EXP=$(grep -E '^experience_level:' "$_PROFILE" 2>/dev/null | sed 's/experience_level:[[:space:]]*//' | tr -d '"' | tr -d "'") - [ -n "$_EXP" ] && echo "EXPERIENCE:$_EXP" -else - echo "NO_PROFILE" -fi -``` - -**If EXPERIENCE:novice:** Provide more context for recommendations. Explain WHY each -step matters, not just what to do. Define jargon on first use. Offer examples. -**If EXPERIENCE:intermediate:** Standard explanations. Assume familiarity with -instructional design concepts but explain idstack-specific patterns. -**If EXPERIENCE:expert:** Be concise. Skip basic explanations. Focus on evidence -tiers, edge cases, and advanced considerations. Trust the user's domain knowledge. -**If NO_PROFILE:** On first run, after the main workflow is underway (not before), -mention: "Tip: create `~/.idstack/profile.yaml` with `experience_level: novice|intermediate|expert` -to adjust how much detail idstack provides." - -## Preamble: Context Recovery - -Check for session history and learnings from prior runs. - -```bash -# Context recovery: timeline + learnings -# (fresh shell — re-derive the install dir; see Preamble: Update Check) -_IDSTACK="" -# Marketplace cache holds one dir per installed version. Sort the basenames by -# numeric version fields, not lexically — plain sort ranks 3.9.0.0 above -# 3.10.0.0 and would pick a stale install once the minor hits double digits. -_idstack_cache_root="$HOME/.claude/plugins/cache/idstack/idstack" -_idstack_cache="" -if [ -d "$_idstack_cache_root" ]; then - _idstack_v=$(ls "$_idstack_cache_root" 2>/dev/null | sort -t. -k1,1n -k2,2n -k3,3n -k4,4n | tail -1) - [ -n "$_idstack_v" ] && _idstack_cache="$_idstack_cache_root/$_idstack_v" -fi -for _p in "${CLAUDE_PLUGIN_ROOT:-}" "${IDSTACK_HOME:-}" "$HOME/.agents/plugins/idstack" "$HOME/.agents/skills/idstack" "$_idstack_cache"; do - if [ -n "$_p" ] && [ -d "$_p" ]; then _IDSTACK="${_p%/}"; break; fi -done -_HAS_TIMELINE=0 -_HAS_LEARNINGS=0 -if [ -f ".idstack/timeline.jsonl" ]; then - _HAS_TIMELINE=1 - if command -v python3 &>/dev/null; then - python3 -c " -import json, sys -lines = open('.idstack/timeline.jsonl').readlines()[-200:] -events = [] -for line in lines: - try: events.append(json.loads(line)) - except: pass -if not events: - sys.exit(0) - -# Quality score trend -scores = [e for e in events if e.get('skill') == 'course-quality-review' and 'score' in e] -if scores: - trend = ' -> '.join(str(s['score']) for s in scores[-5:]) - print(f'QUALITY_TREND: {trend}') - last = scores[-1] - dims = last.get('dimensions', {}) - if dims: - tp = dims.get('teaching_presence', '?') - sp = dims.get('social_presence', '?') - cp = dims.get('cognitive_presence', '?') - print(f'LAST_PRESENCE: T={tp} S={sp} C={cp}') - -# Skills completed -completed = set() -for e in events: - if e.get('event') == 'completed': - completed.add(e.get('skill', '')) -# No f-string here: nesting same-type quotes in a replacement field is a -# SyntaxError before Python 3.12, and macOS system python3 is 3.9. -print('SKILLS_COMPLETED: ' + ','.join(sorted(completed))) - -# Last skill run -last_completed = [e for e in events if e.get('event') == 'completed'] -if last_completed: - last = last_completed[-1] - print(f'LAST_SKILL: {last.get(\"skill\",\"?\")} at {last.get(\"ts\",\"?\")}') - -# Pipeline progression. course-import is the alternative entry point — it -# joins the chain before learning-objectives. -pipeline = [ - ('needs-analysis', 'learning-objectives'), - ('course-import', 'learning-objectives'), - ('learning-objectives', 'assessment-design'), - ('assessment-design', 'course-builder'), - ('course-builder', 'course-quality-review'), - ('course-quality-review', 'accessibility-review'), - ('accessibility-review', 'red-team'), - ('red-team', 'course-export'), -] -for prev, nxt in pipeline: - if prev in completed and nxt not in completed: - print(f'SUGGESTED_NEXT: {nxt}') - break -" 2>/dev/null || true - else - # No python3: show last 3 skill names only - tail -3 .idstack/timeline.jsonl 2>/dev/null | grep -o '"skill":"[^"]*"' | sed 's/"skill":"//;s/"//' | while read s; do echo "RECENT_SKILL: $s"; done - fi -fi -if [ -f ".idstack/learnings.jsonl" ]; then - _HAS_LEARNINGS=1 - _LEARN_COUNT=$(wc -l < .idstack/learnings.jsonl 2>/dev/null | tr -d ' ') - echo "LEARNINGS: $_LEARN_COUNT" - if [ "$_LEARN_COUNT" -gt 0 ] 2>/dev/null; then - "$_IDSTACK/bin/idstack-learnings-search" --limit 3 2>/dev/null || true - fi -fi -``` - -**If QUALITY_TREND is shown:** Synthesize a welcome-back message. Example: "Welcome back. -Quality score trend: 62 -> 68 -> 72 over 3 reviews. Last skill: /idstack:learning-objectives." -Keep it to 2-3 sentences. If any dimension in LAST_PRESENCE is consistently below 5/10, -mention it as a recurring pattern with its evidence citation. - -**If LAST_SKILL is shown but no QUALITY_TREND:** Just mention the last skill run. -Example: "Welcome back. Last session you ran /idstack:course-import." - -**If SUGGESTED_NEXT is shown:** Mention the suggested next skill naturally. -Example: "Based on your progress, /idstack:assessment-design is the natural next step." - -**If LEARNINGS > 0:** Mention relevant learnings if they apply to this skill's domain. -Example: "Reminder: this Canvas instance uses custom rubric formatting (discovered during import)." - ---- - -**Skill-specific manifest check:** If the manifest `needs_analysis` section already has data, -ask the user: "I see you've already run this skill. Want to update the results or start fresh?" - -# Needs Analysis — Three-Level Assessment Protocol - -You are an evidence-based instructional design partner. Your job is to guide the user -through a structured needs assessment before any course design begins. Most instructional -designers skip this step or do it superficially. That is the problem you exist to solve. - -## Evidence Base - -This skill draws primarily from Domain 3 (Needs Analysis) and Domain 7 (Learner Analysis) -of the idstack evidence synthesis. Key findings encoded in this skill: - -- Training Needs Analysis is widely practiced but methodologically weak. Most TNA methods - are reactive rather than proactive, and conceptual progress has been minimal since the - 1960s [Needs-8] [T3]. -- Multi-level analysis (organizational, task, individual) is necessary but rarely done. - Most TNAs operate at a single level, usually individual self-assessment [Needs-12] [T3]. -- Prior knowledge level is the strongest predictor of which instructional strategies work. - What helps novices hurts experts (expertise reversal effect) [CogLoad-19] [T1]. -- Learning styles (VARK, etc.) are NOT a reliable basis for differentiating instruction. - The "meshing hypothesis" has been repeatedly challenged [Learner domain] [T1]. - -## Evidence Tier Key - -Every recommendation you make MUST include its evidence tier in brackets: -- [T1] RCTs, meta-analyses with learning outcome measures -- [T2] Quasi-experimental with appropriate controls -- [T3] Systematic reviews (synthesis of mixed evidence) -- [T4] Observational / pre-post without comparison groups -- [T5] Expert opinion, literature reviews, theoretical frameworks - -When multiple tiers apply, cite the strongest. - ---- - -## Manifest Inputs (skill-specific) - -The shared preamble above already ran the manifest existence check -(`MANIFEST_EXISTS` / `NO_MANIFEST`) and this skill's re-run question. - ---- - -## Workflow - -Walk the user through three sequential levels. Ask questions ONE AT A TIME using -AskUserQuestion. Do not batch multiple questions. - -### Step 0.5: Mode detection (imported course vs net-new design) - -Before gathering context, decide which mode this skill is operating in. Read -`import_metadata` from the manifest: - -- **Audit-existing mode** — both of these must be true: - - `import_metadata.source` is one of `cartridge`, `scorm`, `canvas-api` - - `import_metadata.items_imported.modules > 0` (i.e., the import actually produced content) -- **Design-new mode** — anything else (no manifest, no import_metadata, manual source, or zero modules imported). - -**Announce the chosen mode to the user as the first sentence of the conversation.** -Examples: -- "Mode: design-new (no import detected). I'll walk you through fresh needs analysis." -- "Mode: audit-existing (cartridge import from Canvas). The course already exists; I'll skip the 'is training justified?' gate and assess design-fit instead." - -If the user says they meant a different mode (e.g., they imported but want to redesign from scratch), accept and switch. The mode determines the rest of the workflow. - -Save the chosen mode under `needs_analysis.mode` (`"design-new"` or `"audit-existing"`) when you write the manifest. - ---- - -### Step 1: Project Context - -Before diving into the three levels, establish the project context. - -**In design-new mode**, ask the user: - -"What course or training program are we designing? Give me the basics: title, subject -area, and who requested it." - -Then establish the delivery context. Ask about: -- **Modality:** Online, face-to-face, hybrid, or hyflex? -- **Timeline:** How long is the course? (semester, 8-week, workshop, etc.) -- **Class size:** Small (<30), medium (30-100), or large (100+)? -- **Institution type:** Higher ed, corporate, K-12? -- **Available technology:** What tools do you have? (LMS, video platform, discussion forums, interactive tools, etc.) - -**In audit-existing mode**, much of this is already in the manifest from `course-import`. Skip questions whose answers are present and confirm only the unknowns: -- `context.modality` — usually inferable from the LMS (Canvas/Moodle/etc. = online or hybrid). Confirm with the user if ambiguous. -- `context.timeline` — sometimes in `course_settings.xml` (e.g., the syllabus dates from the cartridge). Use it if found; ask only if absent. -- `context.class_size` — typically not in the cartridge; ask. -- `context.institution_type` — usually inferable from `import_metadata.source_lms` (Canvas in higher-ed, etc.). Confirm with the user if ambiguous. -- `context.available_tech` — recover from cartridge tool references; ask for additions. - -Don't re-ask the user for the course title — read it from `import_metadata` or the imported manifest. - -Store these in the `context` section of the manifest. - ---- - -### Step 2: Level 1 — Organizational/Context Analysis - -**In audit-existing mode, the "is training justified?" question is moot.** A credit-bearing course already exists in the registrar's system; the design decision was made upstream. Skip the decision-gate logic below and instead ask one design-fit question: - -> "Given the course as imported (from `import_metadata.source_lms`), what would you say is the *organizational problem* this course was originally created to solve? (e.g., 'undergraduates lack synthesis skills before entering capstone'). Be specific — this anchors the rest of the design audit." - -Then capture stakeholders, current state, desired state, and performance gap as in design-new mode (questions 2–5 below). Set `needs_analysis.training_justification` to: -```json -{"justified": true, "confidence": 10, "rationale": "Existing credit-bearing course; design audit only — training-fit decision is upstream of this skill.", "alternatives_considered": []} -``` -Then proceed to Step 3. - ---- - -**In design-new mode**, run the full decision gate below. - -**Purpose:** Determine whether training is the right intervention. - -This is the level most instructional designers skip [Needs-8] [T3]. The consequence: -courses get built to solve problems that aren't actually knowledge/skill gaps. - -Ask these questions (one at a time): - -1. **"What organizational problem or opportunity triggered this course request?"** - Listen for: specific performance gaps, compliance requirements, new technology - adoption, strategic initiatives. Flag vague answers ("we need training on X") - and push for the underlying problem. - -2. **"Who are the stakeholders? Who requested this, who approves it, who will be - affected by it?"** - -3. **"What is the current state? How are people performing right now?"** - -4. **"What is the desired state? What should performance look like after this - intervention?"** - -5. **"What is the gap between current and desired state?"** - This is the performance gap. Be specific: is it a knowledge gap (people don't - know how), a skill gap (people can't do it), a motivation gap (people won't do - it), or an environment gap (the system prevents it)? - -**Decision Gate — Is training the right intervention?** - -After gathering answers, make a judgment: - -- If the gap is **knowledge or skill**: Training is likely justified. Proceed. -- If the gap is **motivation**: Training alone won't fix this. Flag it. Consider - incentive redesign, performance support, or management intervention. Training - may be part of the solution but not the whole solution [T3]. -- If the gap is **environmental** (bad tools, unclear processes, insufficient resources): - Training is NOT the right intervention. Say so directly. "Based on what you've - described, the performance gap is caused by [environmental factor], not a lack of - knowledge or skills. Training won't fix this. Consider [alternative intervention] - instead." [Needs-8] [T3] - -Populate the `training_justification` object: -- `justified`: true or false (you CAN recommend against training) -- `confidence`: 1-10 (how confident are you in this judgment?) -- `rationale`: one paragraph explaining why -- `alternatives_considered`: list of non-training interventions you evaluated - -**If training is NOT justified:** Present the finding clearly. Ask the user if they -want to proceed anyway (they may have context you don't). If they proceed, note it -in the rationale: "User chose to proceed despite recommendation against training. -Reason: [user's reason]." - ---- - -### Step 3: Level 2 — Task Analysis - -**Purpose:** Identify what learners must actually DO after this course. - -Ask: - -1. **"What are the key tasks or activities that learners need to perform after - completing this course?"** - Push for observable, measurable performance. "Understand ethics" is not a task. - "Evaluate a dataset for potential bias using a structured checklist" is a task. - -2. For each task identified, capture: - - **Description:** What exactly do they do? - - **Frequency:** How often? (daily, weekly, monthly, rare) - - **Criticality:** What happens if they do it wrong? (high = serious consequences, - medium = noticeable impact, low = minor inconvenience) - - **Difficulty:** How hard is this for the target audience? (high, medium, low) - -3. **"What prerequisite knowledge or skills do learners need before they can learn - these tasks?"** - -4. **"What tools, resources, or systems do learners use to perform these tasks?"** - -Assign task IDs: T-1, T-2, T-3, etc. - -**Prioritization logic:** Tasks with high criticality AND high frequency should drive -the core curriculum. Tasks with low criticality AND rare frequency may be better -served by reference materials or job aids rather than formal instruction [T3]. - -Present the task analysis as a table: - -| ID | Task | Frequency | Criticality | Difficulty | Priority | -|----|------|-----------|-------------|------------|----------| -| T-1 | ... | daily | high | medium | Core | -| T-2 | ... | rare | low | low | Reference | - ---- - -### Step 4: Level 3 — Learner Analysis - -**Purpose:** Understand who the learners are, with emphasis on prior knowledge level. - -Ask: - -1. **"What is the prior knowledge level of your learners for this subject?"** - Options: Novice (little to no background), Intermediate (some exposure but not - proficient), Advanced (experienced practitioners), Mixed (varies widely). - - **This is the most important question in the entire needs assessment.** Prior - knowledge level is the primary differentiator for all downstream instructional - decisions. What helps novices hurts experts (expertise reversal effect) - [CogLoad-19] [T1]. The entire sequencing, scaffolding, and assessment strategy - depends on this answer. - -2. **"What motivates your learners? Why would they engage with this course?"** - Listen for: intrinsic motivation (genuine interest), extrinsic motivation - (grade, certification, career advancement), or compliance (required by employer/program). - -3. **"Briefly describe the learner demographics relevant to this course."** - Age range, academic level, professional background, etc. - -4. **"Are there any access constraints or barriers?"** - Examples: no webcam, slow internet, screen reader users, ESL learners, shift - workers with limited time. - -**Learning Styles Redirect:** -If the user mentions "learning styles," "VARK," "visual learners," "auditory -learners," or similar: respond with this exact framing: - -"I appreciate you thinking about learner differences. However, research consistently -shows that matching instruction to learning style preferences does not improve learning -outcomes [T1]. The 'meshing hypothesis' — that students learn better when instruction -matches their style — has been repeatedly tested and not supported. - -What DOES reliably predict which strategies work is prior knowledge level. Novices -benefit from more structure, worked examples, and explicit instruction. Experts -benefit from less scaffolding and more problem-solving autonomy. Let's focus on -prior knowledge instead." - -The `learning_preferences_note` field in the manifest is always populated with: -"Learning styles are NOT used as a differentiation basis per evidence. Prior knowledge -is the primary differentiator." - ---- - -### Step 5: Summary and Manifest Creation - -After completing all three levels, present a structured summary: - -``` -## Needs Analysis Summary - -### Project Context -- Course: [title] -- Modality: [modality] | Timeline: [timeline] | Class Size: [size] -- Institution: [type] | Tech: [available tech] - -### Level 1: Organizational/Context Analysis -- Problem Statement: [one sentence] -- Performance Gap: [knowledge/skill/motivation/environment] -- Training Justified: [Yes/No] (Confidence: X/10) -- Rationale: [one paragraph] -- Alternatives Considered: [list] - -### Level 2: Task Analysis -[task table] -- Core tasks (high priority): [count] -- Reference tasks (low priority): [count] -- Prerequisites: [list] - -### Level 3: Learner Analysis -- Prior Knowledge: [level] ← This drives all downstream decisions -- Motivation: [type] -- Demographics: [summary] -- Access Constraints: [list or "none identified"] -``` - -**Expertise Reversal Check:** -Based on the learner profile, note which instructional strategies are appropriate: -- **Novice learners:** More scaffolding, worked examples, explicit instruction, - structured guidance [CogLoad-4] [T1] -- **Intermediate learners:** Faded scaffolding, guided practice with feedback, - increasing autonomy [CogLoad-5] [T5] -- **Advanced learners:** Less scaffolding, problem-based learning, case studies, - avoid redundant information that adds extraneous cognitive load [CogLoad-19] [T1] -- **Mixed audience:** Adaptive approaches, tiered activities, or separate pathways - [Learner-16] [T1] - ---- - -### Step 6: Generate Report - -Generate an HTML report so the designer has a single document to read. The report follows the **visual contract** in `templates/report.html.tmpl` (the skeleton) and the **content contract** in `templates/report-format.md` (severity ordering, citation format, what each placeholder must carry). - -```bash -# Resolve the idstack install dir. Re-derived at the top of every bash block — -# blocks run in separate shells, so a value derived in an earlier block is not -# available here. Priority: explicit env overrides, Codex-style symlinks, then -# the Claude Code marketplace cache. Empty if none found; guard -# "$_IDSTACK/bin/..." calls accordingly. -_IDSTACK="" -# Marketplace cache holds one dir per installed version. Sort the basenames by -# numeric version fields, not lexically — plain sort ranks 3.9.0.0 above -# 3.10.0.0 and would pick a stale install once the minor hits double digits. -_idstack_cache_root="$HOME/.claude/plugins/cache/idstack/idstack" -_idstack_cache="" -if [ -d "$_idstack_cache_root" ]; then - _idstack_v=$(ls "$_idstack_cache_root" 2>/dev/null | sort -t. -k1,1n -k2,2n -k3,3n -k4,4n | tail -1) - [ -n "$_idstack_v" ] && _idstack_cache="$_idstack_cache_root/$_idstack_v" -fi -for _p in "${CLAUDE_PLUGIN_ROOT:-}" "${IDSTACK_HOME:-}" "$HOME/.agents/plugins/idstack" "$HOME/.agents/skills/idstack" "$_idstack_cache"; do - if [ -n "$_p" ] && [ -d "$_p" ]; then _IDSTACK="${_p%/}"; break; fi -done -# Compute the course slug from project_name and prepare the export folder. -_PROJECT_NAME=$(python3 -c "import json; print(json.load(open('.idstack/project.json')).get('project_name',''))" 2>/dev/null || echo "") -_SLUG=$("$_IDSTACK/bin/idstack-slugify" "$_PROJECT_NAME" 2>/dev/null || echo "untitled-course") -_EXPORT_DIR=".idstack/exports/$_SLUG" -_REPORT_PATH="$_EXPORT_DIR/needs-analysis.html" -mkdir -p "$_EXPORT_DIR/assets" -cp -f "$_IDSTACK/templates/assets/idstack.css" "$_EXPORT_DIR/assets/idstack.css" -echo "Report path: $_REPORT_PATH" -``` - -Write the HTML report at the path printed above (`.idstack/exports/<course-slug>/needs-analysis.html`), following the structure of `templates/report.html.tmpl`. Use these CSS hooks so the stylesheet applies cleanly: `<article class="finding sev-{severity}">`, `<span class="sev-badge sev-{severity}">`, `<span class="tier-badge tier-T{N}">`, `<cite class="citation">[Domain-N] [TN]</cite>`. Customize for this skill: - -- **`{{skill_title}}`:** "Needs Analysis Report" -- **`{{skill_name}}`:** `needs-analysis` -- **`{{mode}}`:** `design-new` or `audit-existing` (include the optional mode segment in the header `meta` line). -- **Summary:** 2–3 sentences. Lead with the verdict — training justified or not, the biggest risk in the learner profile, and the headline finding from the task analysis. Don't bury the lede. -- **Skill-specific section before Findings** — add a `<section class="project-context">` with `<h2>Project context</h2>` and a `<dl>` listing modality, timeline, class size, institution type, and available tech. -- **Finding ids:** `needs-1`, `needs-2`, etc. Findings come from the three levels: organizational gap, task-analysis gaps, learner-profile risks (e.g., expertise mismatch with the planned design). -- **Optional skill-specific sections** (after Top recommendations, before Limitations): - - `<section class="training-justification">` with `<h2>Training justification</h2>` — show `Justified: Yes/No`, `Confidence: X/10`, the rationale paragraph, and alternatives considered (or "n/a — imported credit-bearing course" in audit-existing mode). - - `<section class="expertise-fit">` with `<h2>Expertise-fit read</h2>` — which instructional strategies are appropriate given the learner profile. Cite the expertise-reversal evidence so the read isn't opinion: `[CogLoad-4] [T1]` for novices; `[CogLoad-19] [T1]` for advanced; `[Learner-16] [T1]` for mixed. -- **Limitations:** imported-course mode skips the training-decision gate; learner profile draws on the registrar/syllabus, not a learner survey; task analysis is from job-task lists, not observed performance. -- **Next steps:** Run `/idstack:learning-objectives` to develop ILOs grounded in this analysis. The objectives skill reads your task analysis and learner profile to recommend appropriate Bloom's levels and alignment strategies. - -Every finding in the HTML must correspond to a finding the manifest's `needs_analysis` section can carry, so downstream skills and `bin/idstack-status` can refer to them programmatically. - -### Step 7: Write Manifest - -Create or update the project manifest. Use the Write tool to write `.idstack/project.json`. - -**Why this skill uses the Write-tool fallback (not `bin/idstack-manifest-merge`):** needs-analysis writes top-level fields (`project_name`, `created`), the `context` section, AND the `needs_analysis` section — and on a first run it initializes ALL sections so downstream skills find the expected structure. The merge tool replaces exactly one section per call, so this skill uses the Read-modify-Write path described in `templates/manifest-schema.md`. When a manifest already exists, preserve every other section unchanged. - -**CRITICAL — Manifest Integrity Rules:** -1. If a manifest already exists, READ it first, then modify only the sections this - skill owns (context, needs_analysis). Preserve all other sections unchanged. -2. Include the COMPLETE schema structure. Do not omit fields. -3. Before writing, mentally verify the JSON is valid: matching braces, proper commas, - quoted strings, no trailing commas. -4. The `updated` timestamp must reflect the current time. -5. Set `needs_analysis.report_path` to the value of `$_REPORT_PATH` from the bash block above — i.e., `.idstack/exports/<course-slug>/needs-analysis.html`. -6. If this is a new manifest, initialize ALL sections (including learning_objectives - and quality_review) with empty/default values so downstream skills find the - expected structure. - -Write the manifest, then confirm to the user: - -"Your needs analysis is saved. Two artifacts: - -- **Read this:** `.idstack/exports/<course-slug>/needs-analysis.html` — the branded HTML - report with evidence-backed findings, the training-justification read, and a - learner-profile expertise check. Open it in any browser. The folder is self-contained - (CSS is bundled), so you can zip or email it. -- System state: `.idstack/project.json` (the manifest — for downstream skills). - -**Next step:** Run `/idstack:learning-objectives` to develop learning objectives based on -this analysis. The objectives skill will read your task analysis and learner profile -to recommend appropriate Bloom's levels and alignment strategies." - ---- - -## Manifest Schema Reference - -The idstack manifest lives at `.idstack/project.json`. Schema version: **1.4**. - -This is the canonical schema. Every skill writes to its own section using the shapes documented here; **all other sections must be preserved verbatim**. There is one source of truth — this file. If the schema ever needs to change, edit `templates/manifest-schema.md`, run `bin/idstack-gen-skills`, and bump `LATEST_VERSION` in `bin/idstack-migrate` with a migration step. - -### Two outputs per skill: JSON manifest + HTML report - -Every skill that produces findings emits **both**: - -- a **JSON section** in this manifest (system state — read by other skills, the pipeline orchestrator, and `bin/idstack-status`), and -- an **HTML report** at `.idstack/exports/<course-slug>/<skill>.html` (the human view — read by the instructional designer). - -The HTML report follows the visual contract in `templates/report.html.tmpl` and the content contract in `templates/report-format.md` (observation → evidence → why-it-matters → suggestion, with severity and evidence tier on every finding). The skill writes the report's relative path back into its own section's `report_path` field so other skills can find it. (`bin/idstack-status` discovers reports independently by globbing `.idstack/exports/<course-slug>/*.html`, so the dashboard survives a stale `report_path`.) - -`<course-slug>` is derived from the top-level `project_name` field via `bin/idstack-slugify` (rule: NFKD-fold, lowercase, kebab-case, ASCII-safe; empty input → `untitled-course`). The slug is computed deterministically — skills don't cache it in the manifest. All exports for a course — per-skill HTML reports, the pipeline dashboard at `index.html`, and LMS packages (`course-export.imscc`, `scorm-export.zip`) — live under the same `.idstack/exports/<course-slug>/` folder so the deliverable is self-describing when zipped, emailed, or handed off. - -`report_path` is an optional string field on every section that produces a report. It is a path relative to the project root (typically `.idstack/exports/<course-slug>/<skill>.html`). Empty string means the skill hasn't run yet, or ran in a mode that didn't produce a report. Renaming a course's `project_name` changes the slug, which moves future exports to a new folder; older folders are left in place. - -### Two ways to write to the manifest - -**1. Recommended — `bin/idstack-manifest-merge`:** write only your section, the tool merges atomically. - -```bash -# Resolve the idstack install dir. Re-derived at the top of every bash block — -# blocks run in separate shells, so a value derived in an earlier block is not -# available here. Priority: explicit env overrides, Codex-style symlinks, then -# the Claude Code marketplace cache. Empty if none found; guard -# "$_IDSTACK/bin/..." calls accordingly. -_IDSTACK="" -# Marketplace cache holds one dir per installed version. Sort the basenames by -# numeric version fields, not lexically — plain sort ranks 3.9.0.0 above -# 3.10.0.0 and would pick a stale install once the minor hits double digits. -_idstack_cache_root="$HOME/.claude/plugins/cache/idstack/idstack" -_idstack_cache="" -if [ -d "$_idstack_cache_root" ]; then - _idstack_v=$(ls "$_idstack_cache_root" 2>/dev/null | sort -t. -k1,1n -k2,2n -k3,3n -k4,4n | tail -1) - [ -n "$_idstack_v" ] && _idstack_cache="$_idstack_cache_root/$_idstack_v" -fi -for _p in "${CLAUDE_PLUGIN_ROOT:-}" "${IDSTACK_HOME:-}" "$HOME/.agents/plugins/idstack" "$HOME/.agents/skills/idstack" "$_idstack_cache"; do - if [ -n "$_p" ] && [ -d "$_p" ]; then _IDSTACK="${_p%/}"; break; fi -done -# Write a payload for your skill's section, then: -"$_IDSTACK/bin/idstack-manifest-merge" --section red_team_audit --payload /tmp/payload.json -``` - -(This file is spliced into skills verbatim, so the resolution above is written -out rather than using the `IDSTACK_RESOLVE` placeholder — that placeholder is -only expanded in `SKILL.md.tmpl` bodies.) - -The merge tool replaces only the named top-level section, preserves every other section, updates the top-level `updated` timestamp, validates JSON on read, and rejects unknown sections. Use this in preference to inlining the full manifest in `Edit` operations. - -**2. Fallback — manual full-manifest write:** if the merge tool is unavailable for some reason, Read the full manifest, modify only your section, Write back. Preserve all other sections verbatim. Use the full schema below as reference. - -### Top-level fields - -| Field | Owner skill(s) | Notes | -|---|---|---| -| `version` | (migrate) | Always equals current schema version. Auto-managed by `bin/idstack-migrate`. | -| `project_name` | (any) | Set on first manifest creation. Don't overwrite once set. | -| `created` | (any, once) | ISO-8601 timestamp of first creation. Don't overwrite. | -| `updated` | (any) | ISO-8601 of last write. Updated automatically by `bin/idstack-manifest-merge`. | -| `context` | needs-analysis (initial) | Modality, timeline, class size, etc. Edited by skills that learn new context. | -| `needs_analysis` | needs-analysis | Org context, task analysis, learner profile, training justification. | -| `learning_objectives` | learning-objectives | ILOs, alignment matrix, expertise-reversal flags. | -| `assessments` | assessment-design | Items, formative checkpoints, feedback plan, rubrics. | -| `course_content` | course-builder | Generated modules, syllabus, content paths. | -| `import_metadata` | course-import | Source LMS, items imported, quality-flag details. | -| `export_metadata` | course-export | Export destination, items exported, readiness check. | -| `quality_review` | course-quality-review | QM standards, CoI presence, alignment audit, cross-domain checks, scores. | -| `red_team_audit` | red-team | Confidence score, dimensions, findings (with stable ids), top actions. | -| `accessibility_review` | accessibility-review | WCAG / UDL scores, violations, recommendations, quick wins. | -| `preferences` | (any, opt-in) | User-set verbosity, export format, preferred LMS, auto-advance. | - -### Full schema (canonical shape) - -```json -{ - "version": "1.4", - "project_name": "", - "created": "", - "updated": "", - "context": { - "modality": "", - "timeline": "", - "class_size": "", - "institution_type": "", - "available_tech": [] - }, - "needs_analysis": { - "mode": "", - "report_path": "", - "organizational_context": { - "problem_statement": "", - "stakeholders": [], - "current_state": "", - "desired_state": "", - "performance_gap": "" - }, - "task_analysis": { - "job_tasks": [], - "prerequisite_knowledge": [], - "tools_and_resources": [] - }, - "learner_profile": { - "prior_knowledge_level": "", - "motivation_factors": [], - "demographics": "", - "access_constraints": [], - "learning_preferences_note": "Learning styles are NOT used as a differentiation basis per evidence. Prior knowledge is the primary differentiator." - }, - "training_justification": { - "justified": true, - "confidence": 0, - "rationale": "", - "alternatives_considered": [] - } - }, - "learning_objectives": { - "report_path": "", - "ilos": [], - "alignment_matrix": { - "ilo_to_activity": {}, - "ilo_to_assessment": {}, - "gaps": [] - }, - "expertise_reversal_flags": [] - }, - "assessments": { - "mode": "", - "report_path": "", - "assessment_strategy": "", - "items": [], - "formative_checkpoints": [], - "feedback_plan": { - "strategy": "", - "turnaround_days": 0, - "peer_review": false - }, - "feedback_quality_score": 0, - "rubrics": [], - "audit_notes": [] - }, - "course_content": { - "mode": "", - "report_path": "", - "generated_at": "", - "expertise_adaptation": "", - "syllabus": "", - "modules": [], - "assessments": [], - "rubrics": [], - "content_dir": ".idstack/course-content/", - "generated_files": [], - "build_timestamp": "", - "placeholders_used": [], - "recommended_generation_targets": [] - }, - "import_metadata": { - "source": "", - "report_path": "", - "imported_at": "", - "source_lms": "", - "source_cartridge": "", - "source_size_bytes": 0, - "schema": "", - "items_imported": { - "modules": 0, - "objectives": 0, - "module_objectives": 0, - "assessments": 0, - "activities": 0, - "pages": 0, - "rubrics": 0, - "quizzes": 0, - "discussions": 0 - }, - "quality_flags": 0, - "quality_flag_details": [] - }, - "export_metadata": { - "report_path": "", - "exported_at": "", - "format": "", - "destination": "", - "items_exported": { - "modules": 0, - "pages": 0, - "assignments": 0, - "quizzes": 0, - "discussions": 0 - }, - "failed_items": [], - "notes": "", - "readiness_check": { - "quality_score": 0, - "quality_reviewed": false, - "red_team_critical": 0, - "red_team_reviewed": false, - "accessibility_critical": 0, - "accessibility_reviewed": false, - "verdict": "" - } - }, - "quality_review": { - "report_path": "", - "last_reviewed": "", - "qm_standards": { - "course_overview": {"status": "", "findings": []}, - "learning_objectives": {"status": "", "findings": []}, - "assessment": {"status": "", "findings": []}, - "instructional_materials": {"status": "", "findings": []}, - "learning_activities": {"status": "", "findings": []}, - "course_technology": {"status": "", "findings": []}, - "learner_support": {"status": "", "findings": []}, - "accessibility": {"status": "", "findings": []} - }, - "coi_presence": { - "teaching_presence": {"score": 0, "findings": []}, - "social_presence": {"score": 0, "findings": []}, - "cognitive_presence": {"score": 0, "findings": []} - }, - "alignment_audit": {"findings": []}, - "cross_domain_checks": { - "cognitive_load": {"score": 0, "flags": []}, - "multimedia_principles": {"score": 0, "flags": []}, - "feedback_quality": {"score": 0, "flags": []}, - "expertise_reversal": {"score": 0, "flags": []} - }, - "overall_score": 0, - "score_breakdown": { - "qm_structural": 0, - "coi_presence": 0, - "constructive_alignment": 0, - "cross_domain_evidence": 0 - }, - "quick_wins": [], - "recommendations": [], - "review_history": [] - }, - "red_team_audit": { - "updated": "", - "confidence_score": 0, - "focus": "", - "report_path": "", - "findings_summary": {"critical": 0, "warning": 0, "info": 0}, - "dimensions": { - "alignment": {"score": "", "findings": []}, - "evidence": {"score": "", "mode": "", "findings": []}, - "cognitive_load": {"score": "", "findings": []}, - "personas": {"score": "", "findings": []}, - "prerequisites": {"score": "", "findings": []} - }, - "top_actions": [], - "limitations": [], - "fixes_applied": [], - "fixes_deferred": [] - }, - "accessibility_review": { - "updated": "", - "report_path": "", - "score": {"overall": 0, "wcag": 0, "udl": 0}, - "wcag_violations": [], - "udl_recommendations": [], - "quick_wins": [] - }, - "preferences": { - "verbosity": "normal", - "export_format": "", - "preferred_lms": "", - "auto_advance_pipeline": false - } -} -``` - -### Per-section item shapes - -These document the **shape of array elements and dictionary values** that the canonical schema leaves as `[]` or `{}`. Skills should produce items in these shapes; downstream skills can rely on them. - -**`learning_objectives.alignment_matrix.ilo_to_activity`** — keyed by ILO id, values are arrays of activity names: -```json -{ "ILO-1": ["Module 1 case study", "Discussion 2"], "ILO-2": [] } -``` - -**`learning_objectives.alignment_matrix.ilo_to_assessment`** — same shape, values are arrays of assessment titles. - -**`learning_objectives.alignment_matrix.gaps[]`** — each item: -```json -{ - "ilo": "ILO-1", - "type": "untested|orphaned|underspecified|bloom_mismatch", - "description": "ILO-1 has no matching assessment in the active modules.", - "severity": "critical|warning|info" -} -``` - -**`learning_objectives.ilos[]`** — each item: -```json -{ - "id": "ILO-1", - "statement": "Analyze competitive forces in...", - "blooms_level": "analyze", - "blooms_confidence": "high|medium|low" -} -``` - -**`assessments.items[]`** — each item: -```json -{ - "id": "A-1", - "type": "quiz|discussion|rubric|peer_review|gate|...", - "title": "Module 1 Quiz", - "weight": 5, - "ilos_measured": ["ILO-1", "ILO-3"], - "rubric_present": true, - "elaborated_feedback": false, - "alignment_status": "weak|moderate|strong" -} -``` - -**`assessments.rubrics[]`** — each item: -```json -{ - "id": "rubric-1", - "title": "SM Project Rubric", - "criteria": [{"name": "...", "blooms_level": "...", "weight": 0}], - "applies_to": ["A-3"] -} -``` - -**`import_metadata.quality_flag_details[]`** — each item (replaces the legacy `_import_quality_flags` root field that sometimes appeared in the wild): -```json -{ - "key": "orphan_module_8", - "description": "Module 8 wiki content exists in the cartridge but is not referenced in <organizations>.", - "severity": "warning|critical|info", - "evidence": "Optional citation tag, e.g. [Alignment-1] [T5]" -} -``` - -**`red_team_audit.dimensions.<name>.findings[]`** — each item (matches the `<dimension>-<n>` id convention from the red-team orchestrator): -```json -{ - "id": "alignment-1", - "description": "ILO-2 (vision/mission) has no matching assessment.", - "module": "Module 4", - "severity": "critical|warning|info" -} -``` - -**`accessibility_review.wcag_violations[]`** — each item: -```json -{ - "id": "wcag-1", - "criterion": "1.3.1 Info and Relationships", - "level": "A|AA|AAA", - "description": "All cartridge HTML pages lack <h1> elements.", - "affected": ["page1.html", "page2.html"], - "severity": "critical|warning|info" -} -``` - -**`accessibility_review.udl_recommendations[]`** — each item: -```json -{ - "id": "udl-1", - "principle": "engagement|representation|action_expression", - "description": "Add transcripts to all videos.", - "status": "fully_met|partial|not_met" -} -``` - -**`quality_review.qm_standards.<standard>.findings[]`**, **`quality_review.alignment_audit.findings[]`**, **`quality_review.cross_domain_checks.<check>.flags[]`**, and other findings arrays — each item: -```json -{ - "id": "<dimension>-<n>", - "description": "...", - "evidence": "[Domain-N] [TX]", - "severity": "critical|warning|info" -} -``` - -### Mode field — design-new vs audit-existing - -`needs_analysis.mode`, `assessments.mode`, and `course_content.mode` record which operating mode the corresponding skill ran in. Trigger: `import_metadata.source` ∈ `{cartridge, scorm, canvas-api}` plus the relevant section being non-empty (skill-specific check). - -Allowed values per skill: -- `needs_analysis.mode`: `"design-new"` or `"audit-existing"` -- `assessments.mode`: `"Mode 1"`, `"Mode 2"`, or `"Mode 3"` (Mode 1 = full upstream data, Mode 2 = ILOs-from-scratch, Mode 3 = audit existing assessments) -- `course_content.mode`: `"build-new"` or `"gap-fill"` - -Empty string means the skill hasn't run yet or didn't record the mode (legacy manifests). - -**`assessments.audit_notes[]`** — only populated in Mode 3. Records which audit findings the user chose to act on: -```json -{ - "target_id": "A-3", - "action": "applied|deferred|declined", - "description": "Rubric criterion for ILO-2 added: 'Synthesis depth (1-4 scale)'.", - "reason": "Optional — only meaningful for deferred/declined." -} -``` - -**`course_content.recommended_generation_targets[]`** — populated in `gap-fill` mode. Lists artifacts upstream skills flagged as missing, with status: -```json -{ - "description": "Discussion rubric for Module 5", - "source": "red-team:alignment-3 | quality-review:learner_support-2 | user-request", - "status": "generated|deferred|declined", - "output_path": "Optional — set when status=generated, points to the generated file." -} -``` - -## Feedback - -Have feedback or a feature request? [Share it here](https://forms.gle/6LDgDD1M6WWyYvME8) — no GitHub account needed. - ---- - -## Completion: Timeline Logging - -After the skill workflow completes successfully, log the session to the timeline: - -```bash -# Resolve the idstack install dir. Re-derived at the top of every bash block — -# blocks run in separate shells, so a value derived in an earlier block is not -# available here. Priority: explicit env overrides, Codex-style symlinks, then -# the Claude Code marketplace cache. Empty if none found; guard -# "$_IDSTACK/bin/..." calls accordingly. -_IDSTACK="" -# Marketplace cache holds one dir per installed version. Sort the basenames by -# numeric version fields, not lexically — plain sort ranks 3.9.0.0 above -# 3.10.0.0 and would pick a stale install once the minor hits double digits. -_idstack_cache_root="$HOME/.claude/plugins/cache/idstack/idstack" -_idstack_cache="" -if [ -d "$_idstack_cache_root" ]; then - _idstack_v=$(ls "$_idstack_cache_root" 2>/dev/null | sort -t. -k1,1n -k2,2n -k3,3n -k4,4n | tail -1) - [ -n "$_idstack_v" ] && _idstack_cache="$_idstack_cache_root/$_idstack_v" -fi -for _p in "${CLAUDE_PLUGIN_ROOT:-}" "${IDSTACK_HOME:-}" "$HOME/.agents/plugins/idstack" "$HOME/.agents/skills/idstack" "$_idstack_cache"; do - if [ -n "$_p" ] && [ -d "$_p" ]; then _IDSTACK="${_p%/}"; break; fi -done -"$_IDSTACK/bin/idstack-timeline-log" '{"skill":"needs-analysis","event":"completed"}' -``` - -Replace the JSON above with actual data from this session. Include skill-specific fields -where available (scores, counts, flags). Log synchronously (no background &). - -If you discover a non-obvious project-specific quirk during this session (LMS behavior, -import format issue, course structure pattern), also log it as a learning: - -```bash -# Resolve the idstack install dir. Re-derived at the top of every bash block — -# blocks run in separate shells, so a value derived in an earlier block is not -# available here. Priority: explicit env overrides, Codex-style symlinks, then -# the Claude Code marketplace cache. Empty if none found; guard -# "$_IDSTACK/bin/..." calls accordingly. -_IDSTACK="" -# Marketplace cache holds one dir per installed version. Sort the basenames by -# numeric version fields, not lexically — plain sort ranks 3.9.0.0 above -# 3.10.0.0 and would pick a stale install once the minor hits double digits. -_idstack_cache_root="$HOME/.claude/plugins/cache/idstack/idstack" -_idstack_cache="" -if [ -d "$_idstack_cache_root" ]; then - _idstack_v=$(ls "$_idstack_cache_root" 2>/dev/null | sort -t. -k1,1n -k2,2n -k3,3n -k4,4n | tail -1) - [ -n "$_idstack_v" ] && _idstack_cache="$_idstack_cache_root/$_idstack_v" -fi -for _p in "${CLAUDE_PLUGIN_ROOT:-}" "${IDSTACK_HOME:-}" "$HOME/.agents/plugins/idstack" "$HOME/.agents/skills/idstack" "$_idstack_cache"; do - if [ -n "$_p" ] && [ -d "$_p" ]; then _IDSTACK="${_p%/}"; break; fi -done -"$_IDSTACK/bin/idstack-learnings-log" '{"skill":"needs-analysis","type":"operational","key":"SHORT_KEY","insight":"DESCRIPTION","confidence":8,"source":"observed"}' -``` diff --git a/dist/codex/skills/idstack-pipeline/SKILL.md b/dist/codex/skills/idstack-pipeline/SKILL.md deleted file mode 100644 index 321801b..0000000 --- a/dist/codex/skills/idstack-pipeline/SKILL.md +++ /dev/null @@ -1,487 +0,0 @@ ---- -name: pipeline -description: | - Pipeline orchestrator for idstack. Chains skills from /needs-analysis through - /course-export in evidence-based order, auto-skipping completed skills. - Handles fresh starts (no manifest) and resumption (partial pipeline). (idstack) ---- -<!-- AUTO-GENERATED from SKILL.md.tmpl -- do not edit directly --> -<!-- Edit the .tmpl file instead. Regenerate: bin/idstack-gen-skills --> - - -## Preamble: Interaction Conventions - -idstack skills are designed to run in multiple CLIs (Claude Code, OpenAI Codex CLI, and -others). To stay portable, skill bodies use a few **concept names** that have a CLI-specific -implementation: - -- **AskUserQuestion** — when a skill says "ask via AskUserQuestion" or "using AskUserQuestion", - it means: present a single numbered multiple-choice question (e.g., "Which of these best - describes X? 1) ..., 2) ..., 3) Other") and stop, waiting for the user's next message - before proceeding. Ask **one** question at a time, never batch. In Claude Code this maps - to the `AskUserQuestion` tool; in Codex CLI (which has no analog) just emit the numbered - question as plain text and wait. -- **Agent / Skill (sub-task dispatch)** — when a skill says "if the Agent tool is available, - dispatch X as a sub-task," that's an optimization. If your CLI has no equivalent, fall - through to the inline written-out steps that follow — every skill that uses `Agent` ships - a sequential fallback alongside it. -- **Skill (cross-skill invocation)** — used only by `/idstack:pipeline`. In Claude Code this - invokes a child skill in-process via the `Skill` tool. In CLIs without that primitive, the - pipeline degrades to prompting the user to type the next skill name explicitly. -- **Skill invocation syntax in user-facing text** — when a skill prints "run `/idstack:foo`" - or links to `/idstack:foo` in a "Next steps" section, that's Claude Code's `/idstack:<name>` - invocation. **In Codex CLI, translate to `$foo`** (drop the `/idstack:` prefix, replace - the leading `/` with `$`). Apply this same translation when the skill emits handoff - instructions in reports, manifests, or AskUserQuestion options. Same body text, two - hosts; the model translates per-CLI on output. - -These are **directives to the model**, not magic words — interpret them as the protocol above. - -## Preamble: Update Check - -```bash -# Resolve the idstack install dir. Re-derived at the top of every bash block — -# blocks run in separate shells, so a value derived in an earlier block is not -# available here. Priority: explicit env overrides, Codex-style symlinks, then -# the Claude Code marketplace cache (highest version). Empty if none found; -# guard "$_IDSTACK/bin/..." calls accordingly. -# Canonical copy: templates/snippets/idstack-resolve.sh (the IDSTACK_RESOLVE -# placeholder in skill templates) — keep this block identical to it. -_IDSTACK="" -# Marketplace cache holds one dir per installed version. Sort the basenames by -# numeric version fields, not lexically — plain sort ranks 3.9.0.0 above -# 3.10.0.0 and would pick a stale install once the minor hits double digits. -_idstack_cache_root="$HOME/.claude/plugins/cache/idstack/idstack" -_idstack_cache="" -if [ -d "$_idstack_cache_root" ]; then - _idstack_v=$(ls "$_idstack_cache_root" 2>/dev/null | sort -t. -k1,1n -k2,2n -k3,3n -k4,4n | tail -1) - [ -n "$_idstack_v" ] && _idstack_cache="$_idstack_cache_root/$_idstack_v" -fi -for _p in "${CLAUDE_PLUGIN_ROOT:-}" "${IDSTACK_HOME:-}" "$HOME/.agents/plugins/idstack" "$HOME/.agents/skills/idstack" "$_idstack_cache"; do - if [ -n "$_p" ] && [ -d "$_p" ]; then _IDSTACK="${_p%/}"; break; fi -done -_UPD=$("$_IDSTACK/bin/idstack-update-check" 2>/dev/null || true) -[ -n "$_UPD" ] && echo "$_UPD" -``` - -If the output contains `UPDATE_AVAILABLE`: tell the user "A newer version of idstack is available. Run `cd $_IDSTACK && git pull && ./setup` to update. (The `./setup` step is required — it cleans up legacy symlinks.)" Then continue normally. - -## Preamble: Project Manifest - -Before starting, check for an existing project manifest. - -```bash -# (fresh shell — re-derive the install dir; see Preamble: Update Check) -_IDSTACK="" -# Marketplace cache holds one dir per installed version. Sort the basenames by -# numeric version fields, not lexically — plain sort ranks 3.9.0.0 above -# 3.10.0.0 and would pick a stale install once the minor hits double digits. -_idstack_cache_root="$HOME/.claude/plugins/cache/idstack/idstack" -_idstack_cache="" -if [ -d "$_idstack_cache_root" ]; then - _idstack_v=$(ls "$_idstack_cache_root" 2>/dev/null | sort -t. -k1,1n -k2,2n -k3,3n -k4,4n | tail -1) - [ -n "$_idstack_v" ] && _idstack_cache="$_idstack_cache_root/$_idstack_v" -fi -for _p in "${CLAUDE_PLUGIN_ROOT:-}" "${IDSTACK_HOME:-}" "$HOME/.agents/plugins/idstack" "$HOME/.agents/skills/idstack" "$_idstack_cache"; do - if [ -n "$_p" ] && [ -d "$_p" ]; then _IDSTACK="${_p%/}"; break; fi -done -if [ -f ".idstack/project.json" ]; then - echo "MANIFEST_EXISTS" - "$_IDSTACK/bin/idstack-migrate" .idstack/project.json 2>/dev/null || cat .idstack/project.json -else - echo "NO_MANIFEST" -fi -``` - -**If MANIFEST_EXISTS:** -- Read the manifest. If the JSON is malformed, report the specific parse error to the - user, offer to fix it, and STOP until it is valid. Never silently overwrite corrupt JSON. -- Preserve all existing sections when writing back. - -**If NO_MANIFEST:** -- This skill will create or update the manifest during its workflow. - -## Preamble: Preferences - -```bash -if [ -f ".idstack/project.json" ] && command -v python3 &>/dev/null; then - python3 -c " -import json, sys -try: - data = json.load(open('.idstack/project.json')) - prefs = data.get('preferences', {}) - v = prefs.get('verbosity', 'normal') - if v != 'normal': - print(f'VERBOSITY:{v}') -except: pass -" 2>/dev/null || true -fi -``` - -**If VERBOSITY:concise:** Keep explanations brief. Skip evidence citations inline -(still follow evidence-based recommendations, just don't cite tier codes in output). -**If VERBOSITY:detailed:** Include full evidence citations, alternative approaches -considered, and rationale for each recommendation. -**If VERBOSITY:normal or not shown:** Default behavior — cite evidence tiers inline, -explain key decisions, skip exhaustive alternatives. - -## Preamble: Designer Profile - -```bash -_PROFILE="$HOME/.idstack/profile.yaml" -if [ -f "$_PROFILE" ]; then - # Simple YAML parsing for experience_level (no dependency needed) - _EXP=$(grep -E '^experience_level:' "$_PROFILE" 2>/dev/null | sed 's/experience_level:[[:space:]]*//' | tr -d '"' | tr -d "'") - [ -n "$_EXP" ] && echo "EXPERIENCE:$_EXP" -else - echo "NO_PROFILE" -fi -``` - -**If EXPERIENCE:novice:** Provide more context for recommendations. Explain WHY each -step matters, not just what to do. Define jargon on first use. Offer examples. -**If EXPERIENCE:intermediate:** Standard explanations. Assume familiarity with -instructional design concepts but explain idstack-specific patterns. -**If EXPERIENCE:expert:** Be concise. Skip basic explanations. Focus on evidence -tiers, edge cases, and advanced considerations. Trust the user's domain knowledge. -**If NO_PROFILE:** On first run, after the main workflow is underway (not before), -mention: "Tip: create `~/.idstack/profile.yaml` with `experience_level: novice|intermediate|expert` -to adjust how much detail idstack provides." - -## Preamble: Context Recovery - -Check for session history and learnings from prior runs. - -```bash -# Context recovery: timeline + learnings -# (fresh shell — re-derive the install dir; see Preamble: Update Check) -_IDSTACK="" -# Marketplace cache holds one dir per installed version. Sort the basenames by -# numeric version fields, not lexically — plain sort ranks 3.9.0.0 above -# 3.10.0.0 and would pick a stale install once the minor hits double digits. -_idstack_cache_root="$HOME/.claude/plugins/cache/idstack/idstack" -_idstack_cache="" -if [ -d "$_idstack_cache_root" ]; then - _idstack_v=$(ls "$_idstack_cache_root" 2>/dev/null | sort -t. -k1,1n -k2,2n -k3,3n -k4,4n | tail -1) - [ -n "$_idstack_v" ] && _idstack_cache="$_idstack_cache_root/$_idstack_v" -fi -for _p in "${CLAUDE_PLUGIN_ROOT:-}" "${IDSTACK_HOME:-}" "$HOME/.agents/plugins/idstack" "$HOME/.agents/skills/idstack" "$_idstack_cache"; do - if [ -n "$_p" ] && [ -d "$_p" ]; then _IDSTACK="${_p%/}"; break; fi -done -_HAS_TIMELINE=0 -_HAS_LEARNINGS=0 -if [ -f ".idstack/timeline.jsonl" ]; then - _HAS_TIMELINE=1 - if command -v python3 &>/dev/null; then - python3 -c " -import json, sys -lines = open('.idstack/timeline.jsonl').readlines()[-200:] -events = [] -for line in lines: - try: events.append(json.loads(line)) - except: pass -if not events: - sys.exit(0) - -# Quality score trend -scores = [e for e in events if e.get('skill') == 'course-quality-review' and 'score' in e] -if scores: - trend = ' -> '.join(str(s['score']) for s in scores[-5:]) - print(f'QUALITY_TREND: {trend}') - last = scores[-1] - dims = last.get('dimensions', {}) - if dims: - tp = dims.get('teaching_presence', '?') - sp = dims.get('social_presence', '?') - cp = dims.get('cognitive_presence', '?') - print(f'LAST_PRESENCE: T={tp} S={sp} C={cp}') - -# Skills completed -completed = set() -for e in events: - if e.get('event') == 'completed': - completed.add(e.get('skill', '')) -# No f-string here: nesting same-type quotes in a replacement field is a -# SyntaxError before Python 3.12, and macOS system python3 is 3.9. -print('SKILLS_COMPLETED: ' + ','.join(sorted(completed))) - -# Last skill run -last_completed = [e for e in events if e.get('event') == 'completed'] -if last_completed: - last = last_completed[-1] - print(f'LAST_SKILL: {last.get(\"skill\",\"?\")} at {last.get(\"ts\",\"?\")}') - -# Pipeline progression. course-import is the alternative entry point — it -# joins the chain before learning-objectives. -pipeline = [ - ('needs-analysis', 'learning-objectives'), - ('course-import', 'learning-objectives'), - ('learning-objectives', 'assessment-design'), - ('assessment-design', 'course-builder'), - ('course-builder', 'course-quality-review'), - ('course-quality-review', 'accessibility-review'), - ('accessibility-review', 'red-team'), - ('red-team', 'course-export'), -] -for prev, nxt in pipeline: - if prev in completed and nxt not in completed: - print(f'SUGGESTED_NEXT: {nxt}') - break -" 2>/dev/null || true - else - # No python3: show last 3 skill names only - tail -3 .idstack/timeline.jsonl 2>/dev/null | grep -o '"skill":"[^"]*"' | sed 's/"skill":"//;s/"//' | while read s; do echo "RECENT_SKILL: $s"; done - fi -fi -if [ -f ".idstack/learnings.jsonl" ]; then - _HAS_LEARNINGS=1 - _LEARN_COUNT=$(wc -l < .idstack/learnings.jsonl 2>/dev/null | tr -d ' ') - echo "LEARNINGS: $_LEARN_COUNT" - if [ "$_LEARN_COUNT" -gt 0 ] 2>/dev/null; then - "$_IDSTACK/bin/idstack-learnings-search" --limit 3 2>/dev/null || true - fi -fi -``` - -**If QUALITY_TREND is shown:** Synthesize a welcome-back message. Example: "Welcome back. -Quality score trend: 62 -> 68 -> 72 over 3 reviews. Last skill: /idstack:learning-objectives." -Keep it to 2-3 sentences. If any dimension in LAST_PRESENCE is consistently below 5/10, -mention it as a recurring pattern with its evidence citation. - -**If LAST_SKILL is shown but no QUALITY_TREND:** Just mention the last skill run. -Example: "Welcome back. Last session you ran /idstack:course-import." - -**If SUGGESTED_NEXT is shown:** Mention the suggested next skill naturally. -Example: "Based on your progress, /idstack:assessment-design is the natural next step." - -**If LEARNINGS > 0:** Mention relevant learnings if they apply to this skill's domain. -Example: "Reminder: this Canvas instance uses custom rubric formatting (discovered during import)." - ---- - -# Pipeline Orchestrator - -You are the idstack pipeline orchestrator. Your job is to guide the user through the -full instructional design pipeline by invoking each skill in sequence, automatically -skipping skills that have already been completed. - -## Pipeline Order - -The canonical pipeline order is: - -``` -1. /idstack:needs-analysis — Three-level needs assessment -2. /idstack:learning-objectives — Evidence-based ILO development -3. /idstack:assessment-design — Assessment & rubric design -4. /idstack:course-builder — Generate course content -5. /idstack:course-quality-review — Quality audit (QM + CoI) -6. /idstack:accessibility-review — WCAG + UDL review -7. /idstack:red-team — Adversarial audit -8. /idstack:course-export — Package for LMS -``` - -**Alternative entry point:** If the user has run `/idstack:course-import` (visible in -timeline), the pipeline starts at `/idstack:learning-objectives` (skipping -`/idstack:needs-analysis`, since the import populated the manifest with equivalent data). - -## Determining Completed Skills - -Read `.idstack/timeline.jsonl` to find skills with `"event": "completed"` entries. - -```bash -if [ -f ".idstack/timeline.jsonl" ]; then - python3 -c " -import json -events = [] -for line in open('.idstack/timeline.jsonl'): - try: events.append(json.loads(line)) - except: pass -completed = set() -for e in events: - if e.get('event') == 'completed': - completed.add(e.get('skill', '')) -print('COMPLETED:' + ','.join(sorted(completed))) -" 2>/dev/null || echo "COMPLETED:" -else - echo "COMPLETED:" -fi -``` - -## Workflow - -### Step 1: Determine Starting Point - -Parse the COMPLETED output. The pipeline skills in order are: -- `needs-analysis` -- `learning-objectives` -- `assessment-design` -- `course-builder` -- `course-quality-review` -- `accessibility-review` -- `red-team` -- `course-export` - -Find the first skill in this list that is NOT in the completed set. That is the -starting point. - -**Special cases:** -- If `course-import` is completed but `needs-analysis` is not, skip `needs-analysis` - (import provides equivalent manifest data). -- If ALL skills are completed, tell the user via AskUserQuestion: "All pipeline skills have been completed. What would you like to do?" Options: - - **Regenerate the course dashboard** — reads each per-skill report, refreshes `.idstack/exports/<course-slug>/index.html` with the latest cross-cutting view (no skills re-run). Recommended after editing per-skill outputs by hand. - - **Re-run a specific skill** — e.g., `/idstack:course-quality-review` if recent changes warrant another pass. - - **Exit** — leave everything as-is. - If the user picks "Regenerate the course dashboard," skip directly to Step 4 (Generate Course Dashboard). - -### Step 2: Present Pipeline Status - -Show the user a status table before starting: - -``` -Pipeline Status: - [done] /idstack:needs-analysis - [done] /idstack:learning-objectives - [next] /idstack:assessment-design <-- starting here - [ ] /idstack:course-builder - [ ] /idstack:course-quality-review - [ ] /idstack:accessibility-review - [ ] /idstack:red-team - [ ] /idstack:course-export -``` - -Ask: "Ready to continue the pipeline from /idstack:assessment-design?" (using AskUserQuestion -with options: "Yes, continue" / "Start from a different skill" / "Re-run a completed skill") - -If the user picks "Start from a different skill" or "Re-run a completed skill", -ask which one using AskUserQuestion with the skill list as options. - -### Step 3: Execute Skills in Sequence - -For each skill from the starting point onward: - -1. Announce: "Starting /idstack:<skill-name>..." -2. Invoke the skill using the `Skill` tool with the **plugin-namespaced** skill name - (e.g., `skill: "idstack:needs-analysis"`). Plugin skills are addressed as - `plugin:skill` — a bare `"needs-analysis"` does not resolve in Claude Code. -3. The skill will run its full workflow including all AskUserQuestion interactions -4. When the skill completes (logs to timeline), **execute Step 4 (Generate Course Dashboard) inline** to refresh `.idstack/exports/<course-slug>/index.html` against the new per-skill report. This keeps the dashboard fresh if the designer pauses partway through. -5. Announce completion and move to next. - -**If the Skill tool is NOT available** (e.g., on Codex CLI or any host without a primitive -to invoke a sibling skill in-process), degrade gracefully: after announcing the next skill, -print exactly what the user should type to invoke it (e.g., "Type `$needs-analysis` to run -the next stage, then resume the pipeline by typing `$pipeline` again") and STOP. The user -runs the skill themselves; the pipeline picks up on the next `$pipeline` invocation by re- -reading the timeline and continuing from the new starting point. Generate the course -dashboard (Step 4) before stopping so the partial-run dashboard is up to date. - -**Between skills**, briefly announce the transition: -"[skill-name] complete. Course dashboard refreshed. Moving to /idstack:<next-skill>..." - -### Step 4: Generate Course Dashboard - -After each skill completes (or after the orchestrator finishes the run, including partial runs), assemble the per-skill HTML reports and the manifest into a single `index.html` course dashboard so the designer (and any stakeholder) can read the full picture in one place. - -**When this step runs:** - -- Inline after each skill completes during the orchestrator's main loop (Step 3, item 4) — so a partial pipeline still leaves a useful dashboard behind if the designer pauses. -- As a one-shot regeneration when the "Regenerate the course dashboard" branch from Step 1 is selected (no skills are re-run; this step is the entire body of that branch). - -**Prep.** Compute the course slug and prepare the export folder: - -```bash -# Resolve the idstack install dir. Re-derived at the top of every bash block — -# blocks run in separate shells, so a value derived in an earlier block is not -# available here. Priority: explicit env overrides, Codex-style symlinks, then -# the Claude Code marketplace cache. Empty if none found; guard -# "$_IDSTACK/bin/..." calls accordingly. -_IDSTACK="" -# Marketplace cache holds one dir per installed version. Sort the basenames by -# numeric version fields, not lexically — plain sort ranks 3.9.0.0 above -# 3.10.0.0 and would pick a stale install once the minor hits double digits. -_idstack_cache_root="$HOME/.claude/plugins/cache/idstack/idstack" -_idstack_cache="" -if [ -d "$_idstack_cache_root" ]; then - _idstack_v=$(ls "$_idstack_cache_root" 2>/dev/null | sort -t. -k1,1n -k2,2n -k3,3n -k4,4n | tail -1) - [ -n "$_idstack_v" ] && _idstack_cache="$_idstack_cache_root/$_idstack_v" -fi -for _p in "${CLAUDE_PLUGIN_ROOT:-}" "${IDSTACK_HOME:-}" "$HOME/.agents/plugins/idstack" "$HOME/.agents/skills/idstack" "$_idstack_cache"; do - if [ -n "$_p" ] && [ -d "$_p" ]; then _IDSTACK="${_p%/}"; break; fi -done -_PROJECT_NAME=$(python3 -c "import json; print(json.load(open('.idstack/project.json')).get('project_name',''))" 2>/dev/null || echo "") -_SLUG=$("$_IDSTACK/bin/idstack-slugify" "$_PROJECT_NAME" 2>/dev/null || echo "untitled-course") -_EXPORT_DIR=".idstack/exports/$_SLUG" -mkdir -p "$_EXPORT_DIR/assets" -cp -f "$_IDSTACK/templates/assets/idstack.css" "$_EXPORT_DIR/assets/idstack.css" -echo "Dashboard: $_EXPORT_DIR/index.html" -``` - -**Inputs.** Read each per-skill HTML report file if it exists (they're already in `$_EXPORT_DIR/`): - -- `$_EXPORT_DIR/needs-analysis.html` -- `$_EXPORT_DIR/learning-objectives.html` -- `$_EXPORT_DIR/assessment-design.html` -- `$_EXPORT_DIR/course-builder.html` -- `$_EXPORT_DIR/course-quality-review.html` -- `$_EXPORT_DIR/accessibility-review.html` -- `$_EXPORT_DIR/red-team.html` -- `$_EXPORT_DIR/course-export.html` (if present) -- `$_EXPORT_DIR/course-import.html` (if present) - -Also read `.idstack/project.json` for project_name, scores (`quality_review.overall_score`, `accessibility_review.score.overall`, `red_team_audit.confidence_score`), and each section's `report_path`. - -**Output.** Write `$_EXPORT_DIR/index.html`, following the structure of `templates/index.html.tmpl`. Customize: - -- **`{{project_name}}`:** from the manifest top-level. -- **`{{pipeline_run_status}}`:** e.g., "complete · 8 of 8 skills" or "partial · 4 of 8 skills". -- **Cross-cutting summary** (replaces `{{cross_cutting_summary_2_to_3_paragraphs}}`): 2–3 paragraphs of cross-cutting synthesis. Designed to be read by a designer (or stakeholder) who hasn't yet opened the per-skill reports. Lead with the verdict, follow with the themes that recur across multiple skills, end with where to start. -- **Readiness scoreboard** — populate the four `<div class="score verdict-{verdict}">` cards (Quality, Accessibility, Red-team confidence, Overall verdict). Verdict values: `ready` (green border) / `issues` (amber) / `blocked` (red) / `pending` (grey, when skill hasn't run yet). Thresholds match `bin/idstack-status --readiness`. -- **Pipeline status table** — one row per pipeline skill. `status-done` if the per-skill HTML report file exists, `status-pending` otherwise. `signal` column carries a 1-line skill-specific summary (e.g., for course-quality-review: `overall_score/100`; for red-team: `confidence_score/100, N critical`). Link the report cell to the relative HTML filename (e.g., `<a href="needs-analysis.html">needs-analysis.html</a>`). Add an optional course-import row when that report is present. -- **Top cross-cutting issues** (`<ol>` items): 3–5 highest-impact findings that appear in or affect multiple per-skill reports. Each item shows the source skill(s) and the evidence tier as a `<cite class="citation">` element. -- **LMS export artifacts** — list every file present at the top level of `$_EXPORT_DIR/` whose name matches a known LMS package (`course-export.imscc`, `scorm-export.zip`). If none, write: `<p>No LMS packages produced yet. Run <code>/idstack:course-export</code> to package this course.</p>`. -- **Where to start** — one paragraph. The single change (or 2–3 changes) that would address the largest number of cross-cutting concerns. Anchored to a specific finding id in a specific per-skill report so the designer can act on it (e.g., "Address `assess-1` in <code>assessment-design.html</code> first…"). - -The dashboard is overwritten on every pipeline run — historical dashboards are not retained. The timeline at `.idstack/timeline.jsonl` carries the run history. - -### Step 5: Pipeline Complete - -When all remaining skills have been executed: -- Announce: "Pipeline complete. Your course has been through all 8 stages." -- Confirm the dashboard path: "Course dashboard at `.idstack/exports/<course-slug>/index.html` — open it in any browser for the cross-cutting view; the per-skill HTML reports in the same folder carry the full detail. Zip the folder to hand it to a stakeholder." -- If `/idstack:course-quality-review` produced a score, show it. -- Remind the user they can re-run any skill individually if needed, and that re-running `/idstack:pipeline` regenerates the course dashboard. -- Log the pipeline run so "the pipeline was run" is recoverable from the timeline: - -```bash -# Resolve the idstack install dir. Re-derived at the top of every bash block — -# blocks run in separate shells, so a value derived in an earlier block is not -# available here. Priority: explicit env overrides, Codex-style symlinks, then -# the Claude Code marketplace cache. Empty if none found; guard -# "$_IDSTACK/bin/..." calls accordingly. -_IDSTACK="" -# Marketplace cache holds one dir per installed version. Sort the basenames by -# numeric version fields, not lexically — plain sort ranks 3.9.0.0 above -# 3.10.0.0 and would pick a stale install once the minor hits double digits. -_idstack_cache_root="$HOME/.claude/plugins/cache/idstack/idstack" -_idstack_cache="" -if [ -d "$_idstack_cache_root" ]; then - _idstack_v=$(ls "$_idstack_cache_root" 2>/dev/null | sort -t. -k1,1n -k2,2n -k3,3n -k4,4n | tail -1) - [ -n "$_idstack_v" ] && _idstack_cache="$_idstack_cache_root/$_idstack_v" -fi -for _p in "${CLAUDE_PLUGIN_ROOT:-}" "${IDSTACK_HOME:-}" "$HOME/.agents/plugins/idstack" "$HOME/.agents/skills/idstack" "$_idstack_cache"; do - if [ -n "$_p" ] && [ -d "$_p" ]; then _IDSTACK="${_p%/}"; break; fi -done -[ -n "$_IDSTACK" ] && "$_IDSTACK/bin/idstack-timeline-log" '{"skill":"pipeline","event":"completed"}' -``` - -## Important Rules - -- **One skill at a time.** Never run skills in parallel. Each skill needs the - output of the previous one. -- **Don't interfere.** When a skill is running, let it drive. Don't add your own - questions or commentary between the skill's AskUserQuestion prompts. -- **Respect the user.** If at any point the user says "stop", "pause", or "that's - enough for now", stop the pipeline gracefully. Their progress is saved in - timeline.jsonl and they can resume later with `/idstack:pipeline`. -- **No quality gate yet.** In v2.0, skip logic is purely completion-based. Quality-gated - skipping (only skip if score > threshold) is planned for a future version. diff --git a/dist/codex/skills/idstack-red-team/SKILL.md b/dist/codex/skills/idstack-red-team/SKILL.md deleted file mode 100644 index 185daa8..0000000 --- a/dist/codex/skills/idstack-red-team/SKILL.md +++ /dev/null @@ -1,773 +0,0 @@ ---- -name: red-team -description: | - Adversarial course design audit across 5 dimensions: alignment stress test, - evidence verification, cognitive load analysis, learner persona simulation, - and prerequisite chain integrity. Produces a confidence score (0-100). - Runs in a clean-context sub-agent so synthesis is unbiased by build history. - Works standalone or reads from the idstack project manifest. (idstack) ---- -<!-- AUTO-GENERATED from SKILL.md.tmpl -- do not edit directly --> -<!-- Edit the .tmpl file instead. Regenerate: bin/idstack-gen-skills --> - - -## Preamble: Interaction Conventions - -idstack skills are designed to run in multiple CLIs (Claude Code, OpenAI Codex CLI, and -others). To stay portable, skill bodies use a few **concept names** that have a CLI-specific -implementation: - -- **AskUserQuestion** — when a skill says "ask via AskUserQuestion" or "using AskUserQuestion", - it means: present a single numbered multiple-choice question (e.g., "Which of these best - describes X? 1) ..., 2) ..., 3) Other") and stop, waiting for the user's next message - before proceeding. Ask **one** question at a time, never batch. In Claude Code this maps - to the `AskUserQuestion` tool; in Codex CLI (which has no analog) just emit the numbered - question as plain text and wait. -- **Agent / Skill (sub-task dispatch)** — when a skill says "if the Agent tool is available, - dispatch X as a sub-task," that's an optimization. If your CLI has no equivalent, fall - through to the inline written-out steps that follow — every skill that uses `Agent` ships - a sequential fallback alongside it. -- **Skill (cross-skill invocation)** — used only by `/idstack:pipeline`. In Claude Code this - invokes a child skill in-process via the `Skill` tool. In CLIs without that primitive, the - pipeline degrades to prompting the user to type the next skill name explicitly. -- **Skill invocation syntax in user-facing text** — when a skill prints "run `/idstack:foo`" - or links to `/idstack:foo` in a "Next steps" section, that's Claude Code's `/idstack:<name>` - invocation. **In Codex CLI, translate to `$foo`** (drop the `/idstack:` prefix, replace - the leading `/` with `$`). Apply this same translation when the skill emits handoff - instructions in reports, manifests, or AskUserQuestion options. Same body text, two - hosts; the model translates per-CLI on output. - -These are **directives to the model**, not magic words — interpret them as the protocol above. - -## Preamble: Update Check - -```bash -# Resolve the idstack install dir. Re-derived at the top of every bash block — -# blocks run in separate shells, so a value derived in an earlier block is not -# available here. Priority: explicit env overrides, Codex-style symlinks, then -# the Claude Code marketplace cache (highest version). Empty if none found; -# guard "$_IDSTACK/bin/..." calls accordingly. -# Canonical copy: templates/snippets/idstack-resolve.sh (the IDSTACK_RESOLVE -# placeholder in skill templates) — keep this block identical to it. -_IDSTACK="" -# Marketplace cache holds one dir per installed version. Sort the basenames by -# numeric version fields, not lexically — plain sort ranks 3.9.0.0 above -# 3.10.0.0 and would pick a stale install once the minor hits double digits. -_idstack_cache_root="$HOME/.claude/plugins/cache/idstack/idstack" -_idstack_cache="" -if [ -d "$_idstack_cache_root" ]; then - _idstack_v=$(ls "$_idstack_cache_root" 2>/dev/null | sort -t. -k1,1n -k2,2n -k3,3n -k4,4n | tail -1) - [ -n "$_idstack_v" ] && _idstack_cache="$_idstack_cache_root/$_idstack_v" -fi -for _p in "${CLAUDE_PLUGIN_ROOT:-}" "${IDSTACK_HOME:-}" "$HOME/.agents/plugins/idstack" "$HOME/.agents/skills/idstack" "$_idstack_cache"; do - if [ -n "$_p" ] && [ -d "$_p" ]; then _IDSTACK="${_p%/}"; break; fi -done -_UPD=$("$_IDSTACK/bin/idstack-update-check" 2>/dev/null || true) -[ -n "$_UPD" ] && echo "$_UPD" -``` - -If the output contains `UPDATE_AVAILABLE`: tell the user "A newer version of idstack is available. Run `cd $_IDSTACK && git pull && ./setup` to update. (The `./setup` step is required — it cleans up legacy symlinks.)" Then continue normally. - -## Preamble: Project Manifest - -Before starting, check for an existing project manifest. - -```bash -# (fresh shell — re-derive the install dir; see Preamble: Update Check) -_IDSTACK="" -# Marketplace cache holds one dir per installed version. Sort the basenames by -# numeric version fields, not lexically — plain sort ranks 3.9.0.0 above -# 3.10.0.0 and would pick a stale install once the minor hits double digits. -_idstack_cache_root="$HOME/.claude/plugins/cache/idstack/idstack" -_idstack_cache="" -if [ -d "$_idstack_cache_root" ]; then - _idstack_v=$(ls "$_idstack_cache_root" 2>/dev/null | sort -t. -k1,1n -k2,2n -k3,3n -k4,4n | tail -1) - [ -n "$_idstack_v" ] && _idstack_cache="$_idstack_cache_root/$_idstack_v" -fi -for _p in "${CLAUDE_PLUGIN_ROOT:-}" "${IDSTACK_HOME:-}" "$HOME/.agents/plugins/idstack" "$HOME/.agents/skills/idstack" "$_idstack_cache"; do - if [ -n "$_p" ] && [ -d "$_p" ]; then _IDSTACK="${_p%/}"; break; fi -done -if [ -f ".idstack/project.json" ]; then - echo "MANIFEST_EXISTS" - "$_IDSTACK/bin/idstack-migrate" .idstack/project.json 2>/dev/null || cat .idstack/project.json -else - echo "NO_MANIFEST" -fi -``` - -**If MANIFEST_EXISTS:** -- Read the manifest. If the JSON is malformed, report the specific parse error to the - user, offer to fix it, and STOP until it is valid. Never silently overwrite corrupt JSON. -- Preserve all existing sections when writing back. - -**If NO_MANIFEST:** -- This skill will create or update the manifest during its workflow. - -## Preamble: Preferences - -```bash -if [ -f ".idstack/project.json" ] && command -v python3 &>/dev/null; then - python3 -c " -import json, sys -try: - data = json.load(open('.idstack/project.json')) - prefs = data.get('preferences', {}) - v = prefs.get('verbosity', 'normal') - if v != 'normal': - print(f'VERBOSITY:{v}') -except: pass -" 2>/dev/null || true -fi -``` - -**If VERBOSITY:concise:** Keep explanations brief. Skip evidence citations inline -(still follow evidence-based recommendations, just don't cite tier codes in output). -**If VERBOSITY:detailed:** Include full evidence citations, alternative approaches -considered, and rationale for each recommendation. -**If VERBOSITY:normal or not shown:** Default behavior — cite evidence tiers inline, -explain key decisions, skip exhaustive alternatives. - -## Preamble: Designer Profile - -```bash -_PROFILE="$HOME/.idstack/profile.yaml" -if [ -f "$_PROFILE" ]; then - # Simple YAML parsing for experience_level (no dependency needed) - _EXP=$(grep -E '^experience_level:' "$_PROFILE" 2>/dev/null | sed 's/experience_level:[[:space:]]*//' | tr -d '"' | tr -d "'") - [ -n "$_EXP" ] && echo "EXPERIENCE:$_EXP" -else - echo "NO_PROFILE" -fi -``` - -**If EXPERIENCE:novice:** Provide more context for recommendations. Explain WHY each -step matters, not just what to do. Define jargon on first use. Offer examples. -**If EXPERIENCE:intermediate:** Standard explanations. Assume familiarity with -instructional design concepts but explain idstack-specific patterns. -**If EXPERIENCE:expert:** Be concise. Skip basic explanations. Focus on evidence -tiers, edge cases, and advanced considerations. Trust the user's domain knowledge. -**If NO_PROFILE:** On first run, after the main workflow is underway (not before), -mention: "Tip: create `~/.idstack/profile.yaml` with `experience_level: novice|intermediate|expert` -to adjust how much detail idstack provides." - -## Preamble: Context Recovery - -Check for session history and learnings from prior runs. - -```bash -# Context recovery: timeline + learnings -# (fresh shell — re-derive the install dir; see Preamble: Update Check) -_IDSTACK="" -# Marketplace cache holds one dir per installed version. Sort the basenames by -# numeric version fields, not lexically — plain sort ranks 3.9.0.0 above -# 3.10.0.0 and would pick a stale install once the minor hits double digits. -_idstack_cache_root="$HOME/.claude/plugins/cache/idstack/idstack" -_idstack_cache="" -if [ -d "$_idstack_cache_root" ]; then - _idstack_v=$(ls "$_idstack_cache_root" 2>/dev/null | sort -t. -k1,1n -k2,2n -k3,3n -k4,4n | tail -1) - [ -n "$_idstack_v" ] && _idstack_cache="$_idstack_cache_root/$_idstack_v" -fi -for _p in "${CLAUDE_PLUGIN_ROOT:-}" "${IDSTACK_HOME:-}" "$HOME/.agents/plugins/idstack" "$HOME/.agents/skills/idstack" "$_idstack_cache"; do - if [ -n "$_p" ] && [ -d "$_p" ]; then _IDSTACK="${_p%/}"; break; fi -done -_HAS_TIMELINE=0 -_HAS_LEARNINGS=0 -if [ -f ".idstack/timeline.jsonl" ]; then - _HAS_TIMELINE=1 - if command -v python3 &>/dev/null; then - python3 -c " -import json, sys -lines = open('.idstack/timeline.jsonl').readlines()[-200:] -events = [] -for line in lines: - try: events.append(json.loads(line)) - except: pass -if not events: - sys.exit(0) - -# Quality score trend -scores = [e for e in events if e.get('skill') == 'course-quality-review' and 'score' in e] -if scores: - trend = ' -> '.join(str(s['score']) for s in scores[-5:]) - print(f'QUALITY_TREND: {trend}') - last = scores[-1] - dims = last.get('dimensions', {}) - if dims: - tp = dims.get('teaching_presence', '?') - sp = dims.get('social_presence', '?') - cp = dims.get('cognitive_presence', '?') - print(f'LAST_PRESENCE: T={tp} S={sp} C={cp}') - -# Skills completed -completed = set() -for e in events: - if e.get('event') == 'completed': - completed.add(e.get('skill', '')) -# No f-string here: nesting same-type quotes in a replacement field is a -# SyntaxError before Python 3.12, and macOS system python3 is 3.9. -print('SKILLS_COMPLETED: ' + ','.join(sorted(completed))) - -# Last skill run -last_completed = [e for e in events if e.get('event') == 'completed'] -if last_completed: - last = last_completed[-1] - print(f'LAST_SKILL: {last.get(\"skill\",\"?\")} at {last.get(\"ts\",\"?\")}') - -# Pipeline progression. course-import is the alternative entry point — it -# joins the chain before learning-objectives. -pipeline = [ - ('needs-analysis', 'learning-objectives'), - ('course-import', 'learning-objectives'), - ('learning-objectives', 'assessment-design'), - ('assessment-design', 'course-builder'), - ('course-builder', 'course-quality-review'), - ('course-quality-review', 'accessibility-review'), - ('accessibility-review', 'red-team'), - ('red-team', 'course-export'), -] -for prev, nxt in pipeline: - if prev in completed and nxt not in completed: - print(f'SUGGESTED_NEXT: {nxt}') - break -" 2>/dev/null || true - else - # No python3: show last 3 skill names only - tail -3 .idstack/timeline.jsonl 2>/dev/null | grep -o '"skill":"[^"]*"' | sed 's/"skill":"//;s/"//' | while read s; do echo "RECENT_SKILL: $s"; done - fi -fi -if [ -f ".idstack/learnings.jsonl" ]; then - _HAS_LEARNINGS=1 - _LEARN_COUNT=$(wc -l < .idstack/learnings.jsonl 2>/dev/null | tr -d ' ') - echo "LEARNINGS: $_LEARN_COUNT" - if [ "$_LEARN_COUNT" -gt 0 ] 2>/dev/null; then - "$_IDSTACK/bin/idstack-learnings-search" --limit 3 2>/dev/null || true - fi -fi -``` - -**If QUALITY_TREND is shown:** Synthesize a welcome-back message. Example: "Welcome back. -Quality score trend: 62 -> 68 -> 72 over 3 reviews. Last skill: /idstack:learning-objectives." -Keep it to 2-3 sentences. If any dimension in LAST_PRESENCE is consistently below 5/10, -mention it as a recurring pattern with its evidence citation. - -**If LAST_SKILL is shown but no QUALITY_TREND:** Just mention the last skill run. -Example: "Welcome back. Last session you ran /idstack:course-import." - -**If SUGGESTED_NEXT is shown:** Mention the suggested next skill naturally. -Example: "Based on your progress, /idstack:assessment-design is the natural next step." - -**If LEARNINGS > 0:** Mention relevant learnings if they apply to this skill's domain. -Example: "Reminder: this Canvas instance uses custom rubric formatting (discovered during import)." - ---- - -**Skill-specific manifest check:** If the manifest `red_team_audit` section already has data, -ask the user: "I see you've already run this skill. Want to update the results or start fresh?" - -# Red Team — Adversarial Course Design Audit - -This skill audits the course adversarially. It assumes the course is broken until proven otherwise. - -It is NOT a quality review (`/idstack:course-quality-review` does that). Quality review asks "does this course meet standards?" Red team asks "prove this course actually works." - -Five adversarial dimensions: -1. **Alignment Stress Test** — Do assessments actually measure what objectives claim? -2. **Evidence Verification** — Are the evidence citations accurate and current? -3. **Cognitive Load Analysis** — Will learners' working memory be overwhelmed? -4. **Learner Persona Simulation** — Would specific learner types actually succeed? -5. **Prerequisite Chain Integrity** — Are there hidden dependency gaps? - -The output is a confidence score (0-100): "How confident are we this course works?" - -## Why a clean-context sub-agent - -If the same Claude session helped build the course, it has sunk-cost bias toward its own design choices. Red team work happens in a freshly-spawned sub-agent that has no prior conversation history — only the manifest and course files, which is the same view a real student gets. - -The sub-agent (the **orchestrator**) runs the full audit, writes a structured HTML report under `.idstack/exports/<course-slug>/red-team.html`, and returns a short executive summary including the report path. The parent (this skill) then offers to apply fixes in-context, since the parent already knows the course structure and is good at editing. - ---- - -## Workflow - -1. **Pre-flight**: confirm scope and focus -2. **Spawn orchestrator**: clean-context sub-agent runs the audit, writes the report -3. **Surface summary**: parent shows score + severity counts + top critical finding -4. **Triage**: ask which severity bucket to address -5. **Apply fixes**: parent edits course files in-context -6. **Update manifest**: parent writes `red_team_audit` section from the report file - -No automatic re-verification. If the user wants to confirm fixes hold, they re-run `/idstack:red-team`. - ---- - -### Step 1: Pre-flight - -The preamble above already ran the manifest check. Now confirm scope. - -**Determine course inputs:** -- If `MANIFEST_EXISTS`: the orchestrator will read all sections (`needs_analysis`, `learning_objectives`, `assessments`, `course_content`, `quality_review`, `accessibility_review`). -- If `NO_MANIFEST`: ask the user to provide objectives, assessments, module sequence, and target audience. Capture answers as a brief block to pass to the orchestrator. Standalone mode reduces precision on Dimensions 1 (alignment) and 5 (prerequisites). - -**Ask one focus question** via AskUserQuestion: - -> "Any specific angle to red-team, or a full sweep?" - -Options: -- **Full sweep (recommended)** — all 5 dimensions at equal depth -- **Assessment gaming** — bias depth toward Dimension 1 (alignment) -- **Cognitive overload** — bias depth toward Dimension 3 (cognitive load) -- **Persona accessibility** — bias depth toward Dimension 4 (personas) -- **Evidence accuracy** — bias depth toward Dimension 2 (evidence) - -Save the user's choice as `FOCUS` for the orchestrator brief. - -### Step 2: Spawn the red-team orchestrator - -Use the **Agent tool** with `subagent_type=general-purpose`. The prompt is the full contents of the `<orchestrator-brief>` block below, with these substitutions performed before invoking: - -- `{{FOCUS}}` → the user's choice from Step 1 (or `Full sweep` by default) -- `{{MANIFEST_INFO}}` → either `Manifest at .idstack/project.json — read it directly.` or, in standalone mode, the captured course information from Step 1 -- `{{COURSE_FILES_HINT}}` → if the manifest has `course_content.content_dir`, set this to that path; otherwise `Look under ./course/ or ./modules/ for generated course files.` - -Then call Agent. Block on its return. - -<orchestrator-brief> - -You are an adversarial course design auditor. You have NO context from prior sessions. You did not help build this course; you are seeing it fresh. Your job is to find every way it could fail learners — not to validate the design. - -This is a stress test, not a quality review. Assume the course is broken until proven otherwise. - -**Inputs:** -- {{MANIFEST_INFO}} -- Course files: {{COURSE_FILES_HINT}} -- Focus area: {{FOCUS}} - -**Manifest integrity:** if the manifest JSON is malformed, stop and return an error message naming the parse error. Never silently overwrite. - -## Evidence Tiers - -Every challenge cites its evidence tier: -- [T1] RCTs, meta-analyses with learning outcome measures -- [T2] Quasi-experimental with appropriate controls -- [T3] Systematic reviews (synthesis of mixed evidence) -- [T4] Observational / pre-post without comparison groups -- [T5] Expert opinion, literature reviews, theoretical frameworks - -When multiple tiers apply, cite the strongest. - -## Focus handling - -If `{{FOCUS}}` is `Full sweep`, audit all 5 dimensions at equal depth. -Otherwise, audit the named dimension at full depth and cover the others at reduced depth (3-5 findings each, no exhaustive matrices). - -## Dispatch - -If you have access to the **Agent tool**, dispatch the 5 dimensions in parallel as nested sub-agents using the briefs in "Dimension Briefs" below. Wait for all 5 to return, then deduplicate findings. - -If you do NOT have Agent access, run the dimensions sequentially using the same briefs. - ---- - -## Dimension Briefs - -### Dimension 1 — Alignment Stress Test - -For every learning objective and assessment pair, challenge the alignment. - -**Objective → Assessment match:** -- Does the assessment actually measure the stated objective, or does it test something adjacent? -- If the objective says "analyze" (Bloom's level 4), does the assessment require analysis or just recall (level 1)? Flag Bloom's level mismatches. - [Alignment-14] [T1] — retrieval practice and Bloom's levels interact. - [Alignment-7] [T3] — measurable verbs alone cannot guarantee correct Bloom's classification. - [Alignment-12] [T2] — internal assumptions of revised Bloom's taxonomy require probing. -- Flag any objective with no matching assessment (untested objective). - [Alignment-2] [T5] — constructive alignment requires every objective to be assessed. -- Flag any assessment with no matching objective (orphaned assessment). - [Alignment-2] [T5] — assessments without aligned objectives violate constructive alignment. - -**Activity → Objective match:** -- Does the course include activities that prepare learners for each assessment? -- Flag objectives where the assessment tests something learners never practiced. - [Alignment-1] [T5] — constructive alignment requires objective-activity-assessment coherence. - [Alignment-16] [T4] — students perceive misalignment between activities and assessments as unfair. - -### Dimension 2 — Evidence Verification - -Check every evidence citation in the manifest or course design for accuracy. - -**Tier verification:** -- Is each citation assigned the correct evidence tier? - [Evaluation-1] [T3] — evaluation rigor requires method-matched evidence claims. -- Flag any citation where the tier seems too high for the study type. - [Evaluation-2] [T5] — program evaluation models define study-type-to-evidence-level mappings. -- Flag T4/T5 citations used to support high-stakes design decisions. - [Evaluation-5] [T5] — overreliance on low-tier evidence undermines validity. - -**Currency check (if WebSearch available):** -- For each T1/T2 citation, search for newer meta-analyses or RCTs that might update or contradict the finding. - [Assessment-18] [T3] — systematic reviews of meta-analyses reveal how evidence evolves. -- Only flag contradictions from clearly relevant papers. Ignore tangential matches. -- Check for retractions of cited papers. -- If WebSearch is unavailable, set `mode: limited` in the report and note: "currency verification requires internet." - -### Dimension 3 — Cognitive Load Analysis - -Estimate cognitive load per module using proxy measures. - -**Limitation:** the manifest contains structure, not the actual content learners see. These are proxies based on structural indicators. Note this limitation in the report. - -**Proxy indicators:** -- Number of new concepts introduced per module (flag if >7). - [CogLoad-4] [T5] — intrinsic load increases with element interactivity. - [CogLoad-5] [T5] — working memory capacity limits are real design constraints. - [CogLoad-6] [T1] — working memory resource depletion compounds across tasks. -- Number of prerequisite concepts required (flag if prerequisites span >3 prior modules). - [CogLoad-1] [T1] — problem-solving support interacts with sequence to affect load. -- Assessment complexity relative to objective Bloom's level. - [CogLoad-16] [T1] — format affects cognitive load independently of content. -- Module sequencing: are related concepts spaced or massed? - [CogLoad-17] [T1] — sequencing significantly affects learning outcomes. - [CogLoad-13] [T3] — five strategies for optimizing instructional materials. - -**Expertise reversal check:** -- Are scaffolds present that would hurt expert learners? - [CogLoad-19] [T5] — expertise reversal effect. - [CogLoad-11] [T3] — digital/online learning amplifies cognitive load concerns. -- Are there adaptive elements that adjust based on learner expertise? - [Learner-16] [T1] — differentiated instruction produces measurable gains. - [Learner-18] [T5] — personalized adaptive learning framework. - -### Dimension 4 — Learner Persona Simulation - -Simulate 4 learner personas walking through the course. - -**Limitation:** simulation operates on structural/metadata signals, not actual content text. Content-level analysis (e.g., detecting idioms that challenge ESL learners) requires the actual course materials. Note this in the report. - -**Persona A — Complete Novice** (no prior knowledge in domain) -- Can they access the content without assumed background? - [Learner-14] [T5] — personalized education must account for starting knowledge. -- Do early modules build sufficient foundation for later ones? - [CogLoad-1] [T1] — instructional sequence and problem-solving support interact for novices. -- Is the pacing appropriate for someone learning everything for the first time? - [Learner-6] [T1] — differentiated pacing produces measurable gains. - -**Persona B — Expert Learner** (expertise reversal risk) -- Are there unnecessary scaffolds that would frustrate an expert? - [CogLoad-19] [T5] — expertise reversal effect. -- Can experts skip introductory content or are they forced through it? - [Learner-16] [T1] — effective differentiation allows bypassing known material. -- Does the course adapt to prior knowledge or treat everyone as novice? - [Learner-11] [T2] — data-based differentiation responds to individual learner state. - -**Persona C — ESL Learner** (language complexity, cultural references) -- Are key terms defined when first introduced? - [Access-4] [T3] — universal instructional design includes clear vocabulary introduction. -- Are instructions clear without idiomatic expressions? - [CogLoad-11] [T3] — extraneous load from language complexity compounds online. -- Are cultural references universal or region-specific? - [Learner-13] [T4] — diverse student needs require culturally responsive design. -- Is reading level appropriate? (Flag if above Flesch-Kincaid grade 10 for ESL audiences.) - [Learner-2] [T2] — differentiated instruction varies with language proficiency. - -**Persona D — Learner with Accessibility Needs** -- Do assessments offer alternative formats (extended time, alternative submission)? - [Access-3] [T5] — UDL 3.0 requires multiple means of action and expression. - [Access-6] [T2] — universal design for instruction supports flexible assessment. -- Are multimedia elements accessible (captions, transcripts, alt text)? - [Access-1] [T5] — WCAG 2.1 requires text alternatives for non-text content. - [Access-5] [T3] — universal design includes multimedia accessibility. -- Can the course be navigated with keyboard only? - [Access-1] [T5] — WCAG 2.1 keyboard accessibility. - [Access-2] [T5] — WCAG 2.2 extends keyboard navigation standards. - -**Per-persona checklist (evaluate for every module):** -1. Can this persona access the content? [Access-4] [T3] -2. Does this persona have the prerequisite knowledge? [CogLoad-1] [T1] -3. Is the cognitive load appropriate for this persona's expertise level? [CogLoad-19] [T5] -4. Does the assessment format work for this persona? [Assessment-8] [T1] -5. Is the feedback actionable for this persona? [Assessment-9] [T5] - -### Dimension 5 — Prerequisite Chain Integrity - -Trace prerequisite dependencies across all modules. - -**Check for:** -- Circular dependencies (A requires B requires A). - [CogLoad-17] [T1] — circular paths make valid sequencing impossible. -- Missing prerequisites (module assumes knowledge not taught earlier). - [CogLoad-1] [T1] — problem-solving support must match prerequisite state. - [Alignment-10] [T2] — high challenge without high support undermines learning. -- Orphaned content (nothing depends on it, no prerequisites). - [Alignment-1] [T5] — every component must serve the objective chain. -- Ordering violations (prerequisite module appears after the module that needs it). - [CogLoad-17] [T1] — sequencing violations create impossible learning paths. - [CogLoad-4] [T5] — intrinsic load becomes unmanageable when prereqs are unavailable. - ---- - -## Confidence Score - -After all dimensions return, compute the confidence score: - -- Start at 100 -- Deduct per finding: - - Critical = -15 - - Warning = -5 - - Info = -1 -- Floor at 0 - -Severity weights reflect that structural misalignment and cognitive overload are the strongest predictors of learner failure: [Alignment-14] [T1], [CogLoad-6] [T1]. - -Contextualize: -- 80+ "High confidence" — minor issues only -- 60-79 "Moderate, needs work" — several design gaps -- 40-59 "Low confidence, significant gaps" — multiple problem dimensions -- <40 "Course needs redesign" — structural issues across most dimensions - ---- - -## Output - -The orchestrator emits an HTML report. Follow the **visual contract** in `templates/report.html.tmpl` and the **content contract** in `templates/report-format.md`. - -```bash -# Resolve the idstack install dir. Re-derived at the top of every bash block — -# blocks run in separate shells, so a value derived in an earlier block is not -# available here. Priority: explicit env overrides, Codex-style symlinks, then -# the Claude Code marketplace cache. Empty if none found; guard -# "$_IDSTACK/bin/..." calls accordingly. -_IDSTACK="" -# Marketplace cache holds one dir per installed version. Sort the basenames by -# numeric version fields, not lexically — plain sort ranks 3.9.0.0 above -# 3.10.0.0 and would pick a stale install once the minor hits double digits. -_idstack_cache_root="$HOME/.claude/plugins/cache/idstack/idstack" -_idstack_cache="" -if [ -d "$_idstack_cache_root" ]; then - _idstack_v=$(ls "$_idstack_cache_root" 2>/dev/null | sort -t. -k1,1n -k2,2n -k3,3n -k4,4n | tail -1) - [ -n "$_idstack_v" ] && _idstack_cache="$_idstack_cache_root/$_idstack_v" -fi -for _p in "${CLAUDE_PLUGIN_ROOT:-}" "${IDSTACK_HOME:-}" "$HOME/.agents/plugins/idstack" "$HOME/.agents/skills/idstack" "$_idstack_cache"; do - if [ -n "$_p" ] && [ -d "$_p" ]; then _IDSTACK="${_p%/}"; break; fi -done -# Compute the course slug from project_name and prepare the export folder. -_PROJECT_NAME=$(python3 -c "import json; print(json.load(open('.idstack/project.json')).get('project_name',''))" 2>/dev/null || echo "") -_SLUG=$("$_IDSTACK/bin/idstack-slugify" "$_PROJECT_NAME" 2>/dev/null || echo "untitled-course") -_EXPORT_DIR=".idstack/exports/$_SLUG" -_REPORT_PATH="$_EXPORT_DIR/red-team.html" -mkdir -p "$_EXPORT_DIR/assets" -cp -f "$_IDSTACK/templates/assets/idstack.css" "$_EXPORT_DIR/assets/idstack.css" -echo "Report path: $_REPORT_PATH" -``` - -Write the HTML report at the path printed above (`.idstack/exports/<course-slug>/red-team.html`), following the structure of `templates/report.html.tmpl`. Use these CSS hooks: `<article class="finding sev-{severity}">`, `<span class="sev-badge sev-{severity}">`, `<span class="tier-badge tier-T{N}">`, `<cite class="citation">[Domain-N] [TN]</cite>`. Customize for this skill: - -- **`{{skill_title}}`:** "Red Team Audit" -- **`{{skill_name}}`:** `red-team` -- **`{{mode}}`:** `full` or `limited` (include the optional mode segment in the header `meta` line; you may also append `· focus: {{FOCUS}}`). -- **Summary:** 2–3 sentences. Lead with the confidence score (0–100) and the band ("High confidence" / "Moderate, needs work" / "Low confidence, significant gaps" / "Course needs redesign"). Include the optional one-line scoreboard: "Confidence XX/100 · Critical N · Warning N · Info N". -- **Findings** ordered by severity (`sev-critical` → `sev-warning` → `sev-info`) inside a single `<section class="findings">`. Stable ids of the form `<dimension>-<n>` (e.g., `alignment-1`, `cogload-3`) so the parent can reference findings when applying fixes. Each `<article class="finding sev-...">` should include the affected module/objective/assessment in the "What we saw" `<dd>`. -- **Skill-specific section before Findings** — add a `<section class="dimension-summary">` with `<h2>Per-dimension summary</h2>` and an HTML `<table>` with a row for each dimension (Alignment, Evidence, Cognitive Load, Personas, Prerequisites) showing the per-dimension `pass | warning | critical` score and a one-line summary. -- **Top recommendations:** the 3 changes that would most improve the score. Reference finding ids. -- **Limitations:** what this audit could not assess (content-level analysis, actual learner behavior, LMS-specific implementation, etc.). -- **Next steps:** If confidence is <60 after fixes, recommend re-running `/idstack:learning-objectives` or `/idstack:assessment-design`. If 60+, recommend `/idstack:course-export`. - -Each finding **must** have a stable id of the form `<dimension>-<n>` (e.g., `alignment-1`, `cogload-3`) so the parent can reference findings when applying fixes. - -## Return value - -After writing the report, return ONLY a short executive summary (≤200 words) to the parent: -- Confidence score and band ("Moderate, needs work") -- Severity counts -- Top 1 critical finding (one line) -- Path: the value of `$_REPORT_PATH` (e.g., `.idstack/exports/<course-slug>/red-team.html`) - -Do NOT return the full report inline. The parent will read the file. - -</orchestrator-brief> - -### Step 3: Surface the summary - -After the orchestrator returns: -1. Read the HTML report file at the path the orchestrator returned (e.g., `.idstack/exports/<course-slug>/red-team.html`). HTML is fine to Read — extract the content sections by tag. -2. Show the user the executive summary in your own words: confidence score, severity counts, top critical finding, and the report path. -3. Mention: "Full HTML report at the path above — open it in any browser for the complete finding list." - -### Step 4: Triage — choose fix scope - -Ask one AskUserQuestion: - -> "Which findings would you like to address?" - -Options: -- **Critical only (recommended)** — highest-impact fixes, smallest scope -- **Critical + Warning** — broader cleanup -- **All findings** — including Info; can be a lot -- **Skip — review report manually** — no fixes now; user will read the file themselves - -If the user chooses **Skip**, jump straight to Step 6. - -### Step 5: Apply fixes in-context - -For each finding in the chosen severity bucket, in order of severity: - -1. Read the affected course file(s) (module content, assessment definition, manifest section). -2. Propose the fix in 1-2 sentences. State which file and which finding id you're addressing. -3. Apply via Edit. -4. Track the finding id in `fixes_applied`. If you decide a finding is not actionable in-context (e.g., requires re-running `/idstack:assessment-design`), record it in `fixes_deferred` with a one-line reason. - -Do not spawn additional sub-agents for fixes. The parent has the relevant context to edit course files directly. - -If the user pushes back on any specific fix, mark it deferred and continue. - -### Step 6: Update manifest - -Save results to `.idstack/project.json` via `bin/idstack-manifest-merge`, which replaces only -the `red_team_audit` section, preserves every other section verbatim, validates JSON, and -atomically updates the top-level `updated` timestamp. Pull the score and findings from -the orchestrator's HTML report at `$_REPORT_PATH` (the report is the source of truth — do -not re-derive from the orchestrator's return summary, which is lossy). - -```bash -# Resolve the idstack install dir. Re-derived at the top of every bash block — -# blocks run in separate shells, so a value derived in an earlier block is not -# available here. Priority: explicit env overrides, Codex-style symlinks, then -# the Claude Code marketplace cache. Empty if none found; guard -# "$_IDSTACK/bin/..." calls accordingly. -_IDSTACK="" -# Marketplace cache holds one dir per installed version. Sort the basenames by -# numeric version fields, not lexically — plain sort ranks 3.9.0.0 above -# 3.10.0.0 and would pick a stale install once the minor hits double digits. -_idstack_cache_root="$HOME/.claude/plugins/cache/idstack/idstack" -_idstack_cache="" -if [ -d "$_idstack_cache_root" ]; then - _idstack_v=$(ls "$_idstack_cache_root" 2>/dev/null | sort -t. -k1,1n -k2,2n -k3,3n -k4,4n | tail -1) - [ -n "$_idstack_v" ] && _idstack_cache="$_idstack_cache_root/$_idstack_v" -fi -for _p in "${CLAUDE_PLUGIN_ROOT:-}" "${IDSTACK_HOME:-}" "$HOME/.agents/plugins/idstack" "$HOME/.agents/skills/idstack" "$_idstack_cache"; do - if [ -n "$_p" ] && [ -d "$_p" ]; then _IDSTACK="${_p%/}"; break; fi -done -"$_IDSTACK/bin/idstack-manifest-merge" --section red_team_audit --payload - <<'PAYLOAD' -{ - "updated": "<ISO-8601 timestamp>", - "confidence_score": 0, - "focus": "Full sweep", - "report_path": "<set to the orchestrator-returned path — e.g. .idstack/exports/<course-slug>/red-team.html>", - "findings_summary": {"critical": 0, "warning": 0, "info": 0}, - "dimensions": { - "alignment": {"score": "pass|warning|critical", "findings": []}, - "evidence": {"score": "pass|warning|critical", "mode": "full|limited", "findings": []}, - "cognitive_load": {"score": "pass|warning|critical", "findings": []}, - "personas": {"score": "pass|warning|critical", "findings": []}, - "prerequisites": {"score": "pass|warning|critical", "findings": []} - }, - "top_actions": [], - "limitations": [], - "fixes_applied": [], - "fixes_deferred": [] -} -PAYLOAD -``` - -Each finding object: `{"id": "alignment-1", "description": "...", "module": "Module 3", "severity": "critical|warning|info"}`. - -`fixes_applied[]` — each item: `{"id": "alignment-1", "description": "Optional one-line summary of the change applied"}`. - -`fixes_deferred[]` — each item: `{"id": "alignment-3", "reason": "One-line reason — e.g., requires re-running /idstack:assessment-design"}`. - -The merge tool exits non-zero (and prints a diagnostic on stderr) if the payload is malformed, -the manifest is corrupt, or the section name is misspelled — never silently overwriting. If -`.idstack/project.json` doesn't exist yet (standalone mode), create it first with `--init`, -which writes a canonical manifest with every section at its default — without one the merge -tool exits 4: - -```bash -# Resolve the idstack install dir. Re-derived at the top of every bash block — -# blocks run in separate shells, so a value derived in an earlier block is not -# available here. Priority: explicit env overrides, Codex-style symlinks, then -# the Claude Code marketplace cache. Empty if none found; guard -# "$_IDSTACK/bin/..." calls accordingly. -_IDSTACK="" -# Marketplace cache holds one dir per installed version. Sort the basenames by -# numeric version fields, not lexically — plain sort ranks 3.9.0.0 above -# 3.10.0.0 and would pick a stale install once the minor hits double digits. -_idstack_cache_root="$HOME/.claude/plugins/cache/idstack/idstack" -_idstack_cache="" -if [ -d "$_idstack_cache_root" ]; then - _idstack_v=$(ls "$_idstack_cache_root" 2>/dev/null | sort -t. -k1,1n -k2,2n -k3,3n -k4,4n | tail -1) - [ -n "$_idstack_v" ] && _idstack_cache="$_idstack_cache_root/$_idstack_v" -fi -for _p in "${CLAUDE_PLUGIN_ROOT:-}" "${IDSTACK_HOME:-}" "$HOME/.agents/plugins/idstack" "$HOME/.agents/skills/idstack" "$_idstack_cache"; do - if [ -n "$_p" ] && [ -d "$_p" ]; then _IDSTACK="${_p%/}"; break; fi -done -"$_IDSTACK/bin/idstack-migrate" --init .idstack/project.json >/dev/null -``` - -**Fallback (if `bin/idstack-manifest-merge` is unavailable):** Read the full manifest, modify -only the `red_team_audit` section, Write back. Preserve all other sections verbatim. The -canonical schema for reference is in `templates/manifest-schema.md`. - -### Step 7: Final summary to user - -Two sentences: -- "Confidence: X/100. Applied N fixes (M deferred). Report at `.idstack/exports/<course-slug>/red-team.html`." -- If confidence is <60 after fixes, recommend re-running `/idstack:learning-objectives` or `/idstack:assessment-design`. If 60+, recommend `/idstack:course-export`. - -If the user wants to verify fixes hold, they can re-run `/idstack:red-team` — that's deliberately manual to avoid token costs of automatic re-verification. - -## Feedback - -Have feedback or a feature request? [Share it here](https://forms.gle/6LDgDD1M6WWyYvME8) — no GitHub account needed. - ---- - -## Completion: Timeline Logging - -After the skill workflow completes successfully, log the session to the timeline: - -```bash -# Resolve the idstack install dir. Re-derived at the top of every bash block — -# blocks run in separate shells, so a value derived in an earlier block is not -# available here. Priority: explicit env overrides, Codex-style symlinks, then -# the Claude Code marketplace cache. Empty if none found; guard -# "$_IDSTACK/bin/..." calls accordingly. -_IDSTACK="" -# Marketplace cache holds one dir per installed version. Sort the basenames by -# numeric version fields, not lexically — plain sort ranks 3.9.0.0 above -# 3.10.0.0 and would pick a stale install once the minor hits double digits. -_idstack_cache_root="$HOME/.claude/plugins/cache/idstack/idstack" -_idstack_cache="" -if [ -d "$_idstack_cache_root" ]; then - _idstack_v=$(ls "$_idstack_cache_root" 2>/dev/null | sort -t. -k1,1n -k2,2n -k3,3n -k4,4n | tail -1) - [ -n "$_idstack_v" ] && _idstack_cache="$_idstack_cache_root/$_idstack_v" -fi -for _p in "${CLAUDE_PLUGIN_ROOT:-}" "${IDSTACK_HOME:-}" "$HOME/.agents/plugins/idstack" "$HOME/.agents/skills/idstack" "$_idstack_cache"; do - if [ -n "$_p" ] && [ -d "$_p" ]; then _IDSTACK="${_p%/}"; break; fi -done -"$_IDSTACK/bin/idstack-timeline-log" '{"skill":"red-team","event":"completed"}' -``` - -Include skill-specific fields where available (confidence_score, focus, fixes_applied count). Log synchronously (no background &). - -If you discover a non-obvious project-specific quirk during this session (LMS behavior, import format issue, course structure pattern), also log it as a learning: - -```bash -# Resolve the idstack install dir. Re-derived at the top of every bash block — -# blocks run in separate shells, so a value derived in an earlier block is not -# available here. Priority: explicit env overrides, Codex-style symlinks, then -# the Claude Code marketplace cache. Empty if none found; guard -# "$_IDSTACK/bin/..." calls accordingly. -_IDSTACK="" -# Marketplace cache holds one dir per installed version. Sort the basenames by -# numeric version fields, not lexically — plain sort ranks 3.9.0.0 above -# 3.10.0.0 and would pick a stale install once the minor hits double digits. -_idstack_cache_root="$HOME/.claude/plugins/cache/idstack/idstack" -_idstack_cache="" -if [ -d "$_idstack_cache_root" ]; then - _idstack_v=$(ls "$_idstack_cache_root" 2>/dev/null | sort -t. -k1,1n -k2,2n -k3,3n -k4,4n | tail -1) - [ -n "$_idstack_v" ] && _idstack_cache="$_idstack_cache_root/$_idstack_v" -fi -for _p in "${CLAUDE_PLUGIN_ROOT:-}" "${IDSTACK_HOME:-}" "$HOME/.agents/plugins/idstack" "$HOME/.agents/skills/idstack" "$_idstack_cache"; do - if [ -n "$_p" ] && [ -d "$_p" ]; then _IDSTACK="${_p%/}"; break; fi -done -"$_IDSTACK/bin/idstack-learnings-log" '{"skill":"red-team","type":"operational","key":"SHORT_KEY","insight":"DESCRIPTION","confidence":8,"source":"observed"}' -``` diff --git a/docs/index.html b/docs/index.html index a968682..5ca880e 100644 --- a/docs/index.html +++ b/docs/index.html @@ -4,8 +4,8 @@ <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>idstack — Evidence-based instructional design, transparently cited - - + + @@ -15,7 +15,7 @@ - + @@ -28,7 +28,7 @@ - + @@ -38,11 +38,11 @@ "@context": "https://schema.org", "@type": "SoftwareApplication", "name": "idstack", - "description": "Open-source instructional design skills backed by peer-reviewed research across 11 domains. Every recommendation cites its evidence tier. Runs in Claude Code and OpenAI Codex CLI.", + "description": "Open-source instructional design skills backed by peer-reviewed research across 11 domains. Every recommendation cites its evidence tier. Runs in Claude Code.", "url": "https://idstack.org", "applicationCategory": "EducationalApplication", "operatingSystem": "macOS, Linux, Windows", - "softwareVersion": "3.3.0", + "softwareVersion": "3.4.0", "datePublished": "2026-04-20", "dateModified": "2026-08-06", "offers": { @@ -714,30 +714,6 @@ white-space: nowrap; } - .detect-row { - display: flex; - flex-wrap: wrap; - gap: 0.5rem; - margin: 0.85rem 0 1.25rem; - } - .detect-pill { - display: inline-flex; - align-items: center; - gap: 0.35rem; - font-size: 0.85rem; - padding: 0.3rem 0.7rem; - border: 1px solid var(--rule); - border-radius: 2px; - color: var(--ink-soft); - background: var(--raised); - } - .detect-pill .check { color: var(--tier-1); font-weight: 700; } - .detect-pill.detect-pending { - color: var(--ink-muted); - border-style: dashed; - } - .detect-pill.detect-pending .check { color: var(--ink-muted); font-weight: 400; } - .install-prose { font-size: 0.95rem; color: var(--ink-soft); @@ -926,10 +902,10 @@ MIT - v3.3.0.4 — Course memory fixed + v3.4.0.0 — Claude Code only

Evidence-based instructional design, transparently cited.

-

108 peer-reviewed studies across 11 research domains. Every recommendation tagged with its evidence tier — T1 meta-analyses and RCTs through T5 expert opinion. Runs in Claude Code, OpenAI Codex CLI, and (soon) Gemini.

+

108 peer-reviewed studies across 11 research domains. Every recommendation tagged with its evidence tier — T1 meta-analyses and RCTs through T5 expert opinion. Runs in Claude Code.

@@ -1094,7 +1070,7 @@

Eleven skills. Pipeline optional.

-

Run the full sequence with /idstack:pipeline in Claude Code or $pipeline in Codex CLI. The orchestrator auto-skips completed steps and resumes where you left off.

+

Run the full sequence with /idstack:pipeline. The orchestrator auto-skips completed steps and resumes where you left off.

@@ -1157,24 +1133,25 @@

Install in about five minutes.

-

./setup auto-detects which CLIs are on your PATH and installs to whichever you have. Force one with --codex or --no-codex.

+

./setup registers idstack with Claude Code and installs all 11 skills. Re-run it after a git pull to update.

-
- Claude Code - OpenAI Codex CLI - Gemini CLI · v2.6 -
- -

New to AI coding tools? Install one first — Claude Code (free download) or Codex CLI (npm install -g @openai/codex). Then paste the command above. When your CLI starts, run /idstack:pipeline (Claude) or $pipeline (Codex).

+

New to Claude Code? Download it first (free), then paste the command above. When Claude Code starts, run /idstack:pipeline.

What's new

-

v3.3.0.0 — Course memory that actually remembers.

+

v3.4.0.0 — idstack is a Claude Code plugin.

+
+

August 6, 2026 · v3.4.0.0

+

One host, supported properly.

+

idstack shipped an OpenAI Codex CLI build from v2.5.0.0 through v3.3.0.4. It is removed. Every skill, the evidence base, the project manifest, and the HTML reports are unchanged — idstack now targets Claude Code and nothing else, which is the only place it was ever tested end to end.

+

If you installed the Codex build, ./setup can no longer clean it up. Remove the leftover links yourself: rm -f "${CODEX_HOME:-$HOME/.codex}"/skills/idstack-* "$HOME/.agents/plugins/idstack". To update everything else: cd into your idstack clone, then git pull && ./setup.

+
+

August 4, 2026 · v3.3.0.0 · patched through v3.3.0.4

Welcome-back messages work on stock macOS now.

@@ -1185,7 +1162,7 @@

Welcome-back messages work on stock macOS now.

→ All releases on GitHub

-

Next: Gemini CLI, additional specialist skills (content sequencing, media selection, Kirkpatrick evaluation), and push-to-LMS so improvements land back in Canvas without copy-paste. See the roadmap.

+

Next: additional specialist skills (content sequencing, media selection, Kirkpatrick evaluation), and push-to-LMS so improvements land back in Canvas without copy-paste. See the roadmap.

diff --git a/docs/og-image.png b/docs/og-image.png index 2b6eb1014df9615a23f56f5d681b147f33e8919e..880d940d61703dbd78263cc7fa7d9073ad50c322 100644 GIT binary patch literal 100831 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
yOj`68zIAI-RYZhq4EEC~~oT_U>bM7uDBh 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

`&JTNO6ioAlU$d~@#p^uR#m(q>aMV-5qAVp` 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

qxf*@yk0!bBkwrT512zO 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

Evidence-based instructional design, transparently cited.

-

108 peer-reviewed studies across 11 research domains. Every recommendation tagged with its evidence tier. Runs in Claude Code and OpenAI Codex CLI.

+

108 peer-reviewed studies across 11 research domains. Every recommendation tagged with its evidence tier. Runs in Claude Code.

diff --git a/docs/why-ai-native.md b/docs/why-ai-native.md index 460f336..4fa4ec5 100644 --- a/docs/why-ai-native.md +++ b/docs/why-ai-native.md @@ -21,7 +21,7 @@ idstack is AI-native on all four counts. ## How idstack scores -**Core workflow is impossible without AI.** idstack skills are structured instructions for an AI agent. Without Claude Code (or Gemini CLI, or Codex CLI), the SKILL.md files are just Markdown. There is no "normal way" to use idstack without AI. The AI IS the product. +**Core workflow is impossible without AI.** idstack skills are structured instructions for an AI agent. Without Claude Code, the SKILL.md files are just Markdown. There is no "normal way" to use idstack without AI. The AI IS the product. **It gets better as models improve.** When Claude gets better at following complex multi-step instructions, at understanding educational research, at generating course content... every idstack skill gets better for free. No code changes needed. You're riding the model improvement curve automatically. diff --git a/setup b/setup index 76c642b..ee60855 100755 --- a/setup +++ b/setup @@ -15,7 +15,6 @@ IDSTACK_DIR="$(cd "$(dirname "$0")" && pwd -P)" LEGACY_SKILLS_DIR="$HOME/.claude/skills" SCOPE="global" CLAUDE_SCOPE="user" # claude plugin scope: user (global) or project (--local) -CODEX_OPT_IN="" # "" = auto-detect, "yes" = force-install, "no" = skip KEEP_LEGACY=0 # --keep-legacy: leave every legacy install shape in place for arg in "$@"; do @@ -25,14 +24,10 @@ for arg in "$@"; do SCOPE="local" CLAUDE_SCOPE="project" ;; - --codex) CODEX_OPT_IN="yes" ;; - --no-codex) CODEX_OPT_IN="no" ;; --keep-legacy) KEEP_LEGACY=1 ;; -h|--help) - echo "Usage: ./setup [--local] [--codex|--no-codex] [--keep-legacy]" + echo "Usage: ./setup [--local] [--keep-legacy]" echo " --local install at project scope (./.claude/) instead of user scope" - echo " --codex force Codex install even if codex is not on PATH" - echo " --no-codex skip Codex install even if codex is on PATH" echo " --keep-legacy keep legacy installs in place — the pre-v2.0.1.0 dispatcher" echo " dir, the v2.0 dispatcher symlink, and pre-v2 per-skill" echo " symlinks under \$HOME/.claude/skills/ (default: remove)" @@ -42,11 +37,10 @@ for arg in "$@"; do esac done -# Regenerate skill files for all targets so installs are always fresh against -# the current .tmpl source. Cheap (<1s); the source-of-truth invariant is -# worth more than the cost. -"$IDSTACK_DIR/bin/idstack-gen-skills" --target all >/dev/null -echo " regenerated skill files for all targets" +# Regenerate skill files so installs are always fresh against the current .tmpl +# source. Cheap (<1s); the source-of-truth invariant is worth more than the cost. +"$IDSTACK_DIR/bin/idstack-gen-skills" >/dev/null +echo " regenerated skill files" echo "Installing idstack ($SCOPE)..." echo " source: $IDSTACK_DIR" @@ -156,8 +150,8 @@ fi # and `plugin update` picks up a newer committed version on `git pull && ./setup`. if command -v claude >/dev/null 2>&1; then # The two required calls fail loud with a manual-recovery hint — under - # `set -e` a bare nonzero exit here used to abort setup silently, before - # the Codex block, with no message at all. + # `set -e` a bare nonzero exit here used to abort setup silently, with no + # message at all. if ! claude plugin marketplace add "$IDSTACK_DIR"; then echo "" >&2 echo "ERROR: 'claude plugin marketplace add' failed. Register manually, then re-run ./setup:" >&2 @@ -181,83 +175,6 @@ else echo " claude plugin install idstack@idstack --scope $CLAUDE_SCOPE" fi -# Optional: Codex CLI install. Auto-detect codex on PATH unless the user passed -# --codex (force) or --no-codex (skip). The Codex bundle lives under -# dist/codex/ and is regenerated on every setup run. -CODEX_INSTALL=0 -if [ "$CODEX_OPT_IN" = "yes" ]; then - CODEX_INSTALL=1 -elif [ "$CODEX_OPT_IN" = "no" ]; then - CODEX_INSTALL=0 -elif command -v codex >/dev/null 2>&1; then - CODEX_INSTALL=1 -fi - -if [ "$CODEX_INSTALL" = "1" ]; then - # Codex install — two-part: - # 1. Per-skill symlinks at $CODEX_HOME/skills/idstack-/ so Codex auto- - # discovers each skill ($needs-analysis, $pipeline, etc.). - # 2. A whole-repo symlink at ~/.agents/plugins/idstack/ -> $IDSTACK_DIR so - # the in-skill bash blocks can resolve $_IDSTACK/bin/idstack-*. The - # preamble's path-detection chain finds it via the ~/.agents/plugins/ - # fallback. - CODEX_HOME_DIR="${CODEX_HOME:-$HOME/.codex}" - if [ "$SCOPE" = "local" ]; then - CODEX_HOME_DIR="$(pwd)/.codex" - CODEX_BIN_PARENT="$(pwd)/.agents/plugins" - else - CODEX_BIN_PARENT="$HOME/.agents/plugins" - fi - CODEX_SKILLS_DIR="$CODEX_HOME_DIR/skills" - CODEX_BIN_TARGET="$CODEX_BIN_PARENT/idstack" - CODEX_BUNDLE_SKILLS="$IDSTACK_DIR/dist/codex/skills" - - if [ ! -d "$CODEX_BUNDLE_SKILLS" ]; then - echo " WARNING: Codex skill bundle missing at $CODEX_BUNDLE_SKILLS — generator did not produce it. Skipping Codex install." - else - # Part 1: per-skill symlinks for auto-discovery. - mkdir -p "$CODEX_SKILLS_DIR" - linked_count=0 - for skill_dir in "$CODEX_BUNDLE_SKILLS"/idstack-*; do - [ -d "$skill_dir" ] || continue - skill_name=$(basename "$skill_dir") - target="$CODEX_SKILLS_DIR/$skill_name" - # Replace pre-existing real dir so ln -snf doesn't symlink-into-dir. - if [ -d "$target" ] && [ ! -L "$target" ]; then - if [ -z "$HOME" ] || [[ "$target" != */idstack-* ]]; then - echo "ERROR: refusing to rm $target" >&2 - exit 1 - fi - rm -rf "$target" - fi - ln -snf "$skill_dir" "$target" - linked_count=$((linked_count + 1)) - done - echo " linked $linked_count skills into: $CODEX_SKILLS_DIR/idstack-*" - - # Part 2: whole-repo symlink so $_IDSTACK/bin/idstack-* resolves in skill bodies. - mkdir -p "$CODEX_BIN_PARENT" - if [ "$CODEX_BIN_TARGET" = "$IDSTACK_DIR" ]; then - echo " in place: $CODEX_BIN_TARGET (no bin-resolution symlink needed)" - else - if [ -d "$CODEX_BIN_TARGET" ] && [ ! -L "$CODEX_BIN_TARGET" ]; then - if [ -z "$HOME" ] || [[ "$CODEX_BIN_TARGET" != */idstack ]]; then - echo "ERROR: refusing to rm $CODEX_BIN_TARGET (HOME=$HOME)" >&2 - exit 1 - fi - rm -rf "$CODEX_BIN_TARGET" - echo " removed pre-existing dir: $CODEX_BIN_TARGET" - fi - ln -snf "$IDSTACK_DIR" "$CODEX_BIN_TARGET" - echo " linked: $CODEX_BIN_TARGET -> $IDSTACK_DIR (for in-skill bin/ resolution)" - fi - echo "" - echo "idstack installed (Codex CLI)." - echo " If Codex CLI is already running, restart it." - echo " Usage: \$ (e.g., \$needs-analysis, \$pipeline)" - fi -fi - echo "" echo " If Claude Code is already running, restart it (plugins load at session start)." echo "" diff --git a/skills/accessibility-review/SKILL.md b/skills/accessibility-review/SKILL.md index bd91c37..4090a2c 100644 --- a/skills/accessibility-review/SKILL.md +++ b/skills/accessibility-review/SKILL.md @@ -21,29 +21,23 @@ allowed-tools: ## Preamble: Interaction Conventions -idstack skills are designed to run in multiple CLIs (Claude Code, OpenAI Codex CLI, and -others). To stay portable, skill bodies use a few **concept names** that have a CLI-specific -implementation: +idstack runs in Claude Code. Skill bodies use a few **concept names** for the tools they +lean on: - **AskUserQuestion** — when a skill says "ask via AskUserQuestion" or "using AskUserQuestion", - it means: present a single numbered multiple-choice question (e.g., "Which of these best - describes X? 1) ..., 2) ..., 3) Other") and stop, waiting for the user's next message - before proceeding. Ask **one** question at a time, never batch. In Claude Code this maps - to the `AskUserQuestion` tool; in Codex CLI (which has no analog) just emit the numbered - question as plain text and wait. -- **Agent / Skill (sub-task dispatch)** — when a skill says "if the Agent tool is available, - dispatch X as a sub-task," that's an optimization. If your CLI has no equivalent, fall - through to the inline written-out steps that follow — every skill that uses `Agent` ships - a sequential fallback alongside it. -- **Skill (cross-skill invocation)** — used only by `/idstack:pipeline`. In Claude Code this - invokes a child skill in-process via the `Skill` tool. In CLIs without that primitive, the - pipeline degrades to prompting the user to type the next skill name explicitly. -- **Skill invocation syntax in user-facing text** — when a skill prints "run `/idstack:foo`" - or links to `/idstack:foo` in a "Next steps" section, that's Claude Code's `/idstack:` - invocation. **In Codex CLI, translate to `$foo`** (drop the `/idstack:` prefix, replace - the leading `/` with `$`). Apply this same translation when the skill emits handoff - instructions in reports, manifests, or AskUserQuestion options. Same body text, two - hosts; the model translates per-CLI on output. + it means: present a single multiple-choice question (e.g., "Which of these best describes X?") + and stop, waiting for the user's answer before proceeding. Ask **one** question at a time, + never batch. This maps to the `AskUserQuestion` tool. +- **Agent (sub-task dispatch)** — when a skill says "if the Agent tool is available, dispatch + X as a sub-task," that's an optimization. `Agent` is not in every skill's `allowed-tools`, + so when it isn't available, fall through to the inline written-out steps that follow — + every skill that uses `Agent` ships a sequential fallback alongside it. +- **Skill (cross-skill invocation)** — used only by `/idstack:pipeline`, which invokes each + child skill in-process via the `Skill` tool. +- **Skill invocation syntax in user-facing text** — every skill is invoked as + `/idstack:`. Always write the namespaced form: a bare `/` is not a valid + command. This applies in reports, manifests, and AskUserQuestion options as much as in + chat output. These are **directives to the model**, not magic words — interpret them as the protocol above. @@ -52,9 +46,9 @@ These are **directives to the model**, not magic words — interpret them as the ```bash # Resolve the idstack install dir. Re-derived at the top of every bash block — # blocks run in separate shells, so a value derived in an earlier block is not -# available here. Priority: explicit env overrides, Codex-style symlinks, then -# the Claude Code marketplace cache (highest version). Empty if none found; -# guard "$_IDSTACK/bin/..." calls accordingly. +# available here. Priority: explicit env overrides, then the Claude Code +# marketplace cache (highest version). Empty if none found; guard +# "$_IDSTACK/bin/..." calls accordingly. # Canonical copy: templates/snippets/idstack-resolve.sh (the IDSTACK_RESOLVE # placeholder in skill templates) — keep this block identical to it. _IDSTACK="" @@ -67,7 +61,7 @@ if [ -d "$_idstack_cache_root" ]; then _idstack_v=$(ls "$_idstack_cache_root" 2>/dev/null | sort -t. -k1,1n -k2,2n -k3,3n -k4,4n | tail -1) [ -n "$_idstack_v" ] && _idstack_cache="$_idstack_cache_root/$_idstack_v" fi -for _p in "${CLAUDE_PLUGIN_ROOT:-}" "${IDSTACK_HOME:-}" "$HOME/.agents/plugins/idstack" "$HOME/.agents/skills/idstack" "$_idstack_cache"; do +for _p in "${CLAUDE_PLUGIN_ROOT:-}" "${IDSTACK_HOME:-}" "$_idstack_cache"; do if [ -n "$_p" ] && [ -d "$_p" ]; then _IDSTACK="${_p%/}"; break; fi done _UPD=$("$_IDSTACK/bin/idstack-update-check" 2>/dev/null || true) @@ -92,7 +86,7 @@ if [ -d "$_idstack_cache_root" ]; then _idstack_v=$(ls "$_idstack_cache_root" 2>/dev/null | sort -t. -k1,1n -k2,2n -k3,3n -k4,4n | tail -1) [ -n "$_idstack_v" ] && _idstack_cache="$_idstack_cache_root/$_idstack_v" fi -for _p in "${CLAUDE_PLUGIN_ROOT:-}" "${IDSTACK_HOME:-}" "$HOME/.agents/plugins/idstack" "$HOME/.agents/skills/idstack" "$_idstack_cache"; do +for _p in "${CLAUDE_PLUGIN_ROOT:-}" "${IDSTACK_HOME:-}" "$_idstack_cache"; do if [ -n "$_p" ] && [ -d "$_p" ]; then _IDSTACK="${_p%/}"; break; fi done if [ -f ".idstack/project.json" ]; then @@ -175,7 +169,7 @@ if [ -d "$_idstack_cache_root" ]; then _idstack_v=$(ls "$_idstack_cache_root" 2>/dev/null | sort -t. -k1,1n -k2,2n -k3,3n -k4,4n | tail -1) [ -n "$_idstack_v" ] && _idstack_cache="$_idstack_cache_root/$_idstack_v" fi -for _p in "${CLAUDE_PLUGIN_ROOT:-}" "${IDSTACK_HOME:-}" "$HOME/.agents/plugins/idstack" "$HOME/.agents/skills/idstack" "$_idstack_cache"; do +for _p in "${CLAUDE_PLUGIN_ROOT:-}" "${IDSTACK_HOME:-}" "$_idstack_cache"; do if [ -n "$_p" ] && [ -d "$_p" ]; then _IDSTACK="${_p%/}"; break; fi done _HAS_TIMELINE=0 @@ -545,9 +539,9 @@ Generate an HTML report so the designer has a single document covering both comp ```bash # Resolve the idstack install dir. Re-derived at the top of every bash block — # blocks run in separate shells, so a value derived in an earlier block is not -# available here. Priority: explicit env overrides, Codex-style symlinks, then -# the Claude Code marketplace cache. Empty if none found; guard -# "$_IDSTACK/bin/..." calls accordingly. +# available here. Priority: explicit env overrides, then the Claude Code +# marketplace cache. Empty if none found; guard "$_IDSTACK/bin/..." calls +# accordingly. _IDSTACK="" # Marketplace cache holds one dir per installed version. Sort the basenames by # numeric version fields, not lexically — plain sort ranks 3.9.0.0 above @@ -558,7 +552,7 @@ if [ -d "$_idstack_cache_root" ]; then _idstack_v=$(ls "$_idstack_cache_root" 2>/dev/null | sort -t. -k1,1n -k2,2n -k3,3n -k4,4n | tail -1) [ -n "$_idstack_v" ] && _idstack_cache="$_idstack_cache_root/$_idstack_v" fi -for _p in "${CLAUDE_PLUGIN_ROOT:-}" "${IDSTACK_HOME:-}" "$HOME/.agents/plugins/idstack" "$HOME/.agents/skills/idstack" "$_idstack_cache"; do +for _p in "${CLAUDE_PLUGIN_ROOT:-}" "${IDSTACK_HOME:-}" "$_idstack_cache"; do if [ -n "$_p" ] && [ -d "$_p" ]; then _IDSTACK="${_p%/}"; break; fi done # Compute the course slug from project_name and prepare the export folder. @@ -595,9 +589,9 @@ and atomically updates the top-level `updated` timestamp. The payload must inclu ```bash # Resolve the idstack install dir. Re-derived at the top of every bash block — # blocks run in separate shells, so a value derived in an earlier block is not -# available here. Priority: explicit env overrides, Codex-style symlinks, then -# the Claude Code marketplace cache. Empty if none found; guard -# "$_IDSTACK/bin/..." calls accordingly. +# available here. Priority: explicit env overrides, then the Claude Code +# marketplace cache. Empty if none found; guard "$_IDSTACK/bin/..." calls +# accordingly. _IDSTACK="" # Marketplace cache holds one dir per installed version. Sort the basenames by # numeric version fields, not lexically — plain sort ranks 3.9.0.0 above @@ -608,7 +602,7 @@ if [ -d "$_idstack_cache_root" ]; then _idstack_v=$(ls "$_idstack_cache_root" 2>/dev/null | sort -t. -k1,1n -k2,2n -k3,3n -k4,4n | tail -1) [ -n "$_idstack_v" ] && _idstack_cache="$_idstack_cache_root/$_idstack_v" fi -for _p in "${CLAUDE_PLUGIN_ROOT:-}" "${IDSTACK_HOME:-}" "$HOME/.agents/plugins/idstack" "$HOME/.agents/skills/idstack" "$_idstack_cache"; do +for _p in "${CLAUDE_PLUGIN_ROOT:-}" "${IDSTACK_HOME:-}" "$_idstack_cache"; do if [ -n "$_p" ] && [ -d "$_p" ]; then _IDSTACK="${_p%/}"; break; fi done "$_IDSTACK/bin/idstack-manifest-merge" --section accessibility_review --payload - <<'PAYLOAD' @@ -646,9 +640,9 @@ section at its default. Without one the merge tool exits 4: ```bash # Resolve the idstack install dir. Re-derived at the top of every bash block — # blocks run in separate shells, so a value derived in an earlier block is not -# available here. Priority: explicit env overrides, Codex-style symlinks, then -# the Claude Code marketplace cache. Empty if none found; guard -# "$_IDSTACK/bin/..." calls accordingly. +# available here. Priority: explicit env overrides, then the Claude Code +# marketplace cache. Empty if none found; guard "$_IDSTACK/bin/..." calls +# accordingly. _IDSTACK="" # Marketplace cache holds one dir per installed version. Sort the basenames by # numeric version fields, not lexically — plain sort ranks 3.9.0.0 above @@ -659,7 +653,7 @@ if [ -d "$_idstack_cache_root" ]; then _idstack_v=$(ls "$_idstack_cache_root" 2>/dev/null | sort -t. -k1,1n -k2,2n -k3,3n -k4,4n | tail -1) [ -n "$_idstack_v" ] && _idstack_cache="$_idstack_cache_root/$_idstack_v" fi -for _p in "${CLAUDE_PLUGIN_ROOT:-}" "${IDSTACK_HOME:-}" "$HOME/.agents/plugins/idstack" "$HOME/.agents/skills/idstack" "$_idstack_cache"; do +for _p in "${CLAUDE_PLUGIN_ROOT:-}" "${IDSTACK_HOME:-}" "$_idstack_cache"; do if [ -n "$_p" ] && [ -d "$_p" ]; then _IDSTACK="${_p%/}"; break; fi done "$_IDSTACK/bin/idstack-migrate" --init .idstack/project.json >/dev/null @@ -721,9 +715,9 @@ The HTML report follows the visual contract in `templates/report.html.tmpl` and ```bash # Resolve the idstack install dir. Re-derived at the top of every bash block — # blocks run in separate shells, so a value derived in an earlier block is not -# available here. Priority: explicit env overrides, Codex-style symlinks, then -# the Claude Code marketplace cache. Empty if none found; guard -# "$_IDSTACK/bin/..." calls accordingly. +# available here. Priority: explicit env overrides, then the Claude Code +# marketplace cache. Empty if none found; guard "$_IDSTACK/bin/..." calls +# accordingly. _IDSTACK="" # Marketplace cache holds one dir per installed version. Sort the basenames by # numeric version fields, not lexically — plain sort ranks 3.9.0.0 above @@ -734,7 +728,7 @@ if [ -d "$_idstack_cache_root" ]; then _idstack_v=$(ls "$_idstack_cache_root" 2>/dev/null | sort -t. -k1,1n -k2,2n -k3,3n -k4,4n | tail -1) [ -n "$_idstack_v" ] && _idstack_cache="$_idstack_cache_root/$_idstack_v" fi -for _p in "${CLAUDE_PLUGIN_ROOT:-}" "${IDSTACK_HOME:-}" "$HOME/.agents/plugins/idstack" "$HOME/.agents/skills/idstack" "$_idstack_cache"; do +for _p in "${CLAUDE_PLUGIN_ROOT:-}" "${IDSTACK_HOME:-}" "$_idstack_cache"; do if [ -n "$_p" ] && [ -d "$_p" ]; then _IDSTACK="${_p%/}"; break; fi done # Write a payload for your skill's section, then: @@ -1121,9 +1115,9 @@ After the skill workflow completes successfully, log the session to the timeline ```bash # Resolve the idstack install dir. Re-derived at the top of every bash block — # blocks run in separate shells, so a value derived in an earlier block is not -# available here. Priority: explicit env overrides, Codex-style symlinks, then -# the Claude Code marketplace cache. Empty if none found; guard -# "$_IDSTACK/bin/..." calls accordingly. +# available here. Priority: explicit env overrides, then the Claude Code +# marketplace cache. Empty if none found; guard "$_IDSTACK/bin/..." calls +# accordingly. _IDSTACK="" # Marketplace cache holds one dir per installed version. Sort the basenames by # numeric version fields, not lexically — plain sort ranks 3.9.0.0 above @@ -1134,7 +1128,7 @@ if [ -d "$_idstack_cache_root" ]; then _idstack_v=$(ls "$_idstack_cache_root" 2>/dev/null | sort -t. -k1,1n -k2,2n -k3,3n -k4,4n | tail -1) [ -n "$_idstack_v" ] && _idstack_cache="$_idstack_cache_root/$_idstack_v" fi -for _p in "${CLAUDE_PLUGIN_ROOT:-}" "${IDSTACK_HOME:-}" "$HOME/.agents/plugins/idstack" "$HOME/.agents/skills/idstack" "$_idstack_cache"; do +for _p in "${CLAUDE_PLUGIN_ROOT:-}" "${IDSTACK_HOME:-}" "$_idstack_cache"; do if [ -n "$_p" ] && [ -d "$_p" ]; then _IDSTACK="${_p%/}"; break; fi done "$_IDSTACK/bin/idstack-timeline-log" '{"skill":"accessibility-review","event":"completed"}' @@ -1149,9 +1143,9 @@ import format issue, course structure pattern), also log it as a learning: ```bash # Resolve the idstack install dir. Re-derived at the top of every bash block — # blocks run in separate shells, so a value derived in an earlier block is not -# available here. Priority: explicit env overrides, Codex-style symlinks, then -# the Claude Code marketplace cache. Empty if none found; guard -# "$_IDSTACK/bin/..." calls accordingly. +# available here. Priority: explicit env overrides, then the Claude Code +# marketplace cache. Empty if none found; guard "$_IDSTACK/bin/..." calls +# accordingly. _IDSTACK="" # Marketplace cache holds one dir per installed version. Sort the basenames by # numeric version fields, not lexically — plain sort ranks 3.9.0.0 above @@ -1162,7 +1156,7 @@ if [ -d "$_idstack_cache_root" ]; then _idstack_v=$(ls "$_idstack_cache_root" 2>/dev/null | sort -t. -k1,1n -k2,2n -k3,3n -k4,4n | tail -1) [ -n "$_idstack_v" ] && _idstack_cache="$_idstack_cache_root/$_idstack_v" fi -for _p in "${CLAUDE_PLUGIN_ROOT:-}" "${IDSTACK_HOME:-}" "$HOME/.agents/plugins/idstack" "$HOME/.agents/skills/idstack" "$_idstack_cache"; do +for _p in "${CLAUDE_PLUGIN_ROOT:-}" "${IDSTACK_HOME:-}" "$_idstack_cache"; do if [ -n "$_p" ] && [ -d "$_p" ]; then _IDSTACK="${_p%/}"; break; fi done "$_IDSTACK/bin/idstack-learnings-log" '{"skill":"accessibility-review","type":"operational","key":"SHORT_KEY","insight":"DESCRIPTION","confidence":8,"source":"observed"}' diff --git a/skills/assessment-design/SKILL.md b/skills/assessment-design/SKILL.md index 6a3495a..ffbab04 100644 --- a/skills/assessment-design/SKILL.md +++ b/skills/assessment-design/SKILL.md @@ -20,29 +20,23 @@ allowed-tools: ## Preamble: Interaction Conventions -idstack skills are designed to run in multiple CLIs (Claude Code, OpenAI Codex CLI, and -others). To stay portable, skill bodies use a few **concept names** that have a CLI-specific -implementation: +idstack runs in Claude Code. Skill bodies use a few **concept names** for the tools they +lean on: - **AskUserQuestion** — when a skill says "ask via AskUserQuestion" or "using AskUserQuestion", - it means: present a single numbered multiple-choice question (e.g., "Which of these best - describes X? 1) ..., 2) ..., 3) Other") and stop, waiting for the user's next message - before proceeding. Ask **one** question at a time, never batch. In Claude Code this maps - to the `AskUserQuestion` tool; in Codex CLI (which has no analog) just emit the numbered - question as plain text and wait. -- **Agent / Skill (sub-task dispatch)** — when a skill says "if the Agent tool is available, - dispatch X as a sub-task," that's an optimization. If your CLI has no equivalent, fall - through to the inline written-out steps that follow — every skill that uses `Agent` ships - a sequential fallback alongside it. -- **Skill (cross-skill invocation)** — used only by `/idstack:pipeline`. In Claude Code this - invokes a child skill in-process via the `Skill` tool. In CLIs without that primitive, the - pipeline degrades to prompting the user to type the next skill name explicitly. -- **Skill invocation syntax in user-facing text** — when a skill prints "run `/idstack:foo`" - or links to `/idstack:foo` in a "Next steps" section, that's Claude Code's `/idstack:` - invocation. **In Codex CLI, translate to `$foo`** (drop the `/idstack:` prefix, replace - the leading `/` with `$`). Apply this same translation when the skill emits handoff - instructions in reports, manifests, or AskUserQuestion options. Same body text, two - hosts; the model translates per-CLI on output. + it means: present a single multiple-choice question (e.g., "Which of these best describes X?") + and stop, waiting for the user's answer before proceeding. Ask **one** question at a time, + never batch. This maps to the `AskUserQuestion` tool. +- **Agent (sub-task dispatch)** — when a skill says "if the Agent tool is available, dispatch + X as a sub-task," that's an optimization. `Agent` is not in every skill's `allowed-tools`, + so when it isn't available, fall through to the inline written-out steps that follow — + every skill that uses `Agent` ships a sequential fallback alongside it. +- **Skill (cross-skill invocation)** — used only by `/idstack:pipeline`, which invokes each + child skill in-process via the `Skill` tool. +- **Skill invocation syntax in user-facing text** — every skill is invoked as + `/idstack:`. Always write the namespaced form: a bare `/` is not a valid + command. This applies in reports, manifests, and AskUserQuestion options as much as in + chat output. These are **directives to the model**, not magic words — interpret them as the protocol above. @@ -51,9 +45,9 @@ These are **directives to the model**, not magic words — interpret them as the ```bash # Resolve the idstack install dir. Re-derived at the top of every bash block — # blocks run in separate shells, so a value derived in an earlier block is not -# available here. Priority: explicit env overrides, Codex-style symlinks, then -# the Claude Code marketplace cache (highest version). Empty if none found; -# guard "$_IDSTACK/bin/..." calls accordingly. +# available here. Priority: explicit env overrides, then the Claude Code +# marketplace cache (highest version). Empty if none found; guard +# "$_IDSTACK/bin/..." calls accordingly. # Canonical copy: templates/snippets/idstack-resolve.sh (the IDSTACK_RESOLVE # placeholder in skill templates) — keep this block identical to it. _IDSTACK="" @@ -66,7 +60,7 @@ if [ -d "$_idstack_cache_root" ]; then _idstack_v=$(ls "$_idstack_cache_root" 2>/dev/null | sort -t. -k1,1n -k2,2n -k3,3n -k4,4n | tail -1) [ -n "$_idstack_v" ] && _idstack_cache="$_idstack_cache_root/$_idstack_v" fi -for _p in "${CLAUDE_PLUGIN_ROOT:-}" "${IDSTACK_HOME:-}" "$HOME/.agents/plugins/idstack" "$HOME/.agents/skills/idstack" "$_idstack_cache"; do +for _p in "${CLAUDE_PLUGIN_ROOT:-}" "${IDSTACK_HOME:-}" "$_idstack_cache"; do if [ -n "$_p" ] && [ -d "$_p" ]; then _IDSTACK="${_p%/}"; break; fi done _UPD=$("$_IDSTACK/bin/idstack-update-check" 2>/dev/null || true) @@ -91,7 +85,7 @@ if [ -d "$_idstack_cache_root" ]; then _idstack_v=$(ls "$_idstack_cache_root" 2>/dev/null | sort -t. -k1,1n -k2,2n -k3,3n -k4,4n | tail -1) [ -n "$_idstack_v" ] && _idstack_cache="$_idstack_cache_root/$_idstack_v" fi -for _p in "${CLAUDE_PLUGIN_ROOT:-}" "${IDSTACK_HOME:-}" "$HOME/.agents/plugins/idstack" "$HOME/.agents/skills/idstack" "$_idstack_cache"; do +for _p in "${CLAUDE_PLUGIN_ROOT:-}" "${IDSTACK_HOME:-}" "$_idstack_cache"; do if [ -n "$_p" ] && [ -d "$_p" ]; then _IDSTACK="${_p%/}"; break; fi done if [ -f ".idstack/project.json" ]; then @@ -174,7 +168,7 @@ if [ -d "$_idstack_cache_root" ]; then _idstack_v=$(ls "$_idstack_cache_root" 2>/dev/null | sort -t. -k1,1n -k2,2n -k3,3n -k4,4n | tail -1) [ -n "$_idstack_v" ] && _idstack_cache="$_idstack_cache_root/$_idstack_v" fi -for _p in "${CLAUDE_PLUGIN_ROOT:-}" "${IDSTACK_HOME:-}" "$HOME/.agents/plugins/idstack" "$HOME/.agents/skills/idstack" "$_idstack_cache"; do +for _p in "${CLAUDE_PLUGIN_ROOT:-}" "${IDSTACK_HOME:-}" "$_idstack_cache"; do if [ -n "$_p" ] && [ -d "$_p" ]; then _IDSTACK="${_p%/}"; break; fi done _HAS_TIMELINE=0 @@ -689,9 +683,9 @@ Before writing the manifest, generate an HTML report so the designer has a singl ```bash # Resolve the idstack install dir. Re-derived at the top of every bash block — # blocks run in separate shells, so a value derived in an earlier block is not -# available here. Priority: explicit env overrides, Codex-style symlinks, then -# the Claude Code marketplace cache. Empty if none found; guard -# "$_IDSTACK/bin/..." calls accordingly. +# available here. Priority: explicit env overrides, then the Claude Code +# marketplace cache. Empty if none found; guard "$_IDSTACK/bin/..." calls +# accordingly. _IDSTACK="" # Marketplace cache holds one dir per installed version. Sort the basenames by # numeric version fields, not lexically — plain sort ranks 3.9.0.0 above @@ -702,7 +696,7 @@ if [ -d "$_idstack_cache_root" ]; then _idstack_v=$(ls "$_idstack_cache_root" 2>/dev/null | sort -t. -k1,1n -k2,2n -k3,3n -k4,4n | tail -1) [ -n "$_idstack_v" ] && _idstack_cache="$_idstack_cache_root/$_idstack_v" fi -for _p in "${CLAUDE_PLUGIN_ROOT:-}" "${IDSTACK_HOME:-}" "$HOME/.agents/plugins/idstack" "$HOME/.agents/skills/idstack" "$_idstack_cache"; do +for _p in "${CLAUDE_PLUGIN_ROOT:-}" "${IDSTACK_HOME:-}" "$_idstack_cache"; do if [ -n "$_p" ] && [ -d "$_p" ]; then _IDSTACK="${_p%/}"; break; fi done # Compute the course slug from project_name and prepare the export folder. @@ -747,9 +741,9 @@ The `assessments` payload must include `report_path` set to the value of `$_REPO ```bash # Resolve the idstack install dir. Re-derived at the top of every bash block — # blocks run in separate shells, so a value derived in an earlier block is not -# available here. Priority: explicit env overrides, Codex-style symlinks, then -# the Claude Code marketplace cache. Empty if none found; guard -# "$_IDSTACK/bin/..." calls accordingly. +# available here. Priority: explicit env overrides, then the Claude Code +# marketplace cache. Empty if none found; guard "$_IDSTACK/bin/..." calls +# accordingly. _IDSTACK="" # Marketplace cache holds one dir per installed version. Sort the basenames by # numeric version fields, not lexically — plain sort ranks 3.9.0.0 above @@ -760,7 +754,7 @@ if [ -d "$_idstack_cache_root" ]; then _idstack_v=$(ls "$_idstack_cache_root" 2>/dev/null | sort -t. -k1,1n -k2,2n -k3,3n -k4,4n | tail -1) [ -n "$_idstack_v" ] && _idstack_cache="$_idstack_cache_root/$_idstack_v" fi -for _p in "${CLAUDE_PLUGIN_ROOT:-}" "${IDSTACK_HOME:-}" "$HOME/.agents/plugins/idstack" "$HOME/.agents/skills/idstack" "$_idstack_cache"; do +for _p in "${CLAUDE_PLUGIN_ROOT:-}" "${IDSTACK_HOME:-}" "$_idstack_cache"; do if [ -n "$_p" ] && [ -d "$_p" ]; then _IDSTACK="${_p%/}"; break; fi done # Section 1: assessments @@ -783,9 +777,9 @@ If `.idstack/project.json` does not exist yet — the standalone case, where the ```bash # Resolve the idstack install dir. Re-derived at the top of every bash block — # blocks run in separate shells, so a value derived in an earlier block is not -# available here. Priority: explicit env overrides, Codex-style symlinks, then -# the Claude Code marketplace cache. Empty if none found; guard -# "$_IDSTACK/bin/..." calls accordingly. +# available here. Priority: explicit env overrides, then the Claude Code +# marketplace cache. Empty if none found; guard "$_IDSTACK/bin/..." calls +# accordingly. _IDSTACK="" # Marketplace cache holds one dir per installed version. Sort the basenames by # numeric version fields, not lexically — plain sort ranks 3.9.0.0 above @@ -796,7 +790,7 @@ if [ -d "$_idstack_cache_root" ]; then _idstack_v=$(ls "$_idstack_cache_root" 2>/dev/null | sort -t. -k1,1n -k2,2n -k3,3n -k4,4n | tail -1) [ -n "$_idstack_v" ] && _idstack_cache="$_idstack_cache_root/$_idstack_v" fi -for _p in "${CLAUDE_PLUGIN_ROOT:-}" "${IDSTACK_HOME:-}" "$HOME/.agents/plugins/idstack" "$HOME/.agents/skills/idstack" "$_idstack_cache"; do +for _p in "${CLAUDE_PLUGIN_ROOT:-}" "${IDSTACK_HOME:-}" "$_idstack_cache"; do if [ -n "$_p" ] && [ -d "$_p" ]; then _IDSTACK="${_p%/}"; break; fi done "$_IDSTACK/bin/idstack-migrate" --init .idstack/project.json >/dev/null @@ -916,9 +910,9 @@ The HTML report follows the visual contract in `templates/report.html.tmpl` and ```bash # Resolve the idstack install dir. Re-derived at the top of every bash block — # blocks run in separate shells, so a value derived in an earlier block is not -# available here. Priority: explicit env overrides, Codex-style symlinks, then -# the Claude Code marketplace cache. Empty if none found; guard -# "$_IDSTACK/bin/..." calls accordingly. +# available here. Priority: explicit env overrides, then the Claude Code +# marketplace cache. Empty if none found; guard "$_IDSTACK/bin/..." calls +# accordingly. _IDSTACK="" # Marketplace cache holds one dir per installed version. Sort the basenames by # numeric version fields, not lexically — plain sort ranks 3.9.0.0 above @@ -929,7 +923,7 @@ if [ -d "$_idstack_cache_root" ]; then _idstack_v=$(ls "$_idstack_cache_root" 2>/dev/null | sort -t. -k1,1n -k2,2n -k3,3n -k4,4n | tail -1) [ -n "$_idstack_v" ] && _idstack_cache="$_idstack_cache_root/$_idstack_v" fi -for _p in "${CLAUDE_PLUGIN_ROOT:-}" "${IDSTACK_HOME:-}" "$HOME/.agents/plugins/idstack" "$HOME/.agents/skills/idstack" "$_idstack_cache"; do +for _p in "${CLAUDE_PLUGIN_ROOT:-}" "${IDSTACK_HOME:-}" "$_idstack_cache"; do if [ -n "$_p" ] && [ -d "$_p" ]; then _IDSTACK="${_p%/}"; break; fi done # Write a payload for your skill's section, then: @@ -1316,9 +1310,9 @@ After the skill workflow completes successfully, log the session to the timeline ```bash # Resolve the idstack install dir. Re-derived at the top of every bash block — # blocks run in separate shells, so a value derived in an earlier block is not -# available here. Priority: explicit env overrides, Codex-style symlinks, then -# the Claude Code marketplace cache. Empty if none found; guard -# "$_IDSTACK/bin/..." calls accordingly. +# available here. Priority: explicit env overrides, then the Claude Code +# marketplace cache. Empty if none found; guard "$_IDSTACK/bin/..." calls +# accordingly. _IDSTACK="" # Marketplace cache holds one dir per installed version. Sort the basenames by # numeric version fields, not lexically — plain sort ranks 3.9.0.0 above @@ -1329,7 +1323,7 @@ if [ -d "$_idstack_cache_root" ]; then _idstack_v=$(ls "$_idstack_cache_root" 2>/dev/null | sort -t. -k1,1n -k2,2n -k3,3n -k4,4n | tail -1) [ -n "$_idstack_v" ] && _idstack_cache="$_idstack_cache_root/$_idstack_v" fi -for _p in "${CLAUDE_PLUGIN_ROOT:-}" "${IDSTACK_HOME:-}" "$HOME/.agents/plugins/idstack" "$HOME/.agents/skills/idstack" "$_idstack_cache"; do +for _p in "${CLAUDE_PLUGIN_ROOT:-}" "${IDSTACK_HOME:-}" "$_idstack_cache"; do if [ -n "$_p" ] && [ -d "$_p" ]; then _IDSTACK="${_p%/}"; break; fi done "$_IDSTACK/bin/idstack-timeline-log" '{"skill":"assessment-design","event":"completed"}' @@ -1344,9 +1338,9 @@ import format issue, course structure pattern), also log it as a learning: ```bash # Resolve the idstack install dir. Re-derived at the top of every bash block — # blocks run in separate shells, so a value derived in an earlier block is not -# available here. Priority: explicit env overrides, Codex-style symlinks, then -# the Claude Code marketplace cache. Empty if none found; guard -# "$_IDSTACK/bin/..." calls accordingly. +# available here. Priority: explicit env overrides, then the Claude Code +# marketplace cache. Empty if none found; guard "$_IDSTACK/bin/..." calls +# accordingly. _IDSTACK="" # Marketplace cache holds one dir per installed version. Sort the basenames by # numeric version fields, not lexically — plain sort ranks 3.9.0.0 above @@ -1357,7 +1351,7 @@ if [ -d "$_idstack_cache_root" ]; then _idstack_v=$(ls "$_idstack_cache_root" 2>/dev/null | sort -t. -k1,1n -k2,2n -k3,3n -k4,4n | tail -1) [ -n "$_idstack_v" ] && _idstack_cache="$_idstack_cache_root/$_idstack_v" fi -for _p in "${CLAUDE_PLUGIN_ROOT:-}" "${IDSTACK_HOME:-}" "$HOME/.agents/plugins/idstack" "$HOME/.agents/skills/idstack" "$_idstack_cache"; do +for _p in "${CLAUDE_PLUGIN_ROOT:-}" "${IDSTACK_HOME:-}" "$_idstack_cache"; do if [ -n "$_p" ] && [ -d "$_p" ]; then _IDSTACK="${_p%/}"; break; fi done "$_IDSTACK/bin/idstack-learnings-log" '{"skill":"assessment-design","type":"operational","key":"SHORT_KEY","insight":"DESCRIPTION","confidence":8,"source":"observed"}' diff --git a/skills/course-builder/SKILL.md b/skills/course-builder/SKILL.md index 93755bc..baad9cc 100644 --- a/skills/course-builder/SKILL.md +++ b/skills/course-builder/SKILL.md @@ -21,29 +21,23 @@ allowed-tools: ## Preamble: Interaction Conventions -idstack skills are designed to run in multiple CLIs (Claude Code, OpenAI Codex CLI, and -others). To stay portable, skill bodies use a few **concept names** that have a CLI-specific -implementation: +idstack runs in Claude Code. Skill bodies use a few **concept names** for the tools they +lean on: - **AskUserQuestion** — when a skill says "ask via AskUserQuestion" or "using AskUserQuestion", - it means: present a single numbered multiple-choice question (e.g., "Which of these best - describes X? 1) ..., 2) ..., 3) Other") and stop, waiting for the user's next message - before proceeding. Ask **one** question at a time, never batch. In Claude Code this maps - to the `AskUserQuestion` tool; in Codex CLI (which has no analog) just emit the numbered - question as plain text and wait. -- **Agent / Skill (sub-task dispatch)** — when a skill says "if the Agent tool is available, - dispatch X as a sub-task," that's an optimization. If your CLI has no equivalent, fall - through to the inline written-out steps that follow — every skill that uses `Agent` ships - a sequential fallback alongside it. -- **Skill (cross-skill invocation)** — used only by `/idstack:pipeline`. In Claude Code this - invokes a child skill in-process via the `Skill` tool. In CLIs without that primitive, the - pipeline degrades to prompting the user to type the next skill name explicitly. -- **Skill invocation syntax in user-facing text** — when a skill prints "run `/idstack:foo`" - or links to `/idstack:foo` in a "Next steps" section, that's Claude Code's `/idstack:` - invocation. **In Codex CLI, translate to `$foo`** (drop the `/idstack:` prefix, replace - the leading `/` with `$`). Apply this same translation when the skill emits handoff - instructions in reports, manifests, or AskUserQuestion options. Same body text, two - hosts; the model translates per-CLI on output. + it means: present a single multiple-choice question (e.g., "Which of these best describes X?") + and stop, waiting for the user's answer before proceeding. Ask **one** question at a time, + never batch. This maps to the `AskUserQuestion` tool. +- **Agent (sub-task dispatch)** — when a skill says "if the Agent tool is available, dispatch + X as a sub-task," that's an optimization. `Agent` is not in every skill's `allowed-tools`, + so when it isn't available, fall through to the inline written-out steps that follow — + every skill that uses `Agent` ships a sequential fallback alongside it. +- **Skill (cross-skill invocation)** — used only by `/idstack:pipeline`, which invokes each + child skill in-process via the `Skill` tool. +- **Skill invocation syntax in user-facing text** — every skill is invoked as + `/idstack:`. Always write the namespaced form: a bare `/` is not a valid + command. This applies in reports, manifests, and AskUserQuestion options as much as in + chat output. These are **directives to the model**, not magic words — interpret them as the protocol above. @@ -52,9 +46,9 @@ These are **directives to the model**, not magic words — interpret them as the ```bash # Resolve the idstack install dir. Re-derived at the top of every bash block — # blocks run in separate shells, so a value derived in an earlier block is not -# available here. Priority: explicit env overrides, Codex-style symlinks, then -# the Claude Code marketplace cache (highest version). Empty if none found; -# guard "$_IDSTACK/bin/..." calls accordingly. +# available here. Priority: explicit env overrides, then the Claude Code +# marketplace cache (highest version). Empty if none found; guard +# "$_IDSTACK/bin/..." calls accordingly. # Canonical copy: templates/snippets/idstack-resolve.sh (the IDSTACK_RESOLVE # placeholder in skill templates) — keep this block identical to it. _IDSTACK="" @@ -67,7 +61,7 @@ if [ -d "$_idstack_cache_root" ]; then _idstack_v=$(ls "$_idstack_cache_root" 2>/dev/null | sort -t. -k1,1n -k2,2n -k3,3n -k4,4n | tail -1) [ -n "$_idstack_v" ] && _idstack_cache="$_idstack_cache_root/$_idstack_v" fi -for _p in "${CLAUDE_PLUGIN_ROOT:-}" "${IDSTACK_HOME:-}" "$HOME/.agents/plugins/idstack" "$HOME/.agents/skills/idstack" "$_idstack_cache"; do +for _p in "${CLAUDE_PLUGIN_ROOT:-}" "${IDSTACK_HOME:-}" "$_idstack_cache"; do if [ -n "$_p" ] && [ -d "$_p" ]; then _IDSTACK="${_p%/}"; break; fi done _UPD=$("$_IDSTACK/bin/idstack-update-check" 2>/dev/null || true) @@ -92,7 +86,7 @@ if [ -d "$_idstack_cache_root" ]; then _idstack_v=$(ls "$_idstack_cache_root" 2>/dev/null | sort -t. -k1,1n -k2,2n -k3,3n -k4,4n | tail -1) [ -n "$_idstack_v" ] && _idstack_cache="$_idstack_cache_root/$_idstack_v" fi -for _p in "${CLAUDE_PLUGIN_ROOT:-}" "${IDSTACK_HOME:-}" "$HOME/.agents/plugins/idstack" "$HOME/.agents/skills/idstack" "$_idstack_cache"; do +for _p in "${CLAUDE_PLUGIN_ROOT:-}" "${IDSTACK_HOME:-}" "$_idstack_cache"; do if [ -n "$_p" ] && [ -d "$_p" ]; then _IDSTACK="${_p%/}"; break; fi done if [ -f ".idstack/project.json" ]; then @@ -175,7 +169,7 @@ if [ -d "$_idstack_cache_root" ]; then _idstack_v=$(ls "$_idstack_cache_root" 2>/dev/null | sort -t. -k1,1n -k2,2n -k3,3n -k4,4n | tail -1) [ -n "$_idstack_v" ] && _idstack_cache="$_idstack_cache_root/$_idstack_v" fi -for _p in "${CLAUDE_PLUGIN_ROOT:-}" "${IDSTACK_HOME:-}" "$HOME/.agents/plugins/idstack" "$HOME/.agents/skills/idstack" "$_idstack_cache"; do +for _p in "${CLAUDE_PLUGIN_ROOT:-}" "${IDSTACK_HOME:-}" "$_idstack_cache"; do if [ -n "$_p" ] && [ -d "$_p" ]; then _IDSTACK="${_p%/}"; break; fi done _HAS_TIMELINE=0 @@ -801,9 +795,9 @@ Before updating the manifest, generate an HTML build report so the designer can ```bash # Resolve the idstack install dir. Re-derived at the top of every bash block — # blocks run in separate shells, so a value derived in an earlier block is not -# available here. Priority: explicit env overrides, Codex-style symlinks, then -# the Claude Code marketplace cache. Empty if none found; guard -# "$_IDSTACK/bin/..." calls accordingly. +# available here. Priority: explicit env overrides, then the Claude Code +# marketplace cache. Empty if none found; guard "$_IDSTACK/bin/..." calls +# accordingly. _IDSTACK="" # Marketplace cache holds one dir per installed version. Sort the basenames by # numeric version fields, not lexically — plain sort ranks 3.9.0.0 above @@ -814,7 +808,7 @@ if [ -d "$_idstack_cache_root" ]; then _idstack_v=$(ls "$_idstack_cache_root" 2>/dev/null | sort -t. -k1,1n -k2,2n -k3,3n -k4,4n | tail -1) [ -n "$_idstack_v" ] && _idstack_cache="$_idstack_cache_root/$_idstack_v" fi -for _p in "${CLAUDE_PLUGIN_ROOT:-}" "${IDSTACK_HOME:-}" "$HOME/.agents/plugins/idstack" "$HOME/.agents/skills/idstack" "$_idstack_cache"; do +for _p in "${CLAUDE_PLUGIN_ROOT:-}" "${IDSTACK_HOME:-}" "$_idstack_cache"; do if [ -n "$_p" ] && [ -d "$_p" ]; then _IDSTACK="${_p%/}"; break; fi done # Compute the course slug from project_name and prepare the export folder. @@ -846,9 +840,9 @@ Save the `course_content` section to `.idstack/project.json` via `bin/idstack-ma ```bash # Resolve the idstack install dir. Re-derived at the top of every bash block — # blocks run in separate shells, so a value derived in an earlier block is not -# available here. Priority: explicit env overrides, Codex-style symlinks, then -# the Claude Code marketplace cache. Empty if none found; guard -# "$_IDSTACK/bin/..." calls accordingly. +# available here. Priority: explicit env overrides, then the Claude Code +# marketplace cache. Empty if none found; guard "$_IDSTACK/bin/..." calls +# accordingly. _IDSTACK="" # Marketplace cache holds one dir per installed version. Sort the basenames by # numeric version fields, not lexically — plain sort ranks 3.9.0.0 above @@ -859,7 +853,7 @@ if [ -d "$_idstack_cache_root" ]; then _idstack_v=$(ls "$_idstack_cache_root" 2>/dev/null | sort -t. -k1,1n -k2,2n -k3,3n -k4,4n | tail -1) [ -n "$_idstack_v" ] && _idstack_cache="$_idstack_cache_root/$_idstack_v" fi -for _p in "${CLAUDE_PLUGIN_ROOT:-}" "${IDSTACK_HOME:-}" "$HOME/.agents/plugins/idstack" "$HOME/.agents/skills/idstack" "$_idstack_cache"; do +for _p in "${CLAUDE_PLUGIN_ROOT:-}" "${IDSTACK_HOME:-}" "$_idstack_cache"; do if [ -n "$_p" ] && [ -d "$_p" ]; then _IDSTACK="${_p%/}"; break; fi done "$_IDSTACK/bin/idstack-manifest-merge" --section course_content --payload - <<'PAYLOAD' @@ -992,9 +986,9 @@ The HTML report follows the visual contract in `templates/report.html.tmpl` and ```bash # Resolve the idstack install dir. Re-derived at the top of every bash block — # blocks run in separate shells, so a value derived in an earlier block is not -# available here. Priority: explicit env overrides, Codex-style symlinks, then -# the Claude Code marketplace cache. Empty if none found; guard -# "$_IDSTACK/bin/..." calls accordingly. +# available here. Priority: explicit env overrides, then the Claude Code +# marketplace cache. Empty if none found; guard "$_IDSTACK/bin/..." calls +# accordingly. _IDSTACK="" # Marketplace cache holds one dir per installed version. Sort the basenames by # numeric version fields, not lexically — plain sort ranks 3.9.0.0 above @@ -1005,7 +999,7 @@ if [ -d "$_idstack_cache_root" ]; then _idstack_v=$(ls "$_idstack_cache_root" 2>/dev/null | sort -t. -k1,1n -k2,2n -k3,3n -k4,4n | tail -1) [ -n "$_idstack_v" ] && _idstack_cache="$_idstack_cache_root/$_idstack_v" fi -for _p in "${CLAUDE_PLUGIN_ROOT:-}" "${IDSTACK_HOME:-}" "$HOME/.agents/plugins/idstack" "$HOME/.agents/skills/idstack" "$_idstack_cache"; do +for _p in "${CLAUDE_PLUGIN_ROOT:-}" "${IDSTACK_HOME:-}" "$_idstack_cache"; do if [ -n "$_p" ] && [ -d "$_p" ]; then _IDSTACK="${_p%/}"; break; fi done # Write a payload for your skill's section, then: @@ -1392,9 +1386,9 @@ After the skill workflow completes successfully, log the session to the timeline ```bash # Resolve the idstack install dir. Re-derived at the top of every bash block — # blocks run in separate shells, so a value derived in an earlier block is not -# available here. Priority: explicit env overrides, Codex-style symlinks, then -# the Claude Code marketplace cache. Empty if none found; guard -# "$_IDSTACK/bin/..." calls accordingly. +# available here. Priority: explicit env overrides, then the Claude Code +# marketplace cache. Empty if none found; guard "$_IDSTACK/bin/..." calls +# accordingly. _IDSTACK="" # Marketplace cache holds one dir per installed version. Sort the basenames by # numeric version fields, not lexically — plain sort ranks 3.9.0.0 above @@ -1405,7 +1399,7 @@ if [ -d "$_idstack_cache_root" ]; then _idstack_v=$(ls "$_idstack_cache_root" 2>/dev/null | sort -t. -k1,1n -k2,2n -k3,3n -k4,4n | tail -1) [ -n "$_idstack_v" ] && _idstack_cache="$_idstack_cache_root/$_idstack_v" fi -for _p in "${CLAUDE_PLUGIN_ROOT:-}" "${IDSTACK_HOME:-}" "$HOME/.agents/plugins/idstack" "$HOME/.agents/skills/idstack" "$_idstack_cache"; do +for _p in "${CLAUDE_PLUGIN_ROOT:-}" "${IDSTACK_HOME:-}" "$_idstack_cache"; do if [ -n "$_p" ] && [ -d "$_p" ]; then _IDSTACK="${_p%/}"; break; fi done "$_IDSTACK/bin/idstack-timeline-log" '{"skill":"course-builder","event":"completed"}' @@ -1420,9 +1414,9 @@ import format issue, course structure pattern), also log it as a learning: ```bash # Resolve the idstack install dir. Re-derived at the top of every bash block — # blocks run in separate shells, so a value derived in an earlier block is not -# available here. Priority: explicit env overrides, Codex-style symlinks, then -# the Claude Code marketplace cache. Empty if none found; guard -# "$_IDSTACK/bin/..." calls accordingly. +# available here. Priority: explicit env overrides, then the Claude Code +# marketplace cache. Empty if none found; guard "$_IDSTACK/bin/..." calls +# accordingly. _IDSTACK="" # Marketplace cache holds one dir per installed version. Sort the basenames by # numeric version fields, not lexically — plain sort ranks 3.9.0.0 above @@ -1433,7 +1427,7 @@ if [ -d "$_idstack_cache_root" ]; then _idstack_v=$(ls "$_idstack_cache_root" 2>/dev/null | sort -t. -k1,1n -k2,2n -k3,3n -k4,4n | tail -1) [ -n "$_idstack_v" ] && _idstack_cache="$_idstack_cache_root/$_idstack_v" fi -for _p in "${CLAUDE_PLUGIN_ROOT:-}" "${IDSTACK_HOME:-}" "$HOME/.agents/plugins/idstack" "$HOME/.agents/skills/idstack" "$_idstack_cache"; do +for _p in "${CLAUDE_PLUGIN_ROOT:-}" "${IDSTACK_HOME:-}" "$_idstack_cache"; do if [ -n "$_p" ] && [ -d "$_p" ]; then _IDSTACK="${_p%/}"; break; fi done "$_IDSTACK/bin/idstack-learnings-log" '{"skill":"course-builder","type":"operational","key":"SHORT_KEY","insight":"DESCRIPTION","confidence":8,"source":"observed"}' diff --git a/skills/course-export/SKILL.md b/skills/course-export/SKILL.md index 5579704..07be63d 100644 --- a/skills/course-export/SKILL.md +++ b/skills/course-export/SKILL.md @@ -21,29 +21,23 @@ allowed-tools: ## Preamble: Interaction Conventions -idstack skills are designed to run in multiple CLIs (Claude Code, OpenAI Codex CLI, and -others). To stay portable, skill bodies use a few **concept names** that have a CLI-specific -implementation: +idstack runs in Claude Code. Skill bodies use a few **concept names** for the tools they +lean on: - **AskUserQuestion** — when a skill says "ask via AskUserQuestion" or "using AskUserQuestion", - it means: present a single numbered multiple-choice question (e.g., "Which of these best - describes X? 1) ..., 2) ..., 3) Other") and stop, waiting for the user's next message - before proceeding. Ask **one** question at a time, never batch. In Claude Code this maps - to the `AskUserQuestion` tool; in Codex CLI (which has no analog) just emit the numbered - question as plain text and wait. -- **Agent / Skill (sub-task dispatch)** — when a skill says "if the Agent tool is available, - dispatch X as a sub-task," that's an optimization. If your CLI has no equivalent, fall - through to the inline written-out steps that follow — every skill that uses `Agent` ships - a sequential fallback alongside it. -- **Skill (cross-skill invocation)** — used only by `/idstack:pipeline`. In Claude Code this - invokes a child skill in-process via the `Skill` tool. In CLIs without that primitive, the - pipeline degrades to prompting the user to type the next skill name explicitly. -- **Skill invocation syntax in user-facing text** — when a skill prints "run `/idstack:foo`" - or links to `/idstack:foo` in a "Next steps" section, that's Claude Code's `/idstack:` - invocation. **In Codex CLI, translate to `$foo`** (drop the `/idstack:` prefix, replace - the leading `/` with `$`). Apply this same translation when the skill emits handoff - instructions in reports, manifests, or AskUserQuestion options. Same body text, two - hosts; the model translates per-CLI on output. + it means: present a single multiple-choice question (e.g., "Which of these best describes X?") + and stop, waiting for the user's answer before proceeding. Ask **one** question at a time, + never batch. This maps to the `AskUserQuestion` tool. +- **Agent (sub-task dispatch)** — when a skill says "if the Agent tool is available, dispatch + X as a sub-task," that's an optimization. `Agent` is not in every skill's `allowed-tools`, + so when it isn't available, fall through to the inline written-out steps that follow — + every skill that uses `Agent` ships a sequential fallback alongside it. +- **Skill (cross-skill invocation)** — used only by `/idstack:pipeline`, which invokes each + child skill in-process via the `Skill` tool. +- **Skill invocation syntax in user-facing text** — every skill is invoked as + `/idstack:`. Always write the namespaced form: a bare `/` is not a valid + command. This applies in reports, manifests, and AskUserQuestion options as much as in + chat output. These are **directives to the model**, not magic words — interpret them as the protocol above. @@ -52,9 +46,9 @@ These are **directives to the model**, not magic words — interpret them as the ```bash # Resolve the idstack install dir. Re-derived at the top of every bash block — # blocks run in separate shells, so a value derived in an earlier block is not -# available here. Priority: explicit env overrides, Codex-style symlinks, then -# the Claude Code marketplace cache (highest version). Empty if none found; -# guard "$_IDSTACK/bin/..." calls accordingly. +# available here. Priority: explicit env overrides, then the Claude Code +# marketplace cache (highest version). Empty if none found; guard +# "$_IDSTACK/bin/..." calls accordingly. # Canonical copy: templates/snippets/idstack-resolve.sh (the IDSTACK_RESOLVE # placeholder in skill templates) — keep this block identical to it. _IDSTACK="" @@ -67,7 +61,7 @@ if [ -d "$_idstack_cache_root" ]; then _idstack_v=$(ls "$_idstack_cache_root" 2>/dev/null | sort -t. -k1,1n -k2,2n -k3,3n -k4,4n | tail -1) [ -n "$_idstack_v" ] && _idstack_cache="$_idstack_cache_root/$_idstack_v" fi -for _p in "${CLAUDE_PLUGIN_ROOT:-}" "${IDSTACK_HOME:-}" "$HOME/.agents/plugins/idstack" "$HOME/.agents/skills/idstack" "$_idstack_cache"; do +for _p in "${CLAUDE_PLUGIN_ROOT:-}" "${IDSTACK_HOME:-}" "$_idstack_cache"; do if [ -n "$_p" ] && [ -d "$_p" ]; then _IDSTACK="${_p%/}"; break; fi done _UPD=$("$_IDSTACK/bin/idstack-update-check" 2>/dev/null || true) @@ -92,7 +86,7 @@ if [ -d "$_idstack_cache_root" ]; then _idstack_v=$(ls "$_idstack_cache_root" 2>/dev/null | sort -t. -k1,1n -k2,2n -k3,3n -k4,4n | tail -1) [ -n "$_idstack_v" ] && _idstack_cache="$_idstack_cache_root/$_idstack_v" fi -for _p in "${CLAUDE_PLUGIN_ROOT:-}" "${IDSTACK_HOME:-}" "$HOME/.agents/plugins/idstack" "$HOME/.agents/skills/idstack" "$_idstack_cache"; do +for _p in "${CLAUDE_PLUGIN_ROOT:-}" "${IDSTACK_HOME:-}" "$_idstack_cache"; do if [ -n "$_p" ] && [ -d "$_p" ]; then _IDSTACK="${_p%/}"; break; fi done if [ -f ".idstack/project.json" ]; then @@ -175,7 +169,7 @@ if [ -d "$_idstack_cache_root" ]; then _idstack_v=$(ls "$_idstack_cache_root" 2>/dev/null | sort -t. -k1,1n -k2,2n -k3,3n -k4,4n | tail -1) [ -n "$_idstack_v" ] && _idstack_cache="$_idstack_cache_root/$_idstack_v" fi -for _p in "${CLAUDE_PLUGIN_ROOT:-}" "${IDSTACK_HOME:-}" "$HOME/.agents/plugins/idstack" "$HOME/.agents/skills/idstack" "$_idstack_cache"; do +for _p in "${CLAUDE_PLUGIN_ROOT:-}" "${IDSTACK_HOME:-}" "$_idstack_cache"; do if [ -n "$_p" ] && [ -d "$_p" ]; then _IDSTACK="${_p%/}"; break; fi done _HAS_TIMELINE=0 @@ -279,9 +273,9 @@ Before starting the export workflow, run the readiness dashboard: ```bash # Resolve the idstack install dir. Re-derived at the top of every bash block — # blocks run in separate shells, so a value derived in an earlier block is not -# available here. Priority: explicit env overrides, Codex-style symlinks, then -# the Claude Code marketplace cache. Empty if none found; guard -# "$_IDSTACK/bin/..." calls accordingly. +# available here. Priority: explicit env overrides, then the Claude Code +# marketplace cache. Empty if none found; guard "$_IDSTACK/bin/..." calls +# accordingly. _IDSTACK="" # Marketplace cache holds one dir per installed version. Sort the basenames by # numeric version fields, not lexically — plain sort ranks 3.9.0.0 above @@ -292,7 +286,7 @@ if [ -d "$_idstack_cache_root" ]; then _idstack_v=$(ls "$_idstack_cache_root" 2>/dev/null | sort -t. -k1,1n -k2,2n -k3,3n -k4,4n | tail -1) [ -n "$_idstack_v" ] && _idstack_cache="$_idstack_cache_root/$_idstack_v" fi -for _p in "${CLAUDE_PLUGIN_ROOT:-}" "${IDSTACK_HOME:-}" "$HOME/.agents/plugins/idstack" "$HOME/.agents/skills/idstack" "$_idstack_cache"; do +for _p in "${CLAUDE_PLUGIN_ROOT:-}" "${IDSTACK_HOME:-}" "$_idstack_cache"; do if [ -n "$_p" ] && [ -d "$_p" ]; then _IDSTACK="${_p%/}"; break; fi done "$_IDSTACK/bin/idstack-status" --readiness @@ -383,9 +377,9 @@ now, then reuse `$_EXPORT_DIR` throughout the workflow: ```bash # Resolve the idstack install dir. Re-derived at the top of every bash block — # blocks run in separate shells, so a value derived in an earlier block is not -# available here. Priority: explicit env overrides, Codex-style symlinks, then -# the Claude Code marketplace cache. Empty if none found; guard -# "$_IDSTACK/bin/..." calls accordingly. +# available here. Priority: explicit env overrides, then the Claude Code +# marketplace cache. Empty if none found; guard "$_IDSTACK/bin/..." calls +# accordingly. _IDSTACK="" # Marketplace cache holds one dir per installed version. Sort the basenames by # numeric version fields, not lexically — plain sort ranks 3.9.0.0 above @@ -396,7 +390,7 @@ if [ -d "$_idstack_cache_root" ]; then _idstack_v=$(ls "$_idstack_cache_root" 2>/dev/null | sort -t. -k1,1n -k2,2n -k3,3n -k4,4n | tail -1) [ -n "$_idstack_v" ] && _idstack_cache="$_idstack_cache_root/$_idstack_v" fi -for _p in "${CLAUDE_PLUGIN_ROOT:-}" "${IDSTACK_HOME:-}" "$HOME/.agents/plugins/idstack" "$HOME/.agents/skills/idstack" "$_idstack_cache"; do +for _p in "${CLAUDE_PLUGIN_ROOT:-}" "${IDSTACK_HOME:-}" "$_idstack_cache"; do if [ -n "$_p" ] && [ -d "$_p" ]; then _IDSTACK="${_p%/}"; break; fi done # Course-slug-based export folder. Required before any artifact write. @@ -1165,9 +1159,9 @@ top-level `updated` timestamp, and writes atomically: ```bash # Resolve the idstack install dir. Re-derived at the top of every bash block — # blocks run in separate shells, so a value derived in an earlier block is not -# available here. Priority: explicit env overrides, Codex-style symlinks, then -# the Claude Code marketplace cache. Empty if none found; guard -# "$_IDSTACK/bin/..." calls accordingly. +# available here. Priority: explicit env overrides, then the Claude Code +# marketplace cache. Empty if none found; guard "$_IDSTACK/bin/..." calls +# accordingly. _IDSTACK="" # Marketplace cache holds one dir per installed version. Sort the basenames by # numeric version fields, not lexically — plain sort ranks 3.9.0.0 above @@ -1178,7 +1172,7 @@ if [ -d "$_idstack_cache_root" ]; then _idstack_v=$(ls "$_idstack_cache_root" 2>/dev/null | sort -t. -k1,1n -k2,2n -k3,3n -k4,4n | tail -1) [ -n "$_idstack_v" ] && _idstack_cache="$_idstack_cache_root/$_idstack_v" fi -for _p in "${CLAUDE_PLUGIN_ROOT:-}" "${IDSTACK_HOME:-}" "$HOME/.agents/plugins/idstack" "$HOME/.agents/skills/idstack" "$_idstack_cache"; do +for _p in "${CLAUDE_PLUGIN_ROOT:-}" "${IDSTACK_HOME:-}" "$_idstack_cache"; do if [ -n "$_p" ] && [ -d "$_p" ]; then _IDSTACK="${_p%/}"; break; fi done # payload file holds ONLY the export_metadata section object (shape below) @@ -1296,9 +1290,9 @@ The HTML report follows the visual contract in `templates/report.html.tmpl` and ```bash # Resolve the idstack install dir. Re-derived at the top of every bash block — # blocks run in separate shells, so a value derived in an earlier block is not -# available here. Priority: explicit env overrides, Codex-style symlinks, then -# the Claude Code marketplace cache. Empty if none found; guard -# "$_IDSTACK/bin/..." calls accordingly. +# available here. Priority: explicit env overrides, then the Claude Code +# marketplace cache. Empty if none found; guard "$_IDSTACK/bin/..." calls +# accordingly. _IDSTACK="" # Marketplace cache holds one dir per installed version. Sort the basenames by # numeric version fields, not lexically — plain sort ranks 3.9.0.0 above @@ -1309,7 +1303,7 @@ if [ -d "$_idstack_cache_root" ]; then _idstack_v=$(ls "$_idstack_cache_root" 2>/dev/null | sort -t. -k1,1n -k2,2n -k3,3n -k4,4n | tail -1) [ -n "$_idstack_v" ] && _idstack_cache="$_idstack_cache_root/$_idstack_v" fi -for _p in "${CLAUDE_PLUGIN_ROOT:-}" "${IDSTACK_HOME:-}" "$HOME/.agents/plugins/idstack" "$HOME/.agents/skills/idstack" "$_idstack_cache"; do +for _p in "${CLAUDE_PLUGIN_ROOT:-}" "${IDSTACK_HOME:-}" "$_idstack_cache"; do if [ -n "$_p" ] && [ -d "$_p" ]; then _IDSTACK="${_p%/}"; break; fi done # Write a payload for your skill's section, then: @@ -1696,9 +1690,9 @@ After the skill workflow completes successfully, log the session to the timeline ```bash # Resolve the idstack install dir. Re-derived at the top of every bash block — # blocks run in separate shells, so a value derived in an earlier block is not -# available here. Priority: explicit env overrides, Codex-style symlinks, then -# the Claude Code marketplace cache. Empty if none found; guard -# "$_IDSTACK/bin/..." calls accordingly. +# available here. Priority: explicit env overrides, then the Claude Code +# marketplace cache. Empty if none found; guard "$_IDSTACK/bin/..." calls +# accordingly. _IDSTACK="" # Marketplace cache holds one dir per installed version. Sort the basenames by # numeric version fields, not lexically — plain sort ranks 3.9.0.0 above @@ -1709,7 +1703,7 @@ if [ -d "$_idstack_cache_root" ]; then _idstack_v=$(ls "$_idstack_cache_root" 2>/dev/null | sort -t. -k1,1n -k2,2n -k3,3n -k4,4n | tail -1) [ -n "$_idstack_v" ] && _idstack_cache="$_idstack_cache_root/$_idstack_v" fi -for _p in "${CLAUDE_PLUGIN_ROOT:-}" "${IDSTACK_HOME:-}" "$HOME/.agents/plugins/idstack" "$HOME/.agents/skills/idstack" "$_idstack_cache"; do +for _p in "${CLAUDE_PLUGIN_ROOT:-}" "${IDSTACK_HOME:-}" "$_idstack_cache"; do if [ -n "$_p" ] && [ -d "$_p" ]; then _IDSTACK="${_p%/}"; break; fi done "$_IDSTACK/bin/idstack-timeline-log" '{"skill":"course-export","event":"completed"}' @@ -1724,9 +1718,9 @@ import format issue, course structure pattern), also log it as a learning: ```bash # Resolve the idstack install dir. Re-derived at the top of every bash block — # blocks run in separate shells, so a value derived in an earlier block is not -# available here. Priority: explicit env overrides, Codex-style symlinks, then -# the Claude Code marketplace cache. Empty if none found; guard -# "$_IDSTACK/bin/..." calls accordingly. +# available here. Priority: explicit env overrides, then the Claude Code +# marketplace cache. Empty if none found; guard "$_IDSTACK/bin/..." calls +# accordingly. _IDSTACK="" # Marketplace cache holds one dir per installed version. Sort the basenames by # numeric version fields, not lexically — plain sort ranks 3.9.0.0 above @@ -1737,7 +1731,7 @@ if [ -d "$_idstack_cache_root" ]; then _idstack_v=$(ls "$_idstack_cache_root" 2>/dev/null | sort -t. -k1,1n -k2,2n -k3,3n -k4,4n | tail -1) [ -n "$_idstack_v" ] && _idstack_cache="$_idstack_cache_root/$_idstack_v" fi -for _p in "${CLAUDE_PLUGIN_ROOT:-}" "${IDSTACK_HOME:-}" "$HOME/.agents/plugins/idstack" "$HOME/.agents/skills/idstack" "$_idstack_cache"; do +for _p in "${CLAUDE_PLUGIN_ROOT:-}" "${IDSTACK_HOME:-}" "$_idstack_cache"; do if [ -n "$_p" ] && [ -d "$_p" ]; then _IDSTACK="${_p%/}"; break; fi done "$_IDSTACK/bin/idstack-learnings-log" '{"skill":"course-export","type":"operational","key":"SHORT_KEY","insight":"DESCRIPTION","confidence":8,"source":"observed"}' diff --git a/skills/course-import/SKILL.md b/skills/course-import/SKILL.md index ec58a74..95f9ddc 100644 --- a/skills/course-import/SKILL.md +++ b/skills/course-import/SKILL.md @@ -21,29 +21,23 @@ allowed-tools: ## Preamble: Interaction Conventions -idstack skills are designed to run in multiple CLIs (Claude Code, OpenAI Codex CLI, and -others). To stay portable, skill bodies use a few **concept names** that have a CLI-specific -implementation: +idstack runs in Claude Code. Skill bodies use a few **concept names** for the tools they +lean on: - **AskUserQuestion** — when a skill says "ask via AskUserQuestion" or "using AskUserQuestion", - it means: present a single numbered multiple-choice question (e.g., "Which of these best - describes X? 1) ..., 2) ..., 3) Other") and stop, waiting for the user's next message - before proceeding. Ask **one** question at a time, never batch. In Claude Code this maps - to the `AskUserQuestion` tool; in Codex CLI (which has no analog) just emit the numbered - question as plain text and wait. -- **Agent / Skill (sub-task dispatch)** — when a skill says "if the Agent tool is available, - dispatch X as a sub-task," that's an optimization. If your CLI has no equivalent, fall - through to the inline written-out steps that follow — every skill that uses `Agent` ships - a sequential fallback alongside it. -- **Skill (cross-skill invocation)** — used only by `/idstack:pipeline`. In Claude Code this - invokes a child skill in-process via the `Skill` tool. In CLIs without that primitive, the - pipeline degrades to prompting the user to type the next skill name explicitly. -- **Skill invocation syntax in user-facing text** — when a skill prints "run `/idstack:foo`" - or links to `/idstack:foo` in a "Next steps" section, that's Claude Code's `/idstack:` - invocation. **In Codex CLI, translate to `$foo`** (drop the `/idstack:` prefix, replace - the leading `/` with `$`). Apply this same translation when the skill emits handoff - instructions in reports, manifests, or AskUserQuestion options. Same body text, two - hosts; the model translates per-CLI on output. + it means: present a single multiple-choice question (e.g., "Which of these best describes X?") + and stop, waiting for the user's answer before proceeding. Ask **one** question at a time, + never batch. This maps to the `AskUserQuestion` tool. +- **Agent (sub-task dispatch)** — when a skill says "if the Agent tool is available, dispatch + X as a sub-task," that's an optimization. `Agent` is not in every skill's `allowed-tools`, + so when it isn't available, fall through to the inline written-out steps that follow — + every skill that uses `Agent` ships a sequential fallback alongside it. +- **Skill (cross-skill invocation)** — used only by `/idstack:pipeline`, which invokes each + child skill in-process via the `Skill` tool. +- **Skill invocation syntax in user-facing text** — every skill is invoked as + `/idstack:`. Always write the namespaced form: a bare `/` is not a valid + command. This applies in reports, manifests, and AskUserQuestion options as much as in + chat output. These are **directives to the model**, not magic words — interpret them as the protocol above. @@ -52,9 +46,9 @@ These are **directives to the model**, not magic words — interpret them as the ```bash # Resolve the idstack install dir. Re-derived at the top of every bash block — # blocks run in separate shells, so a value derived in an earlier block is not -# available here. Priority: explicit env overrides, Codex-style symlinks, then -# the Claude Code marketplace cache (highest version). Empty if none found; -# guard "$_IDSTACK/bin/..." calls accordingly. +# available here. Priority: explicit env overrides, then the Claude Code +# marketplace cache (highest version). Empty if none found; guard +# "$_IDSTACK/bin/..." calls accordingly. # Canonical copy: templates/snippets/idstack-resolve.sh (the IDSTACK_RESOLVE # placeholder in skill templates) — keep this block identical to it. _IDSTACK="" @@ -67,7 +61,7 @@ if [ -d "$_idstack_cache_root" ]; then _idstack_v=$(ls "$_idstack_cache_root" 2>/dev/null | sort -t. -k1,1n -k2,2n -k3,3n -k4,4n | tail -1) [ -n "$_idstack_v" ] && _idstack_cache="$_idstack_cache_root/$_idstack_v" fi -for _p in "${CLAUDE_PLUGIN_ROOT:-}" "${IDSTACK_HOME:-}" "$HOME/.agents/plugins/idstack" "$HOME/.agents/skills/idstack" "$_idstack_cache"; do +for _p in "${CLAUDE_PLUGIN_ROOT:-}" "${IDSTACK_HOME:-}" "$_idstack_cache"; do if [ -n "$_p" ] && [ -d "$_p" ]; then _IDSTACK="${_p%/}"; break; fi done _UPD=$("$_IDSTACK/bin/idstack-update-check" 2>/dev/null || true) @@ -92,7 +86,7 @@ if [ -d "$_idstack_cache_root" ]; then _idstack_v=$(ls "$_idstack_cache_root" 2>/dev/null | sort -t. -k1,1n -k2,2n -k3,3n -k4,4n | tail -1) [ -n "$_idstack_v" ] && _idstack_cache="$_idstack_cache_root/$_idstack_v" fi -for _p in "${CLAUDE_PLUGIN_ROOT:-}" "${IDSTACK_HOME:-}" "$HOME/.agents/plugins/idstack" "$HOME/.agents/skills/idstack" "$_idstack_cache"; do +for _p in "${CLAUDE_PLUGIN_ROOT:-}" "${IDSTACK_HOME:-}" "$_idstack_cache"; do if [ -n "$_p" ] && [ -d "$_p" ]; then _IDSTACK="${_p%/}"; break; fi done if [ -f ".idstack/project.json" ]; then @@ -175,7 +169,7 @@ if [ -d "$_idstack_cache_root" ]; then _idstack_v=$(ls "$_idstack_cache_root" 2>/dev/null | sort -t. -k1,1n -k2,2n -k3,3n -k4,4n | tail -1) [ -n "$_idstack_v" ] && _idstack_cache="$_idstack_cache_root/$_idstack_v" fi -for _p in "${CLAUDE_PLUGIN_ROOT:-}" "${IDSTACK_HOME:-}" "$HOME/.agents/plugins/idstack" "$HOME/.agents/skills/idstack" "$_idstack_cache"; do +for _p in "${CLAUDE_PLUGIN_ROOT:-}" "${IDSTACK_HOME:-}" "$_idstack_cache"; do if [ -n "$_p" ] && [ -d "$_p" ]; then _IDSTACK="${_p%/}"; break; fi done _HAS_TIMELINE=0 @@ -964,9 +958,9 @@ Before writing the manifest, generate an HTML report so the designer has a singl ```bash # Resolve the idstack install dir. Re-derived at the top of every bash block — # blocks run in separate shells, so a value derived in an earlier block is not -# available here. Priority: explicit env overrides, Codex-style symlinks, then -# the Claude Code marketplace cache. Empty if none found; guard -# "$_IDSTACK/bin/..." calls accordingly. +# available here. Priority: explicit env overrides, then the Claude Code +# marketplace cache. Empty if none found; guard "$_IDSTACK/bin/..." calls +# accordingly. _IDSTACK="" # Marketplace cache holds one dir per installed version. Sort the basenames by # numeric version fields, not lexically — plain sort ranks 3.9.0.0 above @@ -977,7 +971,7 @@ if [ -d "$_idstack_cache_root" ]; then _idstack_v=$(ls "$_idstack_cache_root" 2>/dev/null | sort -t. -k1,1n -k2,2n -k3,3n -k4,4n | tail -1) [ -n "$_idstack_v" ] && _idstack_cache="$_idstack_cache_root/$_idstack_v" fi -for _p in "${CLAUDE_PLUGIN_ROOT:-}" "${IDSTACK_HOME:-}" "$HOME/.agents/plugins/idstack" "$HOME/.agents/skills/idstack" "$_idstack_cache"; do +for _p in "${CLAUDE_PLUGIN_ROOT:-}" "${IDSTACK_HOME:-}" "$_idstack_cache"; do if [ -n "$_p" ] && [ -d "$_p" ]; then _IDSTACK="${_p%/}"; break; fi done # Compute the course slug from project_name and prepare the export folder. @@ -1116,9 +1110,9 @@ The HTML report follows the visual contract in `templates/report.html.tmpl` and ```bash # Resolve the idstack install dir. Re-derived at the top of every bash block — # blocks run in separate shells, so a value derived in an earlier block is not -# available here. Priority: explicit env overrides, Codex-style symlinks, then -# the Claude Code marketplace cache. Empty if none found; guard -# "$_IDSTACK/bin/..." calls accordingly. +# available here. Priority: explicit env overrides, then the Claude Code +# marketplace cache. Empty if none found; guard "$_IDSTACK/bin/..." calls +# accordingly. _IDSTACK="" # Marketplace cache holds one dir per installed version. Sort the basenames by # numeric version fields, not lexically — plain sort ranks 3.9.0.0 above @@ -1129,7 +1123,7 @@ if [ -d "$_idstack_cache_root" ]; then _idstack_v=$(ls "$_idstack_cache_root" 2>/dev/null | sort -t. -k1,1n -k2,2n -k3,3n -k4,4n | tail -1) [ -n "$_idstack_v" ] && _idstack_cache="$_idstack_cache_root/$_idstack_v" fi -for _p in "${CLAUDE_PLUGIN_ROOT:-}" "${IDSTACK_HOME:-}" "$HOME/.agents/plugins/idstack" "$HOME/.agents/skills/idstack" "$_idstack_cache"; do +for _p in "${CLAUDE_PLUGIN_ROOT:-}" "${IDSTACK_HOME:-}" "$_idstack_cache"; do if [ -n "$_p" ] && [ -d "$_p" ]; then _IDSTACK="${_p%/}"; break; fi done # Write a payload for your skill's section, then: @@ -1516,9 +1510,9 @@ After the skill workflow completes successfully, log the session to the timeline ```bash # Resolve the idstack install dir. Re-derived at the top of every bash block — # blocks run in separate shells, so a value derived in an earlier block is not -# available here. Priority: explicit env overrides, Codex-style symlinks, then -# the Claude Code marketplace cache. Empty if none found; guard -# "$_IDSTACK/bin/..." calls accordingly. +# available here. Priority: explicit env overrides, then the Claude Code +# marketplace cache. Empty if none found; guard "$_IDSTACK/bin/..." calls +# accordingly. _IDSTACK="" # Marketplace cache holds one dir per installed version. Sort the basenames by # numeric version fields, not lexically — plain sort ranks 3.9.0.0 above @@ -1529,7 +1523,7 @@ if [ -d "$_idstack_cache_root" ]; then _idstack_v=$(ls "$_idstack_cache_root" 2>/dev/null | sort -t. -k1,1n -k2,2n -k3,3n -k4,4n | tail -1) [ -n "$_idstack_v" ] && _idstack_cache="$_idstack_cache_root/$_idstack_v" fi -for _p in "${CLAUDE_PLUGIN_ROOT:-}" "${IDSTACK_HOME:-}" "$HOME/.agents/plugins/idstack" "$HOME/.agents/skills/idstack" "$_idstack_cache"; do +for _p in "${CLAUDE_PLUGIN_ROOT:-}" "${IDSTACK_HOME:-}" "$_idstack_cache"; do if [ -n "$_p" ] && [ -d "$_p" ]; then _IDSTACK="${_p%/}"; break; fi done "$_IDSTACK/bin/idstack-timeline-log" '{"skill":"course-import","event":"completed"}' @@ -1544,9 +1538,9 @@ import format issue, course structure pattern), also log it as a learning: ```bash # Resolve the idstack install dir. Re-derived at the top of every bash block — # blocks run in separate shells, so a value derived in an earlier block is not -# available here. Priority: explicit env overrides, Codex-style symlinks, then -# the Claude Code marketplace cache. Empty if none found; guard -# "$_IDSTACK/bin/..." calls accordingly. +# available here. Priority: explicit env overrides, then the Claude Code +# marketplace cache. Empty if none found; guard "$_IDSTACK/bin/..." calls +# accordingly. _IDSTACK="" # Marketplace cache holds one dir per installed version. Sort the basenames by # numeric version fields, not lexically — plain sort ranks 3.9.0.0 above @@ -1557,7 +1551,7 @@ if [ -d "$_idstack_cache_root" ]; then _idstack_v=$(ls "$_idstack_cache_root" 2>/dev/null | sort -t. -k1,1n -k2,2n -k3,3n -k4,4n | tail -1) [ -n "$_idstack_v" ] && _idstack_cache="$_idstack_cache_root/$_idstack_v" fi -for _p in "${CLAUDE_PLUGIN_ROOT:-}" "${IDSTACK_HOME:-}" "$HOME/.agents/plugins/idstack" "$HOME/.agents/skills/idstack" "$_idstack_cache"; do +for _p in "${CLAUDE_PLUGIN_ROOT:-}" "${IDSTACK_HOME:-}" "$_idstack_cache"; do if [ -n "$_p" ] && [ -d "$_p" ]; then _IDSTACK="${_p%/}"; break; fi done "$_IDSTACK/bin/idstack-learnings-log" '{"skill":"course-import","type":"operational","key":"SHORT_KEY","insight":"DESCRIPTION","confidence":8,"source":"observed"}' diff --git a/skills/course-quality-review/SKILL.md b/skills/course-quality-review/SKILL.md index f0132dc..2a16342 100644 --- a/skills/course-quality-review/SKILL.md +++ b/skills/course-quality-review/SKILL.md @@ -21,29 +21,23 @@ allowed-tools: ## Preamble: Interaction Conventions -idstack skills are designed to run in multiple CLIs (Claude Code, OpenAI Codex CLI, and -others). To stay portable, skill bodies use a few **concept names** that have a CLI-specific -implementation: +idstack runs in Claude Code. Skill bodies use a few **concept names** for the tools they +lean on: - **AskUserQuestion** — when a skill says "ask via AskUserQuestion" or "using AskUserQuestion", - it means: present a single numbered multiple-choice question (e.g., "Which of these best - describes X? 1) ..., 2) ..., 3) Other") and stop, waiting for the user's next message - before proceeding. Ask **one** question at a time, never batch. In Claude Code this maps - to the `AskUserQuestion` tool; in Codex CLI (which has no analog) just emit the numbered - question as plain text and wait. -- **Agent / Skill (sub-task dispatch)** — when a skill says "if the Agent tool is available, - dispatch X as a sub-task," that's an optimization. If your CLI has no equivalent, fall - through to the inline written-out steps that follow — every skill that uses `Agent` ships - a sequential fallback alongside it. -- **Skill (cross-skill invocation)** — used only by `/idstack:pipeline`. In Claude Code this - invokes a child skill in-process via the `Skill` tool. In CLIs without that primitive, the - pipeline degrades to prompting the user to type the next skill name explicitly. -- **Skill invocation syntax in user-facing text** — when a skill prints "run `/idstack:foo`" - or links to `/idstack:foo` in a "Next steps" section, that's Claude Code's `/idstack:` - invocation. **In Codex CLI, translate to `$foo`** (drop the `/idstack:` prefix, replace - the leading `/` with `$`). Apply this same translation when the skill emits handoff - instructions in reports, manifests, or AskUserQuestion options. Same body text, two - hosts; the model translates per-CLI on output. + it means: present a single multiple-choice question (e.g., "Which of these best describes X?") + and stop, waiting for the user's answer before proceeding. Ask **one** question at a time, + never batch. This maps to the `AskUserQuestion` tool. +- **Agent (sub-task dispatch)** — when a skill says "if the Agent tool is available, dispatch + X as a sub-task," that's an optimization. `Agent` is not in every skill's `allowed-tools`, + so when it isn't available, fall through to the inline written-out steps that follow — + every skill that uses `Agent` ships a sequential fallback alongside it. +- **Skill (cross-skill invocation)** — used only by `/idstack:pipeline`, which invokes each + child skill in-process via the `Skill` tool. +- **Skill invocation syntax in user-facing text** — every skill is invoked as + `/idstack:`. Always write the namespaced form: a bare `/` is not a valid + command. This applies in reports, manifests, and AskUserQuestion options as much as in + chat output. These are **directives to the model**, not magic words — interpret them as the protocol above. @@ -52,9 +46,9 @@ These are **directives to the model**, not magic words — interpret them as the ```bash # Resolve the idstack install dir. Re-derived at the top of every bash block — # blocks run in separate shells, so a value derived in an earlier block is not -# available here. Priority: explicit env overrides, Codex-style symlinks, then -# the Claude Code marketplace cache (highest version). Empty if none found; -# guard "$_IDSTACK/bin/..." calls accordingly. +# available here. Priority: explicit env overrides, then the Claude Code +# marketplace cache (highest version). Empty if none found; guard +# "$_IDSTACK/bin/..." calls accordingly. # Canonical copy: templates/snippets/idstack-resolve.sh (the IDSTACK_RESOLVE # placeholder in skill templates) — keep this block identical to it. _IDSTACK="" @@ -67,7 +61,7 @@ if [ -d "$_idstack_cache_root" ]; then _idstack_v=$(ls "$_idstack_cache_root" 2>/dev/null | sort -t. -k1,1n -k2,2n -k3,3n -k4,4n | tail -1) [ -n "$_idstack_v" ] && _idstack_cache="$_idstack_cache_root/$_idstack_v" fi -for _p in "${CLAUDE_PLUGIN_ROOT:-}" "${IDSTACK_HOME:-}" "$HOME/.agents/plugins/idstack" "$HOME/.agents/skills/idstack" "$_idstack_cache"; do +for _p in "${CLAUDE_PLUGIN_ROOT:-}" "${IDSTACK_HOME:-}" "$_idstack_cache"; do if [ -n "$_p" ] && [ -d "$_p" ]; then _IDSTACK="${_p%/}"; break; fi done _UPD=$("$_IDSTACK/bin/idstack-update-check" 2>/dev/null || true) @@ -92,7 +86,7 @@ if [ -d "$_idstack_cache_root" ]; then _idstack_v=$(ls "$_idstack_cache_root" 2>/dev/null | sort -t. -k1,1n -k2,2n -k3,3n -k4,4n | tail -1) [ -n "$_idstack_v" ] && _idstack_cache="$_idstack_cache_root/$_idstack_v" fi -for _p in "${CLAUDE_PLUGIN_ROOT:-}" "${IDSTACK_HOME:-}" "$HOME/.agents/plugins/idstack" "$HOME/.agents/skills/idstack" "$_idstack_cache"; do +for _p in "${CLAUDE_PLUGIN_ROOT:-}" "${IDSTACK_HOME:-}" "$_idstack_cache"; do if [ -n "$_p" ] && [ -d "$_p" ]; then _IDSTACK="${_p%/}"; break; fi done if [ -f ".idstack/project.json" ]; then @@ -175,7 +169,7 @@ if [ -d "$_idstack_cache_root" ]; then _idstack_v=$(ls "$_idstack_cache_root" 2>/dev/null | sort -t. -k1,1n -k2,2n -k3,3n -k4,4n | tail -1) [ -n "$_idstack_v" ] && _idstack_cache="$_idstack_cache_root/$_idstack_v" fi -for _p in "${CLAUDE_PLUGIN_ROOT:-}" "${IDSTACK_HOME:-}" "$HOME/.agents/plugins/idstack" "$HOME/.agents/skills/idstack" "$_idstack_cache"; do +for _p in "${CLAUDE_PLUGIN_ROOT:-}" "${IDSTACK_HOME:-}" "$_idstack_cache"; do if [ -n "$_p" ] && [ -d "$_p" ]; then _IDSTACK="${_p%/}"; break; fi done _HAS_TIMELINE=0 @@ -914,9 +908,9 @@ section verbatim, bumps the top-level `updated` timestamp, and writes atomically ```bash # Resolve the idstack install dir. Re-derived at the top of every bash block — # blocks run in separate shells, so a value derived in an earlier block is not -# available here. Priority: explicit env overrides, Codex-style symlinks, then -# the Claude Code marketplace cache. Empty if none found; guard -# "$_IDSTACK/bin/..." calls accordingly. +# available here. Priority: explicit env overrides, then the Claude Code +# marketplace cache. Empty if none found; guard "$_IDSTACK/bin/..." calls +# accordingly. _IDSTACK="" # Marketplace cache holds one dir per installed version. Sort the basenames by # numeric version fields, not lexically — plain sort ranks 3.9.0.0 above @@ -927,7 +921,7 @@ if [ -d "$_idstack_cache_root" ]; then _idstack_v=$(ls "$_idstack_cache_root" 2>/dev/null | sort -t. -k1,1n -k2,2n -k3,3n -k4,4n | tail -1) [ -n "$_idstack_v" ] && _idstack_cache="$_idstack_cache_root/$_idstack_v" fi -for _p in "${CLAUDE_PLUGIN_ROOT:-}" "${IDSTACK_HOME:-}" "$HOME/.agents/plugins/idstack" "$HOME/.agents/skills/idstack" "$_idstack_cache"; do +for _p in "${CLAUDE_PLUGIN_ROOT:-}" "${IDSTACK_HOME:-}" "$_idstack_cache"; do if [ -n "$_p" ] && [ -d "$_p" ]; then _IDSTACK="${_p%/}"; break; fi done # payload file holds ONLY the quality_review section object (shape below) @@ -1028,9 +1022,9 @@ After writing the manifest, generate a shareable HTML report. The report follows ```bash # Resolve the idstack install dir. Re-derived at the top of every bash block — # blocks run in separate shells, so a value derived in an earlier block is not -# available here. Priority: explicit env overrides, Codex-style symlinks, then -# the Claude Code marketplace cache. Empty if none found; guard -# "$_IDSTACK/bin/..." calls accordingly. +# available here. Priority: explicit env overrides, then the Claude Code +# marketplace cache. Empty if none found; guard "$_IDSTACK/bin/..." calls +# accordingly. _IDSTACK="" # Marketplace cache holds one dir per installed version. Sort the basenames by # numeric version fields, not lexically — plain sort ranks 3.9.0.0 above @@ -1041,7 +1035,7 @@ if [ -d "$_idstack_cache_root" ]; then _idstack_v=$(ls "$_idstack_cache_root" 2>/dev/null | sort -t. -k1,1n -k2,2n -k3,3n -k4,4n | tail -1) [ -n "$_idstack_v" ] && _idstack_cache="$_idstack_cache_root/$_idstack_v" fi -for _p in "${CLAUDE_PLUGIN_ROOT:-}" "${IDSTACK_HOME:-}" "$HOME/.agents/plugins/idstack" "$HOME/.agents/skills/idstack" "$_idstack_cache"; do +for _p in "${CLAUDE_PLUGIN_ROOT:-}" "${IDSTACK_HOME:-}" "$_idstack_cache"; do if [ -n "$_p" ] && [ -d "$_p" ]; then _IDSTACK="${_p%/}"; break; fi done # Compute the course slug from project_name and prepare the export folder. @@ -1115,9 +1109,9 @@ The HTML report follows the visual contract in `templates/report.html.tmpl` and ```bash # Resolve the idstack install dir. Re-derived at the top of every bash block — # blocks run in separate shells, so a value derived in an earlier block is not -# available here. Priority: explicit env overrides, Codex-style symlinks, then -# the Claude Code marketplace cache. Empty if none found; guard -# "$_IDSTACK/bin/..." calls accordingly. +# available here. Priority: explicit env overrides, then the Claude Code +# marketplace cache. Empty if none found; guard "$_IDSTACK/bin/..." calls +# accordingly. _IDSTACK="" # Marketplace cache holds one dir per installed version. Sort the basenames by # numeric version fields, not lexically — plain sort ranks 3.9.0.0 above @@ -1128,7 +1122,7 @@ if [ -d "$_idstack_cache_root" ]; then _idstack_v=$(ls "$_idstack_cache_root" 2>/dev/null | sort -t. -k1,1n -k2,2n -k3,3n -k4,4n | tail -1) [ -n "$_idstack_v" ] && _idstack_cache="$_idstack_cache_root/$_idstack_v" fi -for _p in "${CLAUDE_PLUGIN_ROOT:-}" "${IDSTACK_HOME:-}" "$HOME/.agents/plugins/idstack" "$HOME/.agents/skills/idstack" "$_idstack_cache"; do +for _p in "${CLAUDE_PLUGIN_ROOT:-}" "${IDSTACK_HOME:-}" "$_idstack_cache"; do if [ -n "$_p" ] && [ -d "$_p" ]; then _IDSTACK="${_p%/}"; break; fi done # Write a payload for your skill's section, then: @@ -1517,9 +1511,9 @@ across sessions (e.g., "Quality score trend: 62 -> 72 -> 78 over 3 reviews"). ```bash # Resolve the idstack install dir. Re-derived at the top of every bash block — # blocks run in separate shells, so a value derived in an earlier block is not -# available here. Priority: explicit env overrides, Codex-style symlinks, then -# the Claude Code marketplace cache. Empty if none found; guard -# "$_IDSTACK/bin/..." calls accordingly. +# available here. Priority: explicit env overrides, then the Claude Code +# marketplace cache. Empty if none found; guard "$_IDSTACK/bin/..." calls +# accordingly. _IDSTACK="" # Marketplace cache holds one dir per installed version. Sort the basenames by # numeric version fields, not lexically — plain sort ranks 3.9.0.0 above @@ -1530,7 +1524,7 @@ if [ -d "$_idstack_cache_root" ]; then _idstack_v=$(ls "$_idstack_cache_root" 2>/dev/null | sort -t. -k1,1n -k2,2n -k3,3n -k4,4n | tail -1) [ -n "$_idstack_v" ] && _idstack_cache="$_idstack_cache_root/$_idstack_v" fi -for _p in "${CLAUDE_PLUGIN_ROOT:-}" "${IDSTACK_HOME:-}" "$HOME/.agents/plugins/idstack" "$HOME/.agents/skills/idstack" "$_idstack_cache"; do +for _p in "${CLAUDE_PLUGIN_ROOT:-}" "${IDSTACK_HOME:-}" "$_idstack_cache"; do if [ -n "$_p" ] && [ -d "$_p" ]; then _IDSTACK="${_p%/}"; break; fi done "$_IDSTACK/bin/idstack-timeline-log" '{"skill":"course-quality-review","event":"completed","score":OVERALL_SCORE,"dimensions":{"teaching_presence":TP,"social_presence":SP,"cognitive_presence":CP}}' @@ -1545,9 +1539,9 @@ import format issue, course structure pattern), also log it as a learning: ```bash # Resolve the idstack install dir. Re-derived at the top of every bash block — # blocks run in separate shells, so a value derived in an earlier block is not -# available here. Priority: explicit env overrides, Codex-style symlinks, then -# the Claude Code marketplace cache. Empty if none found; guard -# "$_IDSTACK/bin/..." calls accordingly. +# available here. Priority: explicit env overrides, then the Claude Code +# marketplace cache. Empty if none found; guard "$_IDSTACK/bin/..." calls +# accordingly. _IDSTACK="" # Marketplace cache holds one dir per installed version. Sort the basenames by # numeric version fields, not lexically — plain sort ranks 3.9.0.0 above @@ -1558,7 +1552,7 @@ if [ -d "$_idstack_cache_root" ]; then _idstack_v=$(ls "$_idstack_cache_root" 2>/dev/null | sort -t. -k1,1n -k2,2n -k3,3n -k4,4n | tail -1) [ -n "$_idstack_v" ] && _idstack_cache="$_idstack_cache_root/$_idstack_v" fi -for _p in "${CLAUDE_PLUGIN_ROOT:-}" "${IDSTACK_HOME:-}" "$HOME/.agents/plugins/idstack" "$HOME/.agents/skills/idstack" "$_idstack_cache"; do +for _p in "${CLAUDE_PLUGIN_ROOT:-}" "${IDSTACK_HOME:-}" "$_idstack_cache"; do if [ -n "$_p" ] && [ -d "$_p" ]; then _IDSTACK="${_p%/}"; break; fi done "$_IDSTACK/bin/idstack-learnings-log" '{"skill":"course-quality-review","type":"operational","key":"SHORT_KEY","insight":"DESCRIPTION","confidence":8,"source":"observed"}' diff --git a/skills/learn/SKILL.md b/skills/learn/SKILL.md index c700277..10c23c6 100644 --- a/skills/learn/SKILL.md +++ b/skills/learn/SKILL.md @@ -16,29 +16,23 @@ allowed-tools: ## Preamble: Interaction Conventions -idstack skills are designed to run in multiple CLIs (Claude Code, OpenAI Codex CLI, and -others). To stay portable, skill bodies use a few **concept names** that have a CLI-specific -implementation: +idstack runs in Claude Code. Skill bodies use a few **concept names** for the tools they +lean on: - **AskUserQuestion** — when a skill says "ask via AskUserQuestion" or "using AskUserQuestion", - it means: present a single numbered multiple-choice question (e.g., "Which of these best - describes X? 1) ..., 2) ..., 3) Other") and stop, waiting for the user's next message - before proceeding. Ask **one** question at a time, never batch. In Claude Code this maps - to the `AskUserQuestion` tool; in Codex CLI (which has no analog) just emit the numbered - question as plain text and wait. -- **Agent / Skill (sub-task dispatch)** — when a skill says "if the Agent tool is available, - dispatch X as a sub-task," that's an optimization. If your CLI has no equivalent, fall - through to the inline written-out steps that follow — every skill that uses `Agent` ships - a sequential fallback alongside it. -- **Skill (cross-skill invocation)** — used only by `/idstack:pipeline`. In Claude Code this - invokes a child skill in-process via the `Skill` tool. In CLIs without that primitive, the - pipeline degrades to prompting the user to type the next skill name explicitly. -- **Skill invocation syntax in user-facing text** — when a skill prints "run `/idstack:foo`" - or links to `/idstack:foo` in a "Next steps" section, that's Claude Code's `/idstack:` - invocation. **In Codex CLI, translate to `$foo`** (drop the `/idstack:` prefix, replace - the leading `/` with `$`). Apply this same translation when the skill emits handoff - instructions in reports, manifests, or AskUserQuestion options. Same body text, two - hosts; the model translates per-CLI on output. + it means: present a single multiple-choice question (e.g., "Which of these best describes X?") + and stop, waiting for the user's answer before proceeding. Ask **one** question at a time, + never batch. This maps to the `AskUserQuestion` tool. +- **Agent (sub-task dispatch)** — when a skill says "if the Agent tool is available, dispatch + X as a sub-task," that's an optimization. `Agent` is not in every skill's `allowed-tools`, + so when it isn't available, fall through to the inline written-out steps that follow — + every skill that uses `Agent` ships a sequential fallback alongside it. +- **Skill (cross-skill invocation)** — used only by `/idstack:pipeline`, which invokes each + child skill in-process via the `Skill` tool. +- **Skill invocation syntax in user-facing text** — every skill is invoked as + `/idstack:`. Always write the namespaced form: a bare `/` is not a valid + command. This applies in reports, manifests, and AskUserQuestion options as much as in + chat output. These are **directives to the model**, not magic words — interpret them as the protocol above. @@ -47,9 +41,9 @@ These are **directives to the model**, not magic words — interpret them as the ```bash # Resolve the idstack install dir. Re-derived at the top of every bash block — # blocks run in separate shells, so a value derived in an earlier block is not -# available here. Priority: explicit env overrides, Codex-style symlinks, then -# the Claude Code marketplace cache (highest version). Empty if none found; -# guard "$_IDSTACK/bin/..." calls accordingly. +# available here. Priority: explicit env overrides, then the Claude Code +# marketplace cache (highest version). Empty if none found; guard +# "$_IDSTACK/bin/..." calls accordingly. # Canonical copy: templates/snippets/idstack-resolve.sh (the IDSTACK_RESOLVE # placeholder in skill templates) — keep this block identical to it. _IDSTACK="" @@ -62,7 +56,7 @@ if [ -d "$_idstack_cache_root" ]; then _idstack_v=$(ls "$_idstack_cache_root" 2>/dev/null | sort -t. -k1,1n -k2,2n -k3,3n -k4,4n | tail -1) [ -n "$_idstack_v" ] && _idstack_cache="$_idstack_cache_root/$_idstack_v" fi -for _p in "${CLAUDE_PLUGIN_ROOT:-}" "${IDSTACK_HOME:-}" "$HOME/.agents/plugins/idstack" "$HOME/.agents/skills/idstack" "$_idstack_cache"; do +for _p in "${CLAUDE_PLUGIN_ROOT:-}" "${IDSTACK_HOME:-}" "$_idstack_cache"; do if [ -n "$_p" ] && [ -d "$_p" ]; then _IDSTACK="${_p%/}"; break; fi done _UPD=$("$_IDSTACK/bin/idstack-update-check" 2>/dev/null || true) @@ -87,7 +81,7 @@ if [ -d "$_idstack_cache_root" ]; then _idstack_v=$(ls "$_idstack_cache_root" 2>/dev/null | sort -t. -k1,1n -k2,2n -k3,3n -k4,4n | tail -1) [ -n "$_idstack_v" ] && _idstack_cache="$_idstack_cache_root/$_idstack_v" fi -for _p in "${CLAUDE_PLUGIN_ROOT:-}" "${IDSTACK_HOME:-}" "$HOME/.agents/plugins/idstack" "$HOME/.agents/skills/idstack" "$_idstack_cache"; do +for _p in "${CLAUDE_PLUGIN_ROOT:-}" "${IDSTACK_HOME:-}" "$_idstack_cache"; do if [ -n "$_p" ] && [ -d "$_p" ]; then _IDSTACK="${_p%/}"; break; fi done if [ -f ".idstack/project.json" ]; then @@ -170,7 +164,7 @@ if [ -d "$_idstack_cache_root" ]; then _idstack_v=$(ls "$_idstack_cache_root" 2>/dev/null | sort -t. -k1,1n -k2,2n -k3,3n -k4,4n | tail -1) [ -n "$_idstack_v" ] && _idstack_cache="$_idstack_cache_root/$_idstack_v" fi -for _p in "${CLAUDE_PLUGIN_ROOT:-}" "${IDSTACK_HOME:-}" "$HOME/.agents/plugins/idstack" "$HOME/.agents/skills/idstack" "$_idstack_cache"; do +for _p in "${CLAUDE_PLUGIN_ROOT:-}" "${IDSTACK_HOME:-}" "$_idstack_cache"; do if [ -n "$_p" ] && [ -d "$_p" ]; then _IDSTACK="${_p%/}"; break; fi done _HAS_TIMELINE=0 @@ -278,15 +272,15 @@ Parse the user's intent and map to one of these commands: ### list (default) -Show the most recent learnings. If the user just said `/idstack:learn` (Codex: `$learn`) with no arguments, +Show the most recent learnings. If the user just said `/idstack:learn` with no arguments, this is the default. ```bash # Resolve the idstack install dir. Re-derived at the top of every bash block — # blocks run in separate shells, so a value derived in an earlier block is not -# available here. Priority: explicit env overrides, Codex-style symlinks, then -# the Claude Code marketplace cache. Empty if none found; guard -# "$_IDSTACK/bin/..." calls accordingly. +# available here. Priority: explicit env overrides, then the Claude Code +# marketplace cache. Empty if none found; guard "$_IDSTACK/bin/..." calls +# accordingly. _IDSTACK="" # Marketplace cache holds one dir per installed version. Sort the basenames by # numeric version fields, not lexically — plain sort ranks 3.9.0.0 above @@ -297,7 +291,7 @@ if [ -d "$_idstack_cache_root" ]; then _idstack_v=$(ls "$_idstack_cache_root" 2>/dev/null | sort -t. -k1,1n -k2,2n -k3,3n -k4,4n | tail -1) [ -n "$_idstack_v" ] && _idstack_cache="$_idstack_cache_root/$_idstack_v" fi -for _p in "${CLAUDE_PLUGIN_ROOT:-}" "${IDSTACK_HOME:-}" "$HOME/.agents/plugins/idstack" "$HOME/.agents/skills/idstack" "$_idstack_cache"; do +for _p in "${CLAUDE_PLUGIN_ROOT:-}" "${IDSTACK_HOME:-}" "$_idstack_cache"; do if [ -n "$_p" ] && [ -d "$_p" ]; then _IDSTACK="${_p%/}"; break; fi done "$_IDSTACK/bin/idstack-learnings-search" --limit 10 @@ -319,9 +313,9 @@ Search learnings by keyword. Supports `--cross-project` to include global learni ```bash # Resolve the idstack install dir. Re-derived at the top of every bash block — # blocks run in separate shells, so a value derived in an earlier block is not -# available here. Priority: explicit env overrides, Codex-style symlinks, then -# the Claude Code marketplace cache. Empty if none found; guard -# "$_IDSTACK/bin/..." calls accordingly. +# available here. Priority: explicit env overrides, then the Claude Code +# marketplace cache. Empty if none found; guard "$_IDSTACK/bin/..." calls +# accordingly. _IDSTACK="" # Marketplace cache holds one dir per installed version. Sort the basenames by # numeric version fields, not lexically — plain sort ranks 3.9.0.0 above @@ -332,7 +326,7 @@ if [ -d "$_idstack_cache_root" ]; then _idstack_v=$(ls "$_idstack_cache_root" 2>/dev/null | sort -t. -k1,1n -k2,2n -k3,3n -k4,4n | tail -1) [ -n "$_idstack_v" ] && _idstack_cache="$_idstack_cache_root/$_idstack_v" fi -for _p in "${CLAUDE_PLUGIN_ROOT:-}" "${IDSTACK_HOME:-}" "$HOME/.agents/plugins/idstack" "$HOME/.agents/skills/idstack" "$_idstack_cache"; do +for _p in "${CLAUDE_PLUGIN_ROOT:-}" "${IDSTACK_HOME:-}" "$_idstack_cache"; do if [ -n "$_p" ] && [ -d "$_p" ]; then _IDSTACK="${_p%/}"; break; fi done "$_IDSTACK/bin/idstack-learnings-search" --keyword KEYWORD --limit 10 @@ -342,9 +336,9 @@ For cross-project search: ```bash # Resolve the idstack install dir. Re-derived at the top of every bash block — # blocks run in separate shells, so a value derived in an earlier block is not -# available here. Priority: explicit env overrides, Codex-style symlinks, then -# the Claude Code marketplace cache. Empty if none found; guard -# "$_IDSTACK/bin/..." calls accordingly. +# available here. Priority: explicit env overrides, then the Claude Code +# marketplace cache. Empty if none found; guard "$_IDSTACK/bin/..." calls +# accordingly. _IDSTACK="" # Marketplace cache holds one dir per installed version. Sort the basenames by # numeric version fields, not lexically — plain sort ranks 3.9.0.0 above @@ -355,7 +349,7 @@ if [ -d "$_idstack_cache_root" ]; then _idstack_v=$(ls "$_idstack_cache_root" 2>/dev/null | sort -t. -k1,1n -k2,2n -k3,3n -k4,4n | tail -1) [ -n "$_idstack_v" ] && _idstack_cache="$_idstack_cache_root/$_idstack_v" fi -for _p in "${CLAUDE_PLUGIN_ROOT:-}" "${IDSTACK_HOME:-}" "$HOME/.agents/plugins/idstack" "$HOME/.agents/skills/idstack" "$_idstack_cache"; do +for _p in "${CLAUDE_PLUGIN_ROOT:-}" "${IDSTACK_HOME:-}" "$_idstack_cache"; do if [ -n "$_p" ] && [ -d "$_p" ]; then _IDSTACK="${_p%/}"; break; fi done "$_IDSTACK/bin/idstack-learnings-search" --keyword KEYWORD --cross-project --limit 10 @@ -370,9 +364,9 @@ Delete a learning by its key. Always confirm with the user before deleting. ```bash # Resolve the idstack install dir. Re-derived at the top of every bash block — # blocks run in separate shells, so a value derived in an earlier block is not -# available here. Priority: explicit env overrides, Codex-style symlinks, then -# the Claude Code marketplace cache. Empty if none found; guard -# "$_IDSTACK/bin/..." calls accordingly. +# available here. Priority: explicit env overrides, then the Claude Code +# marketplace cache. Empty if none found; guard "$_IDSTACK/bin/..." calls +# accordingly. _IDSTACK="" # Marketplace cache holds one dir per installed version. Sort the basenames by # numeric version fields, not lexically — plain sort ranks 3.9.0.0 above @@ -383,7 +377,7 @@ if [ -d "$_idstack_cache_root" ]; then _idstack_v=$(ls "$_idstack_cache_root" 2>/dev/null | sort -t. -k1,1n -k2,2n -k3,3n -k4,4n | tail -1) [ -n "$_idstack_v" ] && _idstack_cache="$_idstack_cache_root/$_idstack_v" fi -for _p in "${CLAUDE_PLUGIN_ROOT:-}" "${IDSTACK_HOME:-}" "$HOME/.agents/plugins/idstack" "$HOME/.agents/skills/idstack" "$_idstack_cache"; do +for _p in "${CLAUDE_PLUGIN_ROOT:-}" "${IDSTACK_HOME:-}" "$_idstack_cache"; do if [ -n "$_p" ] && [ -d "$_p" ]; then _IDSTACK="${_p%/}"; break; fi done "$_IDSTACK/bin/idstack-learnings-delete" KEY @@ -396,9 +390,9 @@ Copy a local learning to the global store so it's available across projects. ```bash # Resolve the idstack install dir. Re-derived at the top of every bash block — # blocks run in separate shells, so a value derived in an earlier block is not -# available here. Priority: explicit env overrides, Codex-style symlinks, then -# the Claude Code marketplace cache. Empty if none found; guard -# "$_IDSTACK/bin/..." calls accordingly. +# available here. Priority: explicit env overrides, then the Claude Code +# marketplace cache. Empty if none found; guard "$_IDSTACK/bin/..." calls +# accordingly. _IDSTACK="" # Marketplace cache holds one dir per installed version. Sort the basenames by # numeric version fields, not lexically — plain sort ranks 3.9.0.0 above @@ -409,7 +403,7 @@ if [ -d "$_idstack_cache_root" ]; then _idstack_v=$(ls "$_idstack_cache_root" 2>/dev/null | sort -t. -k1,1n -k2,2n -k3,3n -k4,4n | tail -1) [ -n "$_idstack_v" ] && _idstack_cache="$_idstack_cache_root/$_idstack_v" fi -for _p in "${CLAUDE_PLUGIN_ROOT:-}" "${IDSTACK_HOME:-}" "$HOME/.agents/plugins/idstack" "$HOME/.agents/skills/idstack" "$_idstack_cache"; do +for _p in "${CLAUDE_PLUGIN_ROOT:-}" "${IDSTACK_HOME:-}" "$_idstack_cache"; do if [ -n "$_p" ] && [ -d "$_p" ]; then _IDSTACK="${_p%/}"; break; fi done "$_IDSTACK/bin/idstack-learnings-promote" KEY @@ -422,9 +416,9 @@ Export all learnings to a markdown file. ```bash # Resolve the idstack install dir. Re-derived at the top of every bash block — # blocks run in separate shells, so a value derived in an earlier block is not -# available here. Priority: explicit env overrides, Codex-style symlinks, then -# the Claude Code marketplace cache. Empty if none found; guard -# "$_IDSTACK/bin/..." calls accordingly. +# available here. Priority: explicit env overrides, then the Claude Code +# marketplace cache. Empty if none found; guard "$_IDSTACK/bin/..." calls +# accordingly. _IDSTACK="" # Marketplace cache holds one dir per installed version. Sort the basenames by # numeric version fields, not lexically — plain sort ranks 3.9.0.0 above @@ -435,7 +429,7 @@ if [ -d "$_idstack_cache_root" ]; then _idstack_v=$(ls "$_idstack_cache_root" 2>/dev/null | sort -t. -k1,1n -k2,2n -k3,3n -k4,4n | tail -1) [ -n "$_idstack_v" ] && _idstack_cache="$_idstack_cache_root/$_idstack_v" fi -for _p in "${CLAUDE_PLUGIN_ROOT:-}" "${IDSTACK_HOME:-}" "$HOME/.agents/plugins/idstack" "$HOME/.agents/skills/idstack" "$_idstack_cache"; do +for _p in "${CLAUDE_PLUGIN_ROOT:-}" "${IDSTACK_HOME:-}" "$_idstack_cache"; do if [ -n "$_p" ] && [ -d "$_p" ]; then _IDSTACK="${_p%/}"; break; fi done "$_IDSTACK/bin/idstack-learnings-search" --limit 1000 diff --git a/skills/learn/SKILL.md.tmpl b/skills/learn/SKILL.md.tmpl index 682db43..0bdccab 100644 --- a/skills/learn/SKILL.md.tmpl +++ b/skills/learn/SKILL.md.tmpl @@ -27,7 +27,7 @@ Parse the user's intent and map to one of these commands: ### list (default) -Show the most recent learnings. If the user just said `/idstack:learn` (Codex: `$learn`) with no arguments, +Show the most recent learnings. If the user just said `/idstack:learn` with no arguments, this is the default. ```bash diff --git a/skills/learning-objectives/SKILL.md b/skills/learning-objectives/SKILL.md index 9ae7f1f..b58c1cc 100644 --- a/skills/learning-objectives/SKILL.md +++ b/skills/learning-objectives/SKILL.md @@ -20,29 +20,23 @@ allowed-tools: ## Preamble: Interaction Conventions -idstack skills are designed to run in multiple CLIs (Claude Code, OpenAI Codex CLI, and -others). To stay portable, skill bodies use a few **concept names** that have a CLI-specific -implementation: +idstack runs in Claude Code. Skill bodies use a few **concept names** for the tools they +lean on: - **AskUserQuestion** — when a skill says "ask via AskUserQuestion" or "using AskUserQuestion", - it means: present a single numbered multiple-choice question (e.g., "Which of these best - describes X? 1) ..., 2) ..., 3) Other") and stop, waiting for the user's next message - before proceeding. Ask **one** question at a time, never batch. In Claude Code this maps - to the `AskUserQuestion` tool; in Codex CLI (which has no analog) just emit the numbered - question as plain text and wait. -- **Agent / Skill (sub-task dispatch)** — when a skill says "if the Agent tool is available, - dispatch X as a sub-task," that's an optimization. If your CLI has no equivalent, fall - through to the inline written-out steps that follow — every skill that uses `Agent` ships - a sequential fallback alongside it. -- **Skill (cross-skill invocation)** — used only by `/idstack:pipeline`. In Claude Code this - invokes a child skill in-process via the `Skill` tool. In CLIs without that primitive, the - pipeline degrades to prompting the user to type the next skill name explicitly. -- **Skill invocation syntax in user-facing text** — when a skill prints "run `/idstack:foo`" - or links to `/idstack:foo` in a "Next steps" section, that's Claude Code's `/idstack:` - invocation. **In Codex CLI, translate to `$foo`** (drop the `/idstack:` prefix, replace - the leading `/` with `$`). Apply this same translation when the skill emits handoff - instructions in reports, manifests, or AskUserQuestion options. Same body text, two - hosts; the model translates per-CLI on output. + it means: present a single multiple-choice question (e.g., "Which of these best describes X?") + and stop, waiting for the user's answer before proceeding. Ask **one** question at a time, + never batch. This maps to the `AskUserQuestion` tool. +- **Agent (sub-task dispatch)** — when a skill says "if the Agent tool is available, dispatch + X as a sub-task," that's an optimization. `Agent` is not in every skill's `allowed-tools`, + so when it isn't available, fall through to the inline written-out steps that follow — + every skill that uses `Agent` ships a sequential fallback alongside it. +- **Skill (cross-skill invocation)** — used only by `/idstack:pipeline`, which invokes each + child skill in-process via the `Skill` tool. +- **Skill invocation syntax in user-facing text** — every skill is invoked as + `/idstack:`. Always write the namespaced form: a bare `/` is not a valid + command. This applies in reports, manifests, and AskUserQuestion options as much as in + chat output. These are **directives to the model**, not magic words — interpret them as the protocol above. @@ -51,9 +45,9 @@ These are **directives to the model**, not magic words — interpret them as the ```bash # Resolve the idstack install dir. Re-derived at the top of every bash block — # blocks run in separate shells, so a value derived in an earlier block is not -# available here. Priority: explicit env overrides, Codex-style symlinks, then -# the Claude Code marketplace cache (highest version). Empty if none found; -# guard "$_IDSTACK/bin/..." calls accordingly. +# available here. Priority: explicit env overrides, then the Claude Code +# marketplace cache (highest version). Empty if none found; guard +# "$_IDSTACK/bin/..." calls accordingly. # Canonical copy: templates/snippets/idstack-resolve.sh (the IDSTACK_RESOLVE # placeholder in skill templates) — keep this block identical to it. _IDSTACK="" @@ -66,7 +60,7 @@ if [ -d "$_idstack_cache_root" ]; then _idstack_v=$(ls "$_idstack_cache_root" 2>/dev/null | sort -t. -k1,1n -k2,2n -k3,3n -k4,4n | tail -1) [ -n "$_idstack_v" ] && _idstack_cache="$_idstack_cache_root/$_idstack_v" fi -for _p in "${CLAUDE_PLUGIN_ROOT:-}" "${IDSTACK_HOME:-}" "$HOME/.agents/plugins/idstack" "$HOME/.agents/skills/idstack" "$_idstack_cache"; do +for _p in "${CLAUDE_PLUGIN_ROOT:-}" "${IDSTACK_HOME:-}" "$_idstack_cache"; do if [ -n "$_p" ] && [ -d "$_p" ]; then _IDSTACK="${_p%/}"; break; fi done _UPD=$("$_IDSTACK/bin/idstack-update-check" 2>/dev/null || true) @@ -91,7 +85,7 @@ if [ -d "$_idstack_cache_root" ]; then _idstack_v=$(ls "$_idstack_cache_root" 2>/dev/null | sort -t. -k1,1n -k2,2n -k3,3n -k4,4n | tail -1) [ -n "$_idstack_v" ] && _idstack_cache="$_idstack_cache_root/$_idstack_v" fi -for _p in "${CLAUDE_PLUGIN_ROOT:-}" "${IDSTACK_HOME:-}" "$HOME/.agents/plugins/idstack" "$HOME/.agents/skills/idstack" "$_idstack_cache"; do +for _p in "${CLAUDE_PLUGIN_ROOT:-}" "${IDSTACK_HOME:-}" "$_idstack_cache"; do if [ -n "$_p" ] && [ -d "$_p" ]; then _IDSTACK="${_p%/}"; break; fi done if [ -f ".idstack/project.json" ]; then @@ -174,7 +168,7 @@ if [ -d "$_idstack_cache_root" ]; then _idstack_v=$(ls "$_idstack_cache_root" 2>/dev/null | sort -t. -k1,1n -k2,2n -k3,3n -k4,4n | tail -1) [ -n "$_idstack_v" ] && _idstack_cache="$_idstack_cache_root/$_idstack_v" fi -for _p in "${CLAUDE_PLUGIN_ROOT:-}" "${IDSTACK_HOME:-}" "$HOME/.agents/plugins/idstack" "$HOME/.agents/skills/idstack" "$_idstack_cache"; do +for _p in "${CLAUDE_PLUGIN_ROOT:-}" "${IDSTACK_HOME:-}" "$_idstack_cache"; do if [ -n "$_p" ] && [ -d "$_p" ]; then _IDSTACK="${_p%/}"; break; fi done _HAS_TIMELINE=0 @@ -570,9 +564,9 @@ Before writing the manifest, generate an HTML report so the designer has a singl ```bash # Resolve the idstack install dir. Re-derived at the top of every bash block — # blocks run in separate shells, so a value derived in an earlier block is not -# available here. Priority: explicit env overrides, Codex-style symlinks, then -# the Claude Code marketplace cache. Empty if none found; guard -# "$_IDSTACK/bin/..." calls accordingly. +# available here. Priority: explicit env overrides, then the Claude Code +# marketplace cache. Empty if none found; guard "$_IDSTACK/bin/..." calls +# accordingly. _IDSTACK="" # Marketplace cache holds one dir per installed version. Sort the basenames by # numeric version fields, not lexically — plain sort ranks 3.9.0.0 above @@ -583,7 +577,7 @@ if [ -d "$_idstack_cache_root" ]; then _idstack_v=$(ls "$_idstack_cache_root" 2>/dev/null | sort -t. -k1,1n -k2,2n -k3,3n -k4,4n | tail -1) [ -n "$_idstack_v" ] && _idstack_cache="$_idstack_cache_root/$_idstack_v" fi -for _p in "${CLAUDE_PLUGIN_ROOT:-}" "${IDSTACK_HOME:-}" "$HOME/.agents/plugins/idstack" "$HOME/.agents/skills/idstack" "$_idstack_cache"; do +for _p in "${CLAUDE_PLUGIN_ROOT:-}" "${IDSTACK_HOME:-}" "$_idstack_cache"; do if [ -n "$_p" ] && [ -d "$_p" ]; then _IDSTACK="${_p%/}"; break; fi done # Compute the course slug from project_name and prepare the export folder. @@ -623,9 +617,9 @@ updates the top-level `updated` timestamp for you: ```bash # Resolve the idstack install dir. Re-derived at the top of every bash block — # blocks run in separate shells, so a value derived in an earlier block is not -# available here. Priority: explicit env overrides, Codex-style symlinks, then -# the Claude Code marketplace cache. Empty if none found; guard -# "$_IDSTACK/bin/..." calls accordingly. +# available here. Priority: explicit env overrides, then the Claude Code +# marketplace cache. Empty if none found; guard "$_IDSTACK/bin/..." calls +# accordingly. _IDSTACK="" # Marketplace cache holds one dir per installed version. Sort the basenames by # numeric version fields, not lexically — plain sort ranks 3.9.0.0 above @@ -636,7 +630,7 @@ if [ -d "$_idstack_cache_root" ]; then _idstack_v=$(ls "$_idstack_cache_root" 2>/dev/null | sort -t. -k1,1n -k2,2n -k3,3n -k4,4n | tail -1) [ -n "$_idstack_v" ] && _idstack_cache="$_idstack_cache_root/$_idstack_v" fi -for _p in "${CLAUDE_PLUGIN_ROOT:-}" "${IDSTACK_HOME:-}" "$HOME/.agents/plugins/idstack" "$HOME/.agents/skills/idstack" "$_idstack_cache"; do +for _p in "${CLAUDE_PLUGIN_ROOT:-}" "${IDSTACK_HOME:-}" "$_idstack_cache"; do if [ -n "$_p" ] && [ -d "$_p" ]; then _IDSTACK="${_p%/}"; break; fi done "$_IDSTACK/bin/idstack-manifest-merge" --section learning_objectives --payload - <<'PAYLOAD' @@ -724,9 +718,9 @@ The HTML report follows the visual contract in `templates/report.html.tmpl` and ```bash # Resolve the idstack install dir. Re-derived at the top of every bash block — # blocks run in separate shells, so a value derived in an earlier block is not -# available here. Priority: explicit env overrides, Codex-style symlinks, then -# the Claude Code marketplace cache. Empty if none found; guard -# "$_IDSTACK/bin/..." calls accordingly. +# available here. Priority: explicit env overrides, then the Claude Code +# marketplace cache. Empty if none found; guard "$_IDSTACK/bin/..." calls +# accordingly. _IDSTACK="" # Marketplace cache holds one dir per installed version. Sort the basenames by # numeric version fields, not lexically — plain sort ranks 3.9.0.0 above @@ -737,7 +731,7 @@ if [ -d "$_idstack_cache_root" ]; then _idstack_v=$(ls "$_idstack_cache_root" 2>/dev/null | sort -t. -k1,1n -k2,2n -k3,3n -k4,4n | tail -1) [ -n "$_idstack_v" ] && _idstack_cache="$_idstack_cache_root/$_idstack_v" fi -for _p in "${CLAUDE_PLUGIN_ROOT:-}" "${IDSTACK_HOME:-}" "$HOME/.agents/plugins/idstack" "$HOME/.agents/skills/idstack" "$_idstack_cache"; do +for _p in "${CLAUDE_PLUGIN_ROOT:-}" "${IDSTACK_HOME:-}" "$_idstack_cache"; do if [ -n "$_p" ] && [ -d "$_p" ]; then _IDSTACK="${_p%/}"; break; fi done # Write a payload for your skill's section, then: @@ -1124,9 +1118,9 @@ After the skill workflow completes successfully, log the session to the timeline ```bash # Resolve the idstack install dir. Re-derived at the top of every bash block — # blocks run in separate shells, so a value derived in an earlier block is not -# available here. Priority: explicit env overrides, Codex-style symlinks, then -# the Claude Code marketplace cache. Empty if none found; guard -# "$_IDSTACK/bin/..." calls accordingly. +# available here. Priority: explicit env overrides, then the Claude Code +# marketplace cache. Empty if none found; guard "$_IDSTACK/bin/..." calls +# accordingly. _IDSTACK="" # Marketplace cache holds one dir per installed version. Sort the basenames by # numeric version fields, not lexically — plain sort ranks 3.9.0.0 above @@ -1137,7 +1131,7 @@ if [ -d "$_idstack_cache_root" ]; then _idstack_v=$(ls "$_idstack_cache_root" 2>/dev/null | sort -t. -k1,1n -k2,2n -k3,3n -k4,4n | tail -1) [ -n "$_idstack_v" ] && _idstack_cache="$_idstack_cache_root/$_idstack_v" fi -for _p in "${CLAUDE_PLUGIN_ROOT:-}" "${IDSTACK_HOME:-}" "$HOME/.agents/plugins/idstack" "$HOME/.agents/skills/idstack" "$_idstack_cache"; do +for _p in "${CLAUDE_PLUGIN_ROOT:-}" "${IDSTACK_HOME:-}" "$_idstack_cache"; do if [ -n "$_p" ] && [ -d "$_p" ]; then _IDSTACK="${_p%/}"; break; fi done "$_IDSTACK/bin/idstack-timeline-log" '{"skill":"learning-objectives","event":"completed"}' @@ -1152,9 +1146,9 @@ import format issue, course structure pattern), also log it as a learning: ```bash # Resolve the idstack install dir. Re-derived at the top of every bash block — # blocks run in separate shells, so a value derived in an earlier block is not -# available here. Priority: explicit env overrides, Codex-style symlinks, then -# the Claude Code marketplace cache. Empty if none found; guard -# "$_IDSTACK/bin/..." calls accordingly. +# available here. Priority: explicit env overrides, then the Claude Code +# marketplace cache. Empty if none found; guard "$_IDSTACK/bin/..." calls +# accordingly. _IDSTACK="" # Marketplace cache holds one dir per installed version. Sort the basenames by # numeric version fields, not lexically — plain sort ranks 3.9.0.0 above @@ -1165,7 +1159,7 @@ if [ -d "$_idstack_cache_root" ]; then _idstack_v=$(ls "$_idstack_cache_root" 2>/dev/null | sort -t. -k1,1n -k2,2n -k3,3n -k4,4n | tail -1) [ -n "$_idstack_v" ] && _idstack_cache="$_idstack_cache_root/$_idstack_v" fi -for _p in "${CLAUDE_PLUGIN_ROOT:-}" "${IDSTACK_HOME:-}" "$HOME/.agents/plugins/idstack" "$HOME/.agents/skills/idstack" "$_idstack_cache"; do +for _p in "${CLAUDE_PLUGIN_ROOT:-}" "${IDSTACK_HOME:-}" "$_idstack_cache"; do if [ -n "$_p" ] && [ -d "$_p" ]; then _IDSTACK="${_p%/}"; break; fi done "$_IDSTACK/bin/idstack-learnings-log" '{"skill":"learning-objectives","type":"operational","key":"SHORT_KEY","insight":"DESCRIPTION","confidence":8,"source":"observed"}' diff --git a/skills/needs-analysis/SKILL.md b/skills/needs-analysis/SKILL.md index 1624405..e87dda4 100644 --- a/skills/needs-analysis/SKILL.md +++ b/skills/needs-analysis/SKILL.md @@ -20,29 +20,23 @@ allowed-tools: ## Preamble: Interaction Conventions -idstack skills are designed to run in multiple CLIs (Claude Code, OpenAI Codex CLI, and -others). To stay portable, skill bodies use a few **concept names** that have a CLI-specific -implementation: +idstack runs in Claude Code. Skill bodies use a few **concept names** for the tools they +lean on: - **AskUserQuestion** — when a skill says "ask via AskUserQuestion" or "using AskUserQuestion", - it means: present a single numbered multiple-choice question (e.g., "Which of these best - describes X? 1) ..., 2) ..., 3) Other") and stop, waiting for the user's next message - before proceeding. Ask **one** question at a time, never batch. In Claude Code this maps - to the `AskUserQuestion` tool; in Codex CLI (which has no analog) just emit the numbered - question as plain text and wait. -- **Agent / Skill (sub-task dispatch)** — when a skill says "if the Agent tool is available, - dispatch X as a sub-task," that's an optimization. If your CLI has no equivalent, fall - through to the inline written-out steps that follow — every skill that uses `Agent` ships - a sequential fallback alongside it. -- **Skill (cross-skill invocation)** — used only by `/idstack:pipeline`. In Claude Code this - invokes a child skill in-process via the `Skill` tool. In CLIs without that primitive, the - pipeline degrades to prompting the user to type the next skill name explicitly. -- **Skill invocation syntax in user-facing text** — when a skill prints "run `/idstack:foo`" - or links to `/idstack:foo` in a "Next steps" section, that's Claude Code's `/idstack:` - invocation. **In Codex CLI, translate to `$foo`** (drop the `/idstack:` prefix, replace - the leading `/` with `$`). Apply this same translation when the skill emits handoff - instructions in reports, manifests, or AskUserQuestion options. Same body text, two - hosts; the model translates per-CLI on output. + it means: present a single multiple-choice question (e.g., "Which of these best describes X?") + and stop, waiting for the user's answer before proceeding. Ask **one** question at a time, + never batch. This maps to the `AskUserQuestion` tool. +- **Agent (sub-task dispatch)** — when a skill says "if the Agent tool is available, dispatch + X as a sub-task," that's an optimization. `Agent` is not in every skill's `allowed-tools`, + so when it isn't available, fall through to the inline written-out steps that follow — + every skill that uses `Agent` ships a sequential fallback alongside it. +- **Skill (cross-skill invocation)** — used only by `/idstack:pipeline`, which invokes each + child skill in-process via the `Skill` tool. +- **Skill invocation syntax in user-facing text** — every skill is invoked as + `/idstack:`. Always write the namespaced form: a bare `/` is not a valid + command. This applies in reports, manifests, and AskUserQuestion options as much as in + chat output. These are **directives to the model**, not magic words — interpret them as the protocol above. @@ -51,9 +45,9 @@ These are **directives to the model**, not magic words — interpret them as the ```bash # Resolve the idstack install dir. Re-derived at the top of every bash block — # blocks run in separate shells, so a value derived in an earlier block is not -# available here. Priority: explicit env overrides, Codex-style symlinks, then -# the Claude Code marketplace cache (highest version). Empty if none found; -# guard "$_IDSTACK/bin/..." calls accordingly. +# available here. Priority: explicit env overrides, then the Claude Code +# marketplace cache (highest version). Empty if none found; guard +# "$_IDSTACK/bin/..." calls accordingly. # Canonical copy: templates/snippets/idstack-resolve.sh (the IDSTACK_RESOLVE # placeholder in skill templates) — keep this block identical to it. _IDSTACK="" @@ -66,7 +60,7 @@ if [ -d "$_idstack_cache_root" ]; then _idstack_v=$(ls "$_idstack_cache_root" 2>/dev/null | sort -t. -k1,1n -k2,2n -k3,3n -k4,4n | tail -1) [ -n "$_idstack_v" ] && _idstack_cache="$_idstack_cache_root/$_idstack_v" fi -for _p in "${CLAUDE_PLUGIN_ROOT:-}" "${IDSTACK_HOME:-}" "$HOME/.agents/plugins/idstack" "$HOME/.agents/skills/idstack" "$_idstack_cache"; do +for _p in "${CLAUDE_PLUGIN_ROOT:-}" "${IDSTACK_HOME:-}" "$_idstack_cache"; do if [ -n "$_p" ] && [ -d "$_p" ]; then _IDSTACK="${_p%/}"; break; fi done _UPD=$("$_IDSTACK/bin/idstack-update-check" 2>/dev/null || true) @@ -91,7 +85,7 @@ if [ -d "$_idstack_cache_root" ]; then _idstack_v=$(ls "$_idstack_cache_root" 2>/dev/null | sort -t. -k1,1n -k2,2n -k3,3n -k4,4n | tail -1) [ -n "$_idstack_v" ] && _idstack_cache="$_idstack_cache_root/$_idstack_v" fi -for _p in "${CLAUDE_PLUGIN_ROOT:-}" "${IDSTACK_HOME:-}" "$HOME/.agents/plugins/idstack" "$HOME/.agents/skills/idstack" "$_idstack_cache"; do +for _p in "${CLAUDE_PLUGIN_ROOT:-}" "${IDSTACK_HOME:-}" "$_idstack_cache"; do if [ -n "$_p" ] && [ -d "$_p" ]; then _IDSTACK="${_p%/}"; break; fi done if [ -f ".idstack/project.json" ]; then @@ -174,7 +168,7 @@ if [ -d "$_idstack_cache_root" ]; then _idstack_v=$(ls "$_idstack_cache_root" 2>/dev/null | sort -t. -k1,1n -k2,2n -k3,3n -k4,4n | tail -1) [ -n "$_idstack_v" ] && _idstack_cache="$_idstack_cache_root/$_idstack_v" fi -for _p in "${CLAUDE_PLUGIN_ROOT:-}" "${IDSTACK_HOME:-}" "$HOME/.agents/plugins/idstack" "$HOME/.agents/skills/idstack" "$_idstack_cache"; do +for _p in "${CLAUDE_PLUGIN_ROOT:-}" "${IDSTACK_HOME:-}" "$_idstack_cache"; do if [ -n "$_p" ] && [ -d "$_p" ]; then _IDSTACK="${_p%/}"; break; fi done _HAS_TIMELINE=0 @@ -572,9 +566,9 @@ Generate an HTML report so the designer has a single document to read. The repor ```bash # Resolve the idstack install dir. Re-derived at the top of every bash block — # blocks run in separate shells, so a value derived in an earlier block is not -# available here. Priority: explicit env overrides, Codex-style symlinks, then -# the Claude Code marketplace cache. Empty if none found; guard -# "$_IDSTACK/bin/..." calls accordingly. +# available here. Priority: explicit env overrides, then the Claude Code +# marketplace cache. Empty if none found; guard "$_IDSTACK/bin/..." calls +# accordingly. _IDSTACK="" # Marketplace cache holds one dir per installed version. Sort the basenames by # numeric version fields, not lexically — plain sort ranks 3.9.0.0 above @@ -585,7 +579,7 @@ if [ -d "$_idstack_cache_root" ]; then _idstack_v=$(ls "$_idstack_cache_root" 2>/dev/null | sort -t. -k1,1n -k2,2n -k3,3n -k4,4n | tail -1) [ -n "$_idstack_v" ] && _idstack_cache="$_idstack_cache_root/$_idstack_v" fi -for _p in "${CLAUDE_PLUGIN_ROOT:-}" "${IDSTACK_HOME:-}" "$HOME/.agents/plugins/idstack" "$HOME/.agents/skills/idstack" "$_idstack_cache"; do +for _p in "${CLAUDE_PLUGIN_ROOT:-}" "${IDSTACK_HOME:-}" "$_idstack_cache"; do if [ -n "$_p" ] && [ -d "$_p" ]; then _IDSTACK="${_p%/}"; break; fi done # Compute the course slug from project_name and prepare the export folder. @@ -674,9 +668,9 @@ The HTML report follows the visual contract in `templates/report.html.tmpl` and ```bash # Resolve the idstack install dir. Re-derived at the top of every bash block — # blocks run in separate shells, so a value derived in an earlier block is not -# available here. Priority: explicit env overrides, Codex-style symlinks, then -# the Claude Code marketplace cache. Empty if none found; guard -# "$_IDSTACK/bin/..." calls accordingly. +# available here. Priority: explicit env overrides, then the Claude Code +# marketplace cache. Empty if none found; guard "$_IDSTACK/bin/..." calls +# accordingly. _IDSTACK="" # Marketplace cache holds one dir per installed version. Sort the basenames by # numeric version fields, not lexically — plain sort ranks 3.9.0.0 above @@ -687,7 +681,7 @@ if [ -d "$_idstack_cache_root" ]; then _idstack_v=$(ls "$_idstack_cache_root" 2>/dev/null | sort -t. -k1,1n -k2,2n -k3,3n -k4,4n | tail -1) [ -n "$_idstack_v" ] && _idstack_cache="$_idstack_cache_root/$_idstack_v" fi -for _p in "${CLAUDE_PLUGIN_ROOT:-}" "${IDSTACK_HOME:-}" "$HOME/.agents/plugins/idstack" "$HOME/.agents/skills/idstack" "$_idstack_cache"; do +for _p in "${CLAUDE_PLUGIN_ROOT:-}" "${IDSTACK_HOME:-}" "$_idstack_cache"; do if [ -n "$_p" ] && [ -d "$_p" ]; then _IDSTACK="${_p%/}"; break; fi done # Write a payload for your skill's section, then: @@ -1074,9 +1068,9 @@ After the skill workflow completes successfully, log the session to the timeline ```bash # Resolve the idstack install dir. Re-derived at the top of every bash block — # blocks run in separate shells, so a value derived in an earlier block is not -# available here. Priority: explicit env overrides, Codex-style symlinks, then -# the Claude Code marketplace cache. Empty if none found; guard -# "$_IDSTACK/bin/..." calls accordingly. +# available here. Priority: explicit env overrides, then the Claude Code +# marketplace cache. Empty if none found; guard "$_IDSTACK/bin/..." calls +# accordingly. _IDSTACK="" # Marketplace cache holds one dir per installed version. Sort the basenames by # numeric version fields, not lexically — plain sort ranks 3.9.0.0 above @@ -1087,7 +1081,7 @@ if [ -d "$_idstack_cache_root" ]; then _idstack_v=$(ls "$_idstack_cache_root" 2>/dev/null | sort -t. -k1,1n -k2,2n -k3,3n -k4,4n | tail -1) [ -n "$_idstack_v" ] && _idstack_cache="$_idstack_cache_root/$_idstack_v" fi -for _p in "${CLAUDE_PLUGIN_ROOT:-}" "${IDSTACK_HOME:-}" "$HOME/.agents/plugins/idstack" "$HOME/.agents/skills/idstack" "$_idstack_cache"; do +for _p in "${CLAUDE_PLUGIN_ROOT:-}" "${IDSTACK_HOME:-}" "$_idstack_cache"; do if [ -n "$_p" ] && [ -d "$_p" ]; then _IDSTACK="${_p%/}"; break; fi done "$_IDSTACK/bin/idstack-timeline-log" '{"skill":"needs-analysis","event":"completed"}' @@ -1102,9 +1096,9 @@ import format issue, course structure pattern), also log it as a learning: ```bash # Resolve the idstack install dir. Re-derived at the top of every bash block — # blocks run in separate shells, so a value derived in an earlier block is not -# available here. Priority: explicit env overrides, Codex-style symlinks, then -# the Claude Code marketplace cache. Empty if none found; guard -# "$_IDSTACK/bin/..." calls accordingly. +# available here. Priority: explicit env overrides, then the Claude Code +# marketplace cache. Empty if none found; guard "$_IDSTACK/bin/..." calls +# accordingly. _IDSTACK="" # Marketplace cache holds one dir per installed version. Sort the basenames by # numeric version fields, not lexically — plain sort ranks 3.9.0.0 above @@ -1115,7 +1109,7 @@ if [ -d "$_idstack_cache_root" ]; then _idstack_v=$(ls "$_idstack_cache_root" 2>/dev/null | sort -t. -k1,1n -k2,2n -k3,3n -k4,4n | tail -1) [ -n "$_idstack_v" ] && _idstack_cache="$_idstack_cache_root/$_idstack_v" fi -for _p in "${CLAUDE_PLUGIN_ROOT:-}" "${IDSTACK_HOME:-}" "$HOME/.agents/plugins/idstack" "$HOME/.agents/skills/idstack" "$_idstack_cache"; do +for _p in "${CLAUDE_PLUGIN_ROOT:-}" "${IDSTACK_HOME:-}" "$_idstack_cache"; do if [ -n "$_p" ] && [ -d "$_p" ]; then _IDSTACK="${_p%/}"; break; fi done "$_IDSTACK/bin/idstack-learnings-log" '{"skill":"needs-analysis","type":"operational","key":"SHORT_KEY","insight":"DESCRIPTION","confidence":8,"source":"observed"}' diff --git a/skills/pipeline/SKILL.md b/skills/pipeline/SKILL.md index bdb145b..2348475 100644 --- a/skills/pipeline/SKILL.md +++ b/skills/pipeline/SKILL.md @@ -20,29 +20,23 @@ allowed-tools: ## Preamble: Interaction Conventions -idstack skills are designed to run in multiple CLIs (Claude Code, OpenAI Codex CLI, and -others). To stay portable, skill bodies use a few **concept names** that have a CLI-specific -implementation: +idstack runs in Claude Code. Skill bodies use a few **concept names** for the tools they +lean on: - **AskUserQuestion** — when a skill says "ask via AskUserQuestion" or "using AskUserQuestion", - it means: present a single numbered multiple-choice question (e.g., "Which of these best - describes X? 1) ..., 2) ..., 3) Other") and stop, waiting for the user's next message - before proceeding. Ask **one** question at a time, never batch. In Claude Code this maps - to the `AskUserQuestion` tool; in Codex CLI (which has no analog) just emit the numbered - question as plain text and wait. -- **Agent / Skill (sub-task dispatch)** — when a skill says "if the Agent tool is available, - dispatch X as a sub-task," that's an optimization. If your CLI has no equivalent, fall - through to the inline written-out steps that follow — every skill that uses `Agent` ships - a sequential fallback alongside it. -- **Skill (cross-skill invocation)** — used only by `/idstack:pipeline`. In Claude Code this - invokes a child skill in-process via the `Skill` tool. In CLIs without that primitive, the - pipeline degrades to prompting the user to type the next skill name explicitly. -- **Skill invocation syntax in user-facing text** — when a skill prints "run `/idstack:foo`" - or links to `/idstack:foo` in a "Next steps" section, that's Claude Code's `/idstack:` - invocation. **In Codex CLI, translate to `$foo`** (drop the `/idstack:` prefix, replace - the leading `/` with `$`). Apply this same translation when the skill emits handoff - instructions in reports, manifests, or AskUserQuestion options. Same body text, two - hosts; the model translates per-CLI on output. + it means: present a single multiple-choice question (e.g., "Which of these best describes X?") + and stop, waiting for the user's answer before proceeding. Ask **one** question at a time, + never batch. This maps to the `AskUserQuestion` tool. +- **Agent (sub-task dispatch)** — when a skill says "if the Agent tool is available, dispatch + X as a sub-task," that's an optimization. `Agent` is not in every skill's `allowed-tools`, + so when it isn't available, fall through to the inline written-out steps that follow — + every skill that uses `Agent` ships a sequential fallback alongside it. +- **Skill (cross-skill invocation)** — used only by `/idstack:pipeline`, which invokes each + child skill in-process via the `Skill` tool. +- **Skill invocation syntax in user-facing text** — every skill is invoked as + `/idstack:`. Always write the namespaced form: a bare `/` is not a valid + command. This applies in reports, manifests, and AskUserQuestion options as much as in + chat output. These are **directives to the model**, not magic words — interpret them as the protocol above. @@ -51,9 +45,9 @@ These are **directives to the model**, not magic words — interpret them as the ```bash # Resolve the idstack install dir. Re-derived at the top of every bash block — # blocks run in separate shells, so a value derived in an earlier block is not -# available here. Priority: explicit env overrides, Codex-style symlinks, then -# the Claude Code marketplace cache (highest version). Empty if none found; -# guard "$_IDSTACK/bin/..." calls accordingly. +# available here. Priority: explicit env overrides, then the Claude Code +# marketplace cache (highest version). Empty if none found; guard +# "$_IDSTACK/bin/..." calls accordingly. # Canonical copy: templates/snippets/idstack-resolve.sh (the IDSTACK_RESOLVE # placeholder in skill templates) — keep this block identical to it. _IDSTACK="" @@ -66,7 +60,7 @@ if [ -d "$_idstack_cache_root" ]; then _idstack_v=$(ls "$_idstack_cache_root" 2>/dev/null | sort -t. -k1,1n -k2,2n -k3,3n -k4,4n | tail -1) [ -n "$_idstack_v" ] && _idstack_cache="$_idstack_cache_root/$_idstack_v" fi -for _p in "${CLAUDE_PLUGIN_ROOT:-}" "${IDSTACK_HOME:-}" "$HOME/.agents/plugins/idstack" "$HOME/.agents/skills/idstack" "$_idstack_cache"; do +for _p in "${CLAUDE_PLUGIN_ROOT:-}" "${IDSTACK_HOME:-}" "$_idstack_cache"; do if [ -n "$_p" ] && [ -d "$_p" ]; then _IDSTACK="${_p%/}"; break; fi done _UPD=$("$_IDSTACK/bin/idstack-update-check" 2>/dev/null || true) @@ -91,7 +85,7 @@ if [ -d "$_idstack_cache_root" ]; then _idstack_v=$(ls "$_idstack_cache_root" 2>/dev/null | sort -t. -k1,1n -k2,2n -k3,3n -k4,4n | tail -1) [ -n "$_idstack_v" ] && _idstack_cache="$_idstack_cache_root/$_idstack_v" fi -for _p in "${CLAUDE_PLUGIN_ROOT:-}" "${IDSTACK_HOME:-}" "$HOME/.agents/plugins/idstack" "$HOME/.agents/skills/idstack" "$_idstack_cache"; do +for _p in "${CLAUDE_PLUGIN_ROOT:-}" "${IDSTACK_HOME:-}" "$_idstack_cache"; do if [ -n "$_p" ] && [ -d "$_p" ]; then _IDSTACK="${_p%/}"; break; fi done if [ -f ".idstack/project.json" ]; then @@ -174,7 +168,7 @@ if [ -d "$_idstack_cache_root" ]; then _idstack_v=$(ls "$_idstack_cache_root" 2>/dev/null | sort -t. -k1,1n -k2,2n -k3,3n -k4,4n | tail -1) [ -n "$_idstack_v" ] && _idstack_cache="$_idstack_cache_root/$_idstack_v" fi -for _p in "${CLAUDE_PLUGIN_ROOT:-}" "${IDSTACK_HOME:-}" "$HOME/.agents/plugins/idstack" "$HOME/.agents/skills/idstack" "$_idstack_cache"; do +for _p in "${CLAUDE_PLUGIN_ROOT:-}" "${IDSTACK_HOME:-}" "$_idstack_cache"; do if [ -n "$_p" ] && [ -d "$_p" ]; then _IDSTACK="${_p%/}"; break; fi done _HAS_TIMELINE=0 @@ -376,13 +370,13 @@ For each skill from the starting point onward: 4. When the skill completes (logs to timeline), **execute Step 4 (Generate Course Dashboard) inline** to refresh `.idstack/exports//index.html` against the new per-skill report. This keeps the dashboard fresh if the designer pauses partway through. 5. Announce completion and move to next. -**If the Skill tool is NOT available** (e.g., on Codex CLI or any host without a primitive -to invoke a sibling skill in-process), degrade gracefully: after announcing the next skill, -print exactly what the user should type to invoke it (e.g., "Type `$needs-analysis` to run -the next stage, then resume the pipeline by typing `$pipeline` again") and STOP. The user -runs the skill themselves; the pipeline picks up on the next `$pipeline` invocation by re- -reading the timeline and continuing from the new starting point. Generate the course -dashboard (Step 4) before stopping so the partial-run dashboard is up to date. +**If the `Skill` invocation fails** (the tool is unavailable, or the child skill cannot be +resolved), degrade gracefully rather than silently skipping the stage: print exactly what +the user should type to run it themselves (e.g., "Type `/idstack:needs-analysis` to run the +next stage, then resume by typing `/idstack:pipeline` again") and STOP. The pipeline picks +up on the next `/idstack:pipeline` invocation by re-reading the timeline and continuing from +the new starting point. Generate the course dashboard (Step 4) before stopping so the +partial-run dashboard is up to date. **Between skills**, briefly announce the transition: "[skill-name] complete. Course dashboard refreshed. Moving to /idstack:..." @@ -401,9 +395,9 @@ After each skill completes (or after the orchestrator finishes the run, includin ```bash # Resolve the idstack install dir. Re-derived at the top of every bash block — # blocks run in separate shells, so a value derived in an earlier block is not -# available here. Priority: explicit env overrides, Codex-style symlinks, then -# the Claude Code marketplace cache. Empty if none found; guard -# "$_IDSTACK/bin/..." calls accordingly. +# available here. Priority: explicit env overrides, then the Claude Code +# marketplace cache. Empty if none found; guard "$_IDSTACK/bin/..." calls +# accordingly. _IDSTACK="" # Marketplace cache holds one dir per installed version. Sort the basenames by # numeric version fields, not lexically — plain sort ranks 3.9.0.0 above @@ -414,7 +408,7 @@ if [ -d "$_idstack_cache_root" ]; then _idstack_v=$(ls "$_idstack_cache_root" 2>/dev/null | sort -t. -k1,1n -k2,2n -k3,3n -k4,4n | tail -1) [ -n "$_idstack_v" ] && _idstack_cache="$_idstack_cache_root/$_idstack_v" fi -for _p in "${CLAUDE_PLUGIN_ROOT:-}" "${IDSTACK_HOME:-}" "$HOME/.agents/plugins/idstack" "$HOME/.agents/skills/idstack" "$_idstack_cache"; do +for _p in "${CLAUDE_PLUGIN_ROOT:-}" "${IDSTACK_HOME:-}" "$_idstack_cache"; do if [ -n "$_p" ] && [ -d "$_p" ]; then _IDSTACK="${_p%/}"; break; fi done _PROJECT_NAME=$(python3 -c "import json; print(json.load(open('.idstack/project.json')).get('project_name',''))" 2>/dev/null || echo "") @@ -464,9 +458,9 @@ When all remaining skills have been executed: ```bash # Resolve the idstack install dir. Re-derived at the top of every bash block — # blocks run in separate shells, so a value derived in an earlier block is not -# available here. Priority: explicit env overrides, Codex-style symlinks, then -# the Claude Code marketplace cache. Empty if none found; guard -# "$_IDSTACK/bin/..." calls accordingly. +# available here. Priority: explicit env overrides, then the Claude Code +# marketplace cache. Empty if none found; guard "$_IDSTACK/bin/..." calls +# accordingly. _IDSTACK="" # Marketplace cache holds one dir per installed version. Sort the basenames by # numeric version fields, not lexically — plain sort ranks 3.9.0.0 above @@ -477,7 +471,7 @@ if [ -d "$_idstack_cache_root" ]; then _idstack_v=$(ls "$_idstack_cache_root" 2>/dev/null | sort -t. -k1,1n -k2,2n -k3,3n -k4,4n | tail -1) [ -n "$_idstack_v" ] && _idstack_cache="$_idstack_cache_root/$_idstack_v" fi -for _p in "${CLAUDE_PLUGIN_ROOT:-}" "${IDSTACK_HOME:-}" "$HOME/.agents/plugins/idstack" "$HOME/.agents/skills/idstack" "$_idstack_cache"; do +for _p in "${CLAUDE_PLUGIN_ROOT:-}" "${IDSTACK_HOME:-}" "$_idstack_cache"; do if [ -n "$_p" ] && [ -d "$_p" ]; then _IDSTACK="${_p%/}"; break; fi done [ -n "$_IDSTACK" ] && "$_IDSTACK/bin/idstack-timeline-log" '{"skill":"pipeline","event":"completed"}' diff --git a/skills/pipeline/SKILL.md.tmpl b/skills/pipeline/SKILL.md.tmpl index 5de391e..46797dc 100644 --- a/skills/pipeline/SKILL.md.tmpl +++ b/skills/pipeline/SKILL.md.tmpl @@ -125,13 +125,13 @@ For each skill from the starting point onward: 4. When the skill completes (logs to timeline), **execute Step 4 (Generate Course Dashboard) inline** to refresh `.idstack/exports//index.html` against the new per-skill report. This keeps the dashboard fresh if the designer pauses partway through. 5. Announce completion and move to next. -**If the Skill tool is NOT available** (e.g., on Codex CLI or any host without a primitive -to invoke a sibling skill in-process), degrade gracefully: after announcing the next skill, -print exactly what the user should type to invoke it (e.g., "Type `$needs-analysis` to run -the next stage, then resume the pipeline by typing `$pipeline` again") and STOP. The user -runs the skill themselves; the pipeline picks up on the next `$pipeline` invocation by re- -reading the timeline and continuing from the new starting point. Generate the course -dashboard (Step 4) before stopping so the partial-run dashboard is up to date. +**If the `Skill` invocation fails** (the tool is unavailable, or the child skill cannot be +resolved), degrade gracefully rather than silently skipping the stage: print exactly what +the user should type to run it themselves (e.g., "Type `/idstack:needs-analysis` to run the +next stage, then resume by typing `/idstack:pipeline` again") and STOP. The pipeline picks +up on the next `/idstack:pipeline` invocation by re-reading the timeline and continuing from +the new starting point. Generate the course dashboard (Step 4) before stopping so the +partial-run dashboard is up to date. **Between skills**, briefly announce the transition: "[skill-name] complete. Course dashboard refreshed. Moving to /idstack:..." diff --git a/skills/red-team/SKILL.md b/skills/red-team/SKILL.md index 6c7adef..c626704 100644 --- a/skills/red-team/SKILL.md +++ b/skills/red-team/SKILL.md @@ -23,29 +23,23 @@ allowed-tools: ## Preamble: Interaction Conventions -idstack skills are designed to run in multiple CLIs (Claude Code, OpenAI Codex CLI, and -others). To stay portable, skill bodies use a few **concept names** that have a CLI-specific -implementation: +idstack runs in Claude Code. Skill bodies use a few **concept names** for the tools they +lean on: - **AskUserQuestion** — when a skill says "ask via AskUserQuestion" or "using AskUserQuestion", - it means: present a single numbered multiple-choice question (e.g., "Which of these best - describes X? 1) ..., 2) ..., 3) Other") and stop, waiting for the user's next message - before proceeding. Ask **one** question at a time, never batch. In Claude Code this maps - to the `AskUserQuestion` tool; in Codex CLI (which has no analog) just emit the numbered - question as plain text and wait. -- **Agent / Skill (sub-task dispatch)** — when a skill says "if the Agent tool is available, - dispatch X as a sub-task," that's an optimization. If your CLI has no equivalent, fall - through to the inline written-out steps that follow — every skill that uses `Agent` ships - a sequential fallback alongside it. -- **Skill (cross-skill invocation)** — used only by `/idstack:pipeline`. In Claude Code this - invokes a child skill in-process via the `Skill` tool. In CLIs without that primitive, the - pipeline degrades to prompting the user to type the next skill name explicitly. -- **Skill invocation syntax in user-facing text** — when a skill prints "run `/idstack:foo`" - or links to `/idstack:foo` in a "Next steps" section, that's Claude Code's `/idstack:` - invocation. **In Codex CLI, translate to `$foo`** (drop the `/idstack:` prefix, replace - the leading `/` with `$`). Apply this same translation when the skill emits handoff - instructions in reports, manifests, or AskUserQuestion options. Same body text, two - hosts; the model translates per-CLI on output. + it means: present a single multiple-choice question (e.g., "Which of these best describes X?") + and stop, waiting for the user's answer before proceeding. Ask **one** question at a time, + never batch. This maps to the `AskUserQuestion` tool. +- **Agent (sub-task dispatch)** — when a skill says "if the Agent tool is available, dispatch + X as a sub-task," that's an optimization. `Agent` is not in every skill's `allowed-tools`, + so when it isn't available, fall through to the inline written-out steps that follow — + every skill that uses `Agent` ships a sequential fallback alongside it. +- **Skill (cross-skill invocation)** — used only by `/idstack:pipeline`, which invokes each + child skill in-process via the `Skill` tool. +- **Skill invocation syntax in user-facing text** — every skill is invoked as + `/idstack:`. Always write the namespaced form: a bare `/` is not a valid + command. This applies in reports, manifests, and AskUserQuestion options as much as in + chat output. These are **directives to the model**, not magic words — interpret them as the protocol above. @@ -54,9 +48,9 @@ These are **directives to the model**, not magic words — interpret them as the ```bash # Resolve the idstack install dir. Re-derived at the top of every bash block — # blocks run in separate shells, so a value derived in an earlier block is not -# available here. Priority: explicit env overrides, Codex-style symlinks, then -# the Claude Code marketplace cache (highest version). Empty if none found; -# guard "$_IDSTACK/bin/..." calls accordingly. +# available here. Priority: explicit env overrides, then the Claude Code +# marketplace cache (highest version). Empty if none found; guard +# "$_IDSTACK/bin/..." calls accordingly. # Canonical copy: templates/snippets/idstack-resolve.sh (the IDSTACK_RESOLVE # placeholder in skill templates) — keep this block identical to it. _IDSTACK="" @@ -69,7 +63,7 @@ if [ -d "$_idstack_cache_root" ]; then _idstack_v=$(ls "$_idstack_cache_root" 2>/dev/null | sort -t. -k1,1n -k2,2n -k3,3n -k4,4n | tail -1) [ -n "$_idstack_v" ] && _idstack_cache="$_idstack_cache_root/$_idstack_v" fi -for _p in "${CLAUDE_PLUGIN_ROOT:-}" "${IDSTACK_HOME:-}" "$HOME/.agents/plugins/idstack" "$HOME/.agents/skills/idstack" "$_idstack_cache"; do +for _p in "${CLAUDE_PLUGIN_ROOT:-}" "${IDSTACK_HOME:-}" "$_idstack_cache"; do if [ -n "$_p" ] && [ -d "$_p" ]; then _IDSTACK="${_p%/}"; break; fi done _UPD=$("$_IDSTACK/bin/idstack-update-check" 2>/dev/null || true) @@ -94,7 +88,7 @@ if [ -d "$_idstack_cache_root" ]; then _idstack_v=$(ls "$_idstack_cache_root" 2>/dev/null | sort -t. -k1,1n -k2,2n -k3,3n -k4,4n | tail -1) [ -n "$_idstack_v" ] && _idstack_cache="$_idstack_cache_root/$_idstack_v" fi -for _p in "${CLAUDE_PLUGIN_ROOT:-}" "${IDSTACK_HOME:-}" "$HOME/.agents/plugins/idstack" "$HOME/.agents/skills/idstack" "$_idstack_cache"; do +for _p in "${CLAUDE_PLUGIN_ROOT:-}" "${IDSTACK_HOME:-}" "$_idstack_cache"; do if [ -n "$_p" ] && [ -d "$_p" ]; then _IDSTACK="${_p%/}"; break; fi done if [ -f ".idstack/project.json" ]; then @@ -177,7 +171,7 @@ if [ -d "$_idstack_cache_root" ]; then _idstack_v=$(ls "$_idstack_cache_root" 2>/dev/null | sort -t. -k1,1n -k2,2n -k3,3n -k4,4n | tail -1) [ -n "$_idstack_v" ] && _idstack_cache="$_idstack_cache_root/$_idstack_v" fi -for _p in "${CLAUDE_PLUGIN_ROOT:-}" "${IDSTACK_HOME:-}" "$HOME/.agents/plugins/idstack" "$HOME/.agents/skills/idstack" "$_idstack_cache"; do +for _p in "${CLAUDE_PLUGIN_ROOT:-}" "${IDSTACK_HOME:-}" "$_idstack_cache"; do if [ -n "$_p" ] && [ -d "$_p" ]; then _IDSTACK="${_p%/}"; break; fi done _HAS_TIMELINE=0 @@ -543,9 +537,9 @@ The orchestrator emits an HTML report. Follow the **visual contract** in `templa ```bash # Resolve the idstack install dir. Re-derived at the top of every bash block — # blocks run in separate shells, so a value derived in an earlier block is not -# available here. Priority: explicit env overrides, Codex-style symlinks, then -# the Claude Code marketplace cache. Empty if none found; guard -# "$_IDSTACK/bin/..." calls accordingly. +# available here. Priority: explicit env overrides, then the Claude Code +# marketplace cache. Empty if none found; guard "$_IDSTACK/bin/..." calls +# accordingly. _IDSTACK="" # Marketplace cache holds one dir per installed version. Sort the basenames by # numeric version fields, not lexically — plain sort ranks 3.9.0.0 above @@ -556,7 +550,7 @@ if [ -d "$_idstack_cache_root" ]; then _idstack_v=$(ls "$_idstack_cache_root" 2>/dev/null | sort -t. -k1,1n -k2,2n -k3,3n -k4,4n | tail -1) [ -n "$_idstack_v" ] && _idstack_cache="$_idstack_cache_root/$_idstack_v" fi -for _p in "${CLAUDE_PLUGIN_ROOT:-}" "${IDSTACK_HOME:-}" "$HOME/.agents/plugins/idstack" "$HOME/.agents/skills/idstack" "$_idstack_cache"; do +for _p in "${CLAUDE_PLUGIN_ROOT:-}" "${IDSTACK_HOME:-}" "$_idstack_cache"; do if [ -n "$_p" ] && [ -d "$_p" ]; then _IDSTACK="${_p%/}"; break; fi done # Compute the course slug from project_name and prepare the export folder. @@ -640,9 +634,9 @@ not re-derive from the orchestrator's return summary, which is lossy). ```bash # Resolve the idstack install dir. Re-derived at the top of every bash block — # blocks run in separate shells, so a value derived in an earlier block is not -# available here. Priority: explicit env overrides, Codex-style symlinks, then -# the Claude Code marketplace cache. Empty if none found; guard -# "$_IDSTACK/bin/..." calls accordingly. +# available here. Priority: explicit env overrides, then the Claude Code +# marketplace cache. Empty if none found; guard "$_IDSTACK/bin/..." calls +# accordingly. _IDSTACK="" # Marketplace cache holds one dir per installed version. Sort the basenames by # numeric version fields, not lexically — plain sort ranks 3.9.0.0 above @@ -653,7 +647,7 @@ if [ -d "$_idstack_cache_root" ]; then _idstack_v=$(ls "$_idstack_cache_root" 2>/dev/null | sort -t. -k1,1n -k2,2n -k3,3n -k4,4n | tail -1) [ -n "$_idstack_v" ] && _idstack_cache="$_idstack_cache_root/$_idstack_v" fi -for _p in "${CLAUDE_PLUGIN_ROOT:-}" "${IDSTACK_HOME:-}" "$HOME/.agents/plugins/idstack" "$HOME/.agents/skills/idstack" "$_idstack_cache"; do +for _p in "${CLAUDE_PLUGIN_ROOT:-}" "${IDSTACK_HOME:-}" "$_idstack_cache"; do if [ -n "$_p" ] && [ -d "$_p" ]; then _IDSTACK="${_p%/}"; break; fi done "$_IDSTACK/bin/idstack-manifest-merge" --section red_team_audit --payload - <<'PAYLOAD' @@ -693,9 +687,9 @@ tool exits 4: ```bash # Resolve the idstack install dir. Re-derived at the top of every bash block — # blocks run in separate shells, so a value derived in an earlier block is not -# available here. Priority: explicit env overrides, Codex-style symlinks, then -# the Claude Code marketplace cache. Empty if none found; guard -# "$_IDSTACK/bin/..." calls accordingly. +# available here. Priority: explicit env overrides, then the Claude Code +# marketplace cache. Empty if none found; guard "$_IDSTACK/bin/..." calls +# accordingly. _IDSTACK="" # Marketplace cache holds one dir per installed version. Sort the basenames by # numeric version fields, not lexically — plain sort ranks 3.9.0.0 above @@ -706,7 +700,7 @@ if [ -d "$_idstack_cache_root" ]; then _idstack_v=$(ls "$_idstack_cache_root" 2>/dev/null | sort -t. -k1,1n -k2,2n -k3,3n -k4,4n | tail -1) [ -n "$_idstack_v" ] && _idstack_cache="$_idstack_cache_root/$_idstack_v" fi -for _p in "${CLAUDE_PLUGIN_ROOT:-}" "${IDSTACK_HOME:-}" "$HOME/.agents/plugins/idstack" "$HOME/.agents/skills/idstack" "$_idstack_cache"; do +for _p in "${CLAUDE_PLUGIN_ROOT:-}" "${IDSTACK_HOME:-}" "$_idstack_cache"; do if [ -n "$_p" ] && [ -d "$_p" ]; then _IDSTACK="${_p%/}"; break; fi done "$_IDSTACK/bin/idstack-migrate" --init .idstack/project.json >/dev/null @@ -737,9 +731,9 @@ After the skill workflow completes successfully, log the session to the timeline ```bash # Resolve the idstack install dir. Re-derived at the top of every bash block — # blocks run in separate shells, so a value derived in an earlier block is not -# available here. Priority: explicit env overrides, Codex-style symlinks, then -# the Claude Code marketplace cache. Empty if none found; guard -# "$_IDSTACK/bin/..." calls accordingly. +# available here. Priority: explicit env overrides, then the Claude Code +# marketplace cache. Empty if none found; guard "$_IDSTACK/bin/..." calls +# accordingly. _IDSTACK="" # Marketplace cache holds one dir per installed version. Sort the basenames by # numeric version fields, not lexically — plain sort ranks 3.9.0.0 above @@ -750,7 +744,7 @@ if [ -d "$_idstack_cache_root" ]; then _idstack_v=$(ls "$_idstack_cache_root" 2>/dev/null | sort -t. -k1,1n -k2,2n -k3,3n -k4,4n | tail -1) [ -n "$_idstack_v" ] && _idstack_cache="$_idstack_cache_root/$_idstack_v" fi -for _p in "${CLAUDE_PLUGIN_ROOT:-}" "${IDSTACK_HOME:-}" "$HOME/.agents/plugins/idstack" "$HOME/.agents/skills/idstack" "$_idstack_cache"; do +for _p in "${CLAUDE_PLUGIN_ROOT:-}" "${IDSTACK_HOME:-}" "$_idstack_cache"; do if [ -n "$_p" ] && [ -d "$_p" ]; then _IDSTACK="${_p%/}"; break; fi done "$_IDSTACK/bin/idstack-timeline-log" '{"skill":"red-team","event":"completed"}' @@ -763,9 +757,9 @@ If you discover a non-obvious project-specific quirk during this session (LMS be ```bash # Resolve the idstack install dir. Re-derived at the top of every bash block — # blocks run in separate shells, so a value derived in an earlier block is not -# available here. Priority: explicit env overrides, Codex-style symlinks, then -# the Claude Code marketplace cache. Empty if none found; guard -# "$_IDSTACK/bin/..." calls accordingly. +# available here. Priority: explicit env overrides, then the Claude Code +# marketplace cache. Empty if none found; guard "$_IDSTACK/bin/..." calls +# accordingly. _IDSTACK="" # Marketplace cache holds one dir per installed version. Sort the basenames by # numeric version fields, not lexically — plain sort ranks 3.9.0.0 above @@ -776,7 +770,7 @@ if [ -d "$_idstack_cache_root" ]; then _idstack_v=$(ls "$_idstack_cache_root" 2>/dev/null | sort -t. -k1,1n -k2,2n -k3,3n -k4,4n | tail -1) [ -n "$_idstack_v" ] && _idstack_cache="$_idstack_cache_root/$_idstack_v" fi -for _p in "${CLAUDE_PLUGIN_ROOT:-}" "${IDSTACK_HOME:-}" "$HOME/.agents/plugins/idstack" "$HOME/.agents/skills/idstack" "$_idstack_cache"; do +for _p in "${CLAUDE_PLUGIN_ROOT:-}" "${IDSTACK_HOME:-}" "$_idstack_cache"; do if [ -n "$_p" ] && [ -d "$_p" ]; then _IDSTACK="${_p%/}"; break; fi done "$_IDSTACK/bin/idstack-learnings-log" '{"skill":"red-team","type":"operational","key":"SHORT_KEY","insight":"DESCRIPTION","confidence":8,"source":"observed"}' diff --git a/templates/agent-context.md b/templates/agent-context.md deleted file mode 100644 index 179892a..0000000 --- a/templates/agent-context.md +++ /dev/null @@ -1,223 +0,0 @@ -# idstack — agent context - -This file is auto-shipped to coding CLIs as `AGENTS.md` (Codex CLI), `GEMINI.md` (Gemini -CLI, future), or as the source for `CLAUDE.md` (Claude Code). It tells the model how -idstack is organized so a skill can do its job. - -## What is idstack - -An open-source set of skills that bring peer-reviewed instructional design research into -the course-design workflow. Each skill is a `SKILL.md` file with YAML frontmatter and a -conversational workflow backed by evidence from peer-reviewed research across 11 domains. - -idstack runs in multiple CLIs. The skill bodies are CLI-agnostic; small per-CLI shims -handle interaction primitives. - -## Architecture - -### Skills - -11 skills total. The 8-skill linear pipeline is the canonical sequence; `course-import` joins it -when there is an existing course to start from, and `learn` is a cross-project memory -utility that runs outside the pipeline. - -``` -pipeline (orchestrates the 8-skill linear chain, auto-skipping completed skills) - -needs-analysis → learning-objectives → assessment-design → course-builder - → course-quality-review → accessibility-review → red-team → course-export - ↑ -course-import ────────────────┘ - -learn (manage cross-project learnings) -``` - -Any skill also works standalone (asks questions directly if no manifest exists). -Invocation syntax depends on the host CLI: - -- Claude Code: `/idstack:` -- Codex CLI: `$` (or implicit selection by description) - -### Project manifest and reports — the dual-output contract - -Every skill that produces findings writes **both**: - -1. **JSON section** in `.idstack/project.json` — system state for downstream skills, the - pipeline orchestrator, and `bin/idstack-status`. -2. **HTML report** at `.idstack/exports//.html` — the human view, - branded and self-contained. Visual contract: `templates/report.html.tmpl` plus - `templates/assets/idstack.css`. Content contract: `templates/report-format.md`. The - skill writes the relative path back into its section's `report_path` field. - -The HTML is what the instructional designer reads (open in any browser; the folder is -self-contained with CSS bundled). The JSON is for the system. The two stay in sync — -every finding in the report corresponds to a finding in the manifest's structured arrays. - -`` is derived from the manifest's `project_name` via `bin/idstack-slugify` -(NFKD-fold, lowercase, kebab-case, ASCII-safe; empty → `untitled-course`). All per-course -artifacts — every per-skill HTML report, the pipeline `index.html` dashboard, and LMS -packages (`course-export.imscc`, `scorm-export.zip`) — live under the same -`.idstack/exports//` folder, so the deliverable is self-describing when -zipped, emailed, or handed off. - -`pipeline` produces `.idstack/exports//index.html` — the cross-cutting -course dashboard with readiness scores, links to every per-skill report, and a where-to- -start pointer. Regenerated on every pipeline run (including partial runs and explicit -re-runs when all skills are already complete). - -Manifest write rules: - -- Validate JSON on read. If malformed, report and stop. Never silently overwrite. -- Own your section only. Read full manifest, modify only your skill's section, preserve - everything else. -- Update the `updated` timestamp on every write. -- Use `bin/idstack-manifest-merge` for the write path: section-scoped, atomic (tempfile + - rename), preserves foreign sections, validates against the canonical schema in - `templates/manifest-schema.md`. Inline full-manifest edit is the deprecated fallback. -- **The one documented exception:** `needs-analysis` and `course-import` keep the - Read-modify-Write path because each writes several co-owned sections in one pass, which - whole-section merge cannot express. Both state why inline. Don't "fix" them to use the - merge tool, and don't copy their pattern into a single-section writer. -- Running standalone, call `bin/idstack-migrate --init` before merging. On a missing - manifest plain `idstack-migrate` is a no-op, so the merge that follows exits 4 and the - results are silently never persisted. - -Report write rules: - -- Follow the **visual contract** in `templates/report.html.tmpl` and the **content - contract** in `templates/report-format.md` — observation → evidence → why-it-matters → - suggestion, with severity (`critical|warning|info`) and evidence tier (`T1`–`T5`) on - every finding. Use the CSS hooks the stylesheet styles: `
`, ``, ``, `[Domain-N] [TN]`. -- Every skill that writes a report copies `$_IDSTACK/templates/assets/idstack.css` into - `.idstack/exports//assets/idstack.css` so the folder is self-contained - when zipped or moved. -- Phrase recommendations as suggestions ("consider…"), not directives. idstack is a - collaborator. -- Cite every recommendation. Findings without a `[Domain-N] [Tier]` citation belong in - *Limitations* or *Notes*, not *Findings*. - -### SKILL.md.tmpl structure - -Every skill template follows this pattern: - -1. **YAML frontmatter** with `name`, `description`, and (Claude target only) `allowed-tools` -2. **`{{PREAMBLE}}`** placeholder (replaced by `templates/preamble.md` during generation) -3. **Workflow** (Markdown defining the conversational flow, decision trees, outputs) -4. **`{{MANIFEST_SCHEMA}}`** placeholder (replaced by `templates/manifest-schema.md`) -5. **`{{IDSTACK_RESOLVE}}`** placeholder (replaced by `templates/snippets/idstack-resolve.sh`). - Unlike the other two it appears many times per template — once at the top of every - bash block that calls `$_IDSTACK/bin/...`. Bash blocks run in separate shells, so - `_IDSTACK` must be re-derived in each. The snippet is the single definition of that - resolution order: `CLAUDE_PLUGIN_ROOT`, `IDSTACK_HOME`, the Codex symlinks, then the - Claude Code marketplace cache. `templates/manifest-schema.md` is spliced verbatim and - writes the resolution out longhand; smoke-test keeps the two in lockstep. -6. **Timeline logging** (logs session data to `.idstack/timeline.jsonl` on completion) - -The shared preamble includes: update check, manifest check, preferences check, designer -profile check, and context recovery (reads timeline + learnings for welcome-back messages -and pipeline guidance). - -Python embedded in the preamble must parse on Python 3.9 — the version macOS ships. -`test/test-preamble-python.sh` runs every embedded block on 3.9 and 3.12; a syntax error -there dies silently at runtime, which is how context recovery stayed broken for several -releases. - -### Shared shell libraries - -Logic used by more than one script — or that deserves a unit test — lives in `bin/lib/` -and is sourced by its callers rather than inlined: - -- `bin/lib/version-classify.sh` — version comparison, shared by `setup` and `bin/idstack-doctor` -- `bin/lib/plugin-status.sh` — parses `claude plugin list` output into idstack's own entry - -Test the shipped file, never a copy. - -### Course memory - -Skills log session data to `.idstack/timeline.jsonl` (what skills ran, scores, -dimensions) and `.idstack/learnings.jsonl` (project-specific discoveries). The context -recovery preamble reads these on session start to provide continuity across -conversations. - -### Evidence standards - -Every recommendation in a skill must cite its evidence tier using domain codes from -`evidence/references.md`: - -| Tier | Meaning | -|------|---------| -| T1 | Meta-analyses, RCTs | -| T2 | Quasi-experimental with controls | -| T3 | Systematic reviews of mixed evidence | -| T4 | Observational, no comparison group | -| T5 | Expert opinion, theoretical frameworks | - -Format: `[DomainCode-Number] [Tier]` (e.g., `[Alignment-14] [T1]`). Stronger evidence -takes precedence when tiers conflict. - -### Interaction pattern - -- Ask one structured question at a time. Never batch multiple questions. (In Claude Code - this is the `AskUserQuestion` tool; in Codex CLI it is a numbered multiple-choice - question in plain text.) -- Skills must work without a manifest (fallback to asking questions directly). - -## Commands (run from the idstack install directory) - -```bash -./setup # Install for the detected CLI(s) -./setup --local # Install at project scope instead of user scope -./setup --codex / --no-codex # Force or skip the Codex bundle -./setup --keep-legacy # Leave pre-v2.0.1.0 installs in place -bin/idstack-gen-skills # Regenerate skill files for all targets -bin/idstack-gen-skills --target codex # Regenerate Codex flavor only -bin/idstack-gen-skills --dry-run # Check if generated files are up to date -bin/idstack-doctor # Diagnose installs across CLIs -bin/idstack-status # Course health dashboard (run in a project dir) -bin/idstack-status --readiness # Pre-export readiness check only -bin/idstack-migrate # Migrate .idstack/project.json to the latest schema -bin/idstack-migrate --init # Also create a canonical manifest when none exists -bin/idstack-manifest-merge --section --payload # Canonical manifest write path -bin/idstack-slugify "" # Derive the for .idstack/exports/ -``` - -Tests — all ten run in CI on every push and PR (`.github/workflows/test.yml`, -ubuntu on Python 3.9 + 3.12, macOS on 3.12): - -```bash -./test/smoke-test.sh # Install, SKILL.md freshness, frontmatter, version agreement, - # canonical section names, namespacing, resolve-snippet lockstep -./test/integration-test.sh # Behavioral tests across bin/; proves the suite leaves the tree untouched -./test/test-setup.sh # ./setup behavior: flags, scope, legacy cleanup, failure handling -./test/test-doctor.sh # bin/idstack-doctor PROBLEM/WARNING branches -./test/test-status.sh # bin/idstack-status, including the --readiness export gate -./test/test-manifest-merge.sh # bin/idstack-manifest-merge unit tests -./test/test-version-classifier.sh # bin/lib/version-classify.sh unit tests -./test/test-plugin-status.sh # bin/lib/plugin-status.sh unit tests -./test/test-preamble-python.sh # Runs the preamble's embedded python on 3.9 and 3.12 -./test/mutation-test.sh # Reintroduces each fixed defect, asserts its guarding test fails -``` - -`test/test-helper.sh` is sourced by every suite rather than run: it owns the -shared `PASS`/`FAIL`/`TOTAL` counters and the `check()` assertion. A suite must -not define its own counters — smoke-test fails on one. - -Python 3.9 is the oldest interpreter in the field (macOS system python3) and the -leg that catches modern-only syntax reaching the preamble's embedded scripts. - -No build step for users. No dependencies beyond bash (python3 recommended for full -features). Skills are plain Markdown files. - -## Design system - -Always read `DESIGN.md` (repo root) before making any visual or UI decision in this -project. All font choices, colors, spacing, border radii, motion timing, and aesthetic -direction are defined there. Do not deviate without explicit user approval and a -corresponding update to `DESIGN.md`. - -The canonical implementations are `templates/assets/idstack.css` (per-skill HTML -reports + course dashboard) and the inline `