@antoneeo/course-creator 0.1.0-beta.1 → 0.1.0-beta.13
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +102 -0
- package/README.md +4 -2
- package/gemini-extension.json +1 -1
- package/package.json +7 -2
- package/scripts/init.js +62 -62
- package/scripts/lib.js +98 -98
- package/scripts/postinstall.js +16 -16
- package/scripts/preuninstall.js +12 -12
- package/skills/course-creator/ENFORCEMENT.md +17 -5
- package/skills/course-creator/SKILL.md +8 -4
- package/skills/course-creator/dispatch.md +5 -5
- package/skills/course-creator/docs_site/app.js +1133 -0
- package/skills/course-creator/docs_site/index.html +17 -0
- package/skills/course-creator/docs_site/strings.js +323 -0
- package/skills/course-creator/docs_site/style.css +288 -0
- package/skills/course-creator/elicitation.md +23 -8
- package/skills/course-creator/guides.md +7 -1
- package/skills/course-creator/memory.md +1 -1
- package/skills/course-creator/review.md +107 -13
- package/skills/course-creator/routing.md +4 -0
- package/skills/course-creator/scripts/course_check.py +132 -132
- package/skills/course-creator/scripts/docs_site.py +1448 -0
- package/skills/course-creator/scripts/knowledge.py +620 -62
- package/skills/course-creator/scripts/sdlc_core.py +958 -84
- package/skills/course-creator/scripts/shared_files.py +10 -0
- package/skills/course-creator/scripts/shared_manifest.json +23 -14
- package/skills/course-creator/templates.md +51 -8
- package/skills/course-creator/vision.md +308 -245
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,107 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.1.0-beta.13] - 2026-10-23
|
|
4
|
+
|
|
5
|
+
### Changed
|
|
6
|
+
- `guides.md`: a document snapshot is verbatim except for credentials and personal or confidential data the guide does not need, which become `[REDACTED: <kind>]` before the snapshot is written; the guide lists the kinds in `redacted_at_intake:`.
|
|
7
|
+
- The session-start orientation (`orient`) now fits under Claude Code's 10,000-character hook limit, even in a project with many documents. Above that limit Claude Code saves the output to a file and shows the agent only the first 2,000 characters, so the guide router never reached the agent. The output is now at most 9,500 characters, including the knowledge layer's lines, and in this order: the triage line, the guide router (one line per guide), the workstream registry (one line per workstream, then the project-wide notes), then the reading guide (README). The manifest (INDEX) and the full files are listed as pointers. A section that does not fit is cut at a line, with a pointer to its file.
|
|
8
|
+
- The per-turn `remind` line also tells the agent, above L1 and when sibling lenses are installed, to choose the lens with `routing.md` and declare it. The line's limit rises from 500 to 600 characters (`vision/rulings.md` r32).
|
|
9
|
+
- `docs_site.py` adds `.docs-site/` to the project's `.gitignore` on its first run in a git repository, so the site's copy of the documents is not committed. It used to print a note only. An entry already in the file is never changed; when git still does not ignore the folder, the note is printed as before.
|
|
10
|
+
- `SKILL.md` names the docs site in the words users use ("docs site", "minisito", "documentation site", "project site"), so a request for it runs `docs_site.py` instead of building a page by hand.
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
- `check` refuses, in every lens, a line shaped like a credential (a password, token or key with a real value, a private-key header, a password in a URL, a bearer token) in a `corpus/given/` file or in the snapshot of a `source_kind: document` guide. The message names the file, the lines and the kinds, never the value. A human clears a false positive with `credential_scan: reviewed — <reason>` (in the sidecar, or in the guide). A project whose stored sources already hold such a line fails `check` until each is redacted or reviewed (`vision/rulings.md` r35a).
|
|
14
|
+
- `adopt`: a dry run listing the document guides that rest on `.sources/` snapshots and what moving each into `corpus/given/` involves. It writes nothing.
|
|
15
|
+
- `check` warns when the same text is stored both as a guide snapshot and as a corpus artifact.
|
|
16
|
+
- `check` also warns on a `corpus/given/` file with no sidecar, accepts a withdrawn artifact's tombstone sidecar (no bytes, no digest), and, when an `original_path:` is a URI, names `original_ref:` for non-local identifiers. A password inside a URL in that warning is masked.
|
|
17
|
+
- Shared claim checks (`check`): a claim's `about` cell may hold several relations separated by `; `, and one reserved predicate, `not-yet-honored-by -> ANALYSIS_<name>`, marks a specification claim the code does not yet honor. `check` refuses the marker when that analysis is missing from `solutions/` or cites neither the claim id nor the id of a row the claim succeeds. `export` drops the markers and says so in one line. Nothing lists or counts claims by the marker (`vision/rulings.md` r33). `memory.md`: a marked claim is cited as not yet true of the code, naming its analysis.
|
|
18
|
+
- Shared claim checks (`check`): the `not-yet-honored-by` marker now works in Hybrid projects (devPNT present). It may name `SHADOW_<doc_key>`; `check` reads the newest `solutions/SHADOW_<doc_key>_vX.Y.md` (versions compared number by number), which must exist and cite the claim id or a predecessor's. A missing shadow is an error that says to export it, and names a file of that key whose name lacks the `_vX.Y` suffix. `check` behaves the same with or without `--hybrid`; export drops both target forms (`vision/rulings.md` r36). New shared helper `sdlc_core.newest_shadow`. `memory.md`: a marked claim is cited naming its target.
|
|
19
|
+
- `verdict --hook` (PreToolUse): refuses an edit when the session declared no guide-router verdict, or never read a guide its verdict names; silent in projects without guides and whenever it cannot tell. Not wired by this package (`ENFORCEMENT.md` §3).
|
|
20
|
+
- `orient` adds a note when one client runs it from two settings entries, which means each session gets the orientation twice. Claude Code and Codex are counted separately, an identical command counts once, and an entry whose validator is gone is not counted.
|
|
21
|
+
- `orient` adds a line naming the sibling lenses installed beside the skill or in the user's Claude skills directory, and says to choose the lens with `routing.md`. The family list is read from `routing.md` §0, as the installer does.
|
|
22
|
+
- `routing.md` §2 gains worked verdicts for four mixed requests: an interface spec written for another team, the requirements another team must build, a guide whose source spec changed, and code that diverges from a supplied spec.
|
|
23
|
+
- The shared validator adds an advisory for a code-lens (`domain: code`) L3 analysis, started on or after 2026-10-24, that lacks a filled `## Guide check` section. Analyses of this lens are not checked.
|
|
24
|
+
|
|
25
|
+
## [0.1.0-beta.12] - 2026-10-19
|
|
26
|
+
|
|
27
|
+
Includes everything listed under [0.1.0-beta.11], which was tagged but never published to npm.
|
|
28
|
+
|
|
29
|
+
### Added
|
|
30
|
+
- `mark <paths> --note "<what was done>"` writes a dated entry `**YYYY-MM-DD — <what was done>.**` at the top of `audit/project_notes.md`. A release or work outside an open unit used to leave no dated event, because the date had to be copied there by hand. Without `--note`, and with no open unit, `mark` says so.
|
|
31
|
+
- `validate`: an advisory for a dated entry in `project_notes.md` whose bold does not pair; the chronology then showed the label running into the body.
|
|
32
|
+
- Docs site chronology: a Diary entry's label carries its unit id ("F-074 — closed"), so it reads on its own.
|
|
33
|
+
- `stale` no longer counts files inside the docs root (`ai_docs/`, `mkt_docs/`) as drift of any area, and `mark` refuses to record it: `validate` already checks the docs root, and every document edit used to leave `check` NOT CLEAN until a redundant re-mark.
|
|
34
|
+
|
|
35
|
+
## [0.1.0-beta.11] - 2026-10-19
|
|
36
|
+
|
|
37
|
+
### Added
|
|
38
|
+
- Docs site: `docs_site.py --zip <file>` also writes the site as one .zip to share; unzipped, `index.html` opens on any system. A zip made by hand with Windows PowerShell 5.1 `Compress-Archive` stores `data\core.js`, and on Linux or macOS the page then finds no data; `--zip` always writes `/` names. It refuses a target inside the docs root or the site folder. The `SKILL.md` line names it.
|
|
39
|
+
|
|
40
|
+
## [0.1.0-beta.10] - 2026-10-19
|
|
41
|
+
|
|
42
|
+
Includes everything listed under [0.1.0-beta.9], which was tagged but never published to npm.
|
|
43
|
+
|
|
44
|
+
### Added
|
|
45
|
+
- Docs site: each card in Open work and each feature in Features links its merge review and its end-to-end test script, when they exist. They were reachable only inside a feature's chain page; who merges looks in Open work while the work is open, who tests looks in Features after it closed.
|
|
46
|
+
|
|
47
|
+
### Changed
|
|
48
|
+
- Docs site: Features are listed newest first by their declared start date; those without a start date come last.
|
|
49
|
+
|
|
50
|
+
## [0.1.0-beta.9] - 2026-10-18
|
|
51
|
+
|
|
52
|
+
### Changed
|
|
53
|
+
- F-071: a review weighs effort against what the owner asked for. In a design review, each outcome the owner named (the opening request, any entry widening scope, an approved north star) is a row of the conformance statement, saying where the design serves it and what share of the design goes to it; an outcome no section serves, or one served by a minor share while a secondary concern takes the bulk, is a finding. `review.md` §Reviewing.
|
|
54
|
+
- F-071: a review runs where something is left to judge. At level 2 the closure review is due only on a security-sensitive area the lens's Rule Zero names, or at the owner's request; otherwise it does not run and logs no row. After a design or closure PASS, a delta made only of version and release metadata, regenerated indexes and audit-mark records, tool-verified copies of a shared file, and CHANGELOG lines adding no claim is not reviewed: its own check runs and a `NOT DUE` row records it. README or overview prose, an E2E script, a rulings row, doctrine, behaviour, tests and any correction of a review finding are still reviewed. `review.md` §When a review is due.
|
|
55
|
+
- `sdlc_check.py benefit` counts `NOT DUE` rows apart, in no review or findings figure.
|
|
56
|
+
|
|
57
|
+
## [0.1.0-beta.8] - 2026-10-18
|
|
58
|
+
|
|
59
|
+
### Fixed
|
|
60
|
+
- F-072 (field run): the `SKILL.md` line that announces the docs site now reads `scripts/docs_site.py --root <project> --open` and says to use it to show the project state. Asked for the state, an agent had opened the site in its own client pane, where it renders blank, and improvised a local server; `--open` opens it in the user's browser, where it works offline.
|
|
61
|
+
|
|
62
|
+
## [0.1.0-beta.7] - 2026-10-18
|
|
63
|
+
|
|
64
|
+
### Added
|
|
65
|
+
- F-074: every Vision is born from an owner interview. Before any Vision text is written (project, feature, the course Vision, and the devPNT M-VISION), the agent asks the owner six questions in order, re-asking an answer that has the wrong form: the problem as one concrete case, the benefit as a result for someone, the actors including who takes the initiative, a success signal with its starting value, the boundaries, and the known constraints with where each comes from. Then the owner approves a one- or two-sentence north star in their own words; without it no Vision is written, not even a draft. The blind check still tests that the rules hold; the approved north star shows they are the owner's. `vision.md` §The owner interview.
|
|
66
|
+
|
|
67
|
+
### Changed
|
|
68
|
+
- `elicitation.md`: the skip path never covers a Vision; the interview is a planned occasion like the round.
|
|
69
|
+
- Bootstrap no longer writes a Vision reconstructed by the agent: the installer seeds `ai_docs/vision/project_vision.md` as a stub awaiting the interview (an existing file is never overwritten).
|
|
70
|
+
- A Non-Goal now bounds scope. It rejects a proposal that pursues what it names; it is not a ban on behaviour that already exists and creates no obligation. A behaviour that must never happen is an Invariant or a constraint with its source.
|
|
71
|
+
- The course Vision template gains North Star and Problem, learners with the initiative, signals with starting values, Non-Goals as scope, and the source of every constraint.
|
|
72
|
+
|
|
73
|
+
## [0.1.0-beta.6] - 2026-10-18
|
|
74
|
+
|
|
75
|
+
### Added
|
|
76
|
+
- F-072: `scripts/docs_site.py --root <project>` writes a static, read-only website of what the agent reads in the project: what it is (reading guide, Vision, rules, documents), its open work with each next step verbatim, its courses, and — with a knowledge base — a map of topics, the facts with the exact passage each comes from, what an inferred fact rests on, and the source documents. It opens from disk with the network off, writes outside the docs root, changes nothing in it, and selects, ranks or recommends nothing. Each analysis opens with the chain the agent works through — Vision, use cases, flows and interfaces, risks, solution study, design and tests, merge brief and E2E script, Diary — the Vision's North Star is shown above the home page, and the guides have a page of their own: the guide router, the operative guides with when to read each, and the code-comprehension guides. The chronology lists the dated events the documents declare (Diary entries, spike outcomes, request-record entries, review-log rows, decision records, dated project notes, notes and sources received), most recent first, read with the same F-073 readers `validate` uses. Shared by the four packages (generator, page and battery are byte-identical spine files).
|
|
77
|
+
|
|
78
|
+
## [0.1.0-beta.5] - 2026-10-17
|
|
79
|
+
|
|
80
|
+
### Changed
|
|
81
|
+
- F-073: one written form for a dated entry, `**YYYY-MM-DD — label.**`, is taught for Diary entries, spike outcomes and project notes that record an event, and shared readers (`entry_openers`, `request_entries`, `adr_date`, `frontmatter_date`) let a chronology list those events the same way the validator reads them. Three new advisories, which never fail a build, not even `--strict`: undated text in the Diary of an L3 analysis started on or after 2026-10-18; a HANDOFF `next:` longer than 240 characters on an L3 file updated on or after that date (`next:` now holds only the next step, and the state of the work goes to the Diary); and, in `check` only, an open L3 unit whose id another branch or tag uses for a different analysis (one `git grep` over local refs, silent without git). The doctrine now says to choose a new unit's id after fetching, and that a session sharing a checkout with another session works in its own git worktree.
|
|
82
|
+
- The package publishes on npm `latest`, like the other three: `npm i @antoneeo/course-creator` installs the newest version with no separate promotion. The version keeps its `-beta.N` suffix and the README its experimental statement; the `beta` dist-tag no longer advances.
|
|
83
|
+
|
|
84
|
+
## [0.1.0-beta.4] - 2026-10-17
|
|
85
|
+
|
|
86
|
+
### Changed
|
|
87
|
+
- F-068 (shared doctrine with kb-agentic): the supersession cascade runs at the unit's closure, once for every supersession since the last one, instead of at each supersession.
|
|
88
|
+
- F1: closing the last open workstream now empties the generated registry `audit/handoff.md` instead of leaving its stale row behind: with zero `HANDOFF_*.md` sources `index` rewrites it empty (keeping `project_notes.md`), and `validate` reports a generated registry that still has rows but no sources as an error.
|
|
89
|
+
- F-069: `graph`, `check`, `stale` and the session-start count are faster on a mature knowledge base, with byte-identical output. Citations of superseded claim ids are found with one pass per living document instead of one scan per (document, id) pair, and one check run reads and resolves each cited extraction once instead of once per claim row. On a synthetic ledger of 2183 rows and 1000 living documents, `graph` drops from 14 s to 4 s. No new flag.
|
|
90
|
+
|
|
91
|
+
## [0.1.0-beta.3] - 2026-10-13
|
|
92
|
+
|
|
93
|
+
### Changed
|
|
94
|
+
- F-067: every L3 unit now keeps a verbatim, append-only request record `audit/requests/REQUEST_course_<slug>.md`, started before the Vision Gate: the owner's opening words and every later statement that settles something (answers, approvals, costs, rulings). Artifacts cite its entries instead of paraphrasing; the shared `review.md` asks for the record and makes an owner statement with no resolving entry a finding. Secrets, personal data and confidential third-party or employer information are redacted.
|
|
95
|
+
|
|
96
|
+
## [0.1.0-beta.2] - 2026-10-08
|
|
97
|
+
|
|
98
|
+
### Changed
|
|
99
|
+
- F-065: every agent review request opens with a fixed REVIEW MANDATE block copied verbatim, which carries `review.md` itself to the reviewer in Standalone and Hybrid alike; §Requesting now lists every input its clauses check (Functional Spec, Interface Contract, Component Map, audit plan, probe harness, closure test evidence). devPNT performs no review: in Hybrid an agent review with verdict PASS precedes every proposal to devPNT, where the human reviews; after the round cap only the user's explicit decision sends an artifact on, with open findings attached.
|
|
100
|
+
|
|
101
|
+
### Fixed
|
|
102
|
+
- Shared memory catalog hashes normalize CRLF/LF, avoiding checkout-dependent index mismatches.
|
|
103
|
+
- The validator prints its final check verdict after memory, topic and corpus checks, so their errors count in the summary.
|
|
104
|
+
|
|
3
105
|
## [0.1.0-beta.1] - 2026-10-02
|
|
4
106
|
|
|
5
107
|
- First public beta on the beta channel. Diagnostic simulation does not establish human learning; interfaces and method may change.
|
package/README.md
CHANGED
|
@@ -2,12 +2,12 @@
|
|
|
2
2
|
|
|
3
3
|
`course-creator` helps an AI agent create a course for a defined learner. It maps what the learner already knows, orders concepts by prerequisite, writes explanations in progressive modules, and links factual statements to reopenable sources. Independent fresh learner agents can expose confusing steps; their results are diagnostic. The course is labeled **efficacy not verified** until enough evidence from real learners supports a narrower claim.
|
|
4
4
|
|
|
5
|
-
The package is the fourth lens of the Agentic SDLC family. It uses the shared `ai_docs/` project memory and governance. Version 0.1.0-beta.
|
|
5
|
+
The package is the fourth lens of the Agentic SDLC family. It uses the shared `ai_docs/` project memory and governance. Version 0.1.0-beta.4 supports Standalone mode. This is an experimental beta: the authoring method and artifact contracts may change; human learning efficacy has not been established. Its course artifacts remain readable without the skill. Each new course keeps a verbatim, append-only record of the requester's own words and decisions in `audit/requests/`, cited by the analysis and checked by its reviews.
|
|
6
6
|
|
|
7
7
|
## Install and initialize
|
|
8
8
|
|
|
9
9
|
```sh
|
|
10
|
-
npm install -g @antoneeo/course-creator
|
|
10
|
+
npm install -g @antoneeo/course-creator
|
|
11
11
|
course-creator-install-skill
|
|
12
12
|
cd YOUR_PROJECT
|
|
13
13
|
course-creator-init
|
|
@@ -15,6 +15,8 @@ course-creator-init
|
|
|
15
15
|
|
|
16
16
|
The installer detects Claude Code, Gemini CLI, Codex and Google Antigravity. Initialization creates absent `ai_docs/` files and a project protocol pointer; it does not overwrite existing project documents. Use `python <skill_dir>/scripts/sdlc_check.py check --root .` to validate the shared SDLC and course structures.
|
|
17
17
|
|
|
18
|
+
**Docs site** (`scripts/docs_site.py --root <project> [--open] [--zip <file>]`): a static, offline, read-only website of what the agent reads in the project — reading guide, Vision, rules, documents, open work with each next step, courses and, with a knowledge base, the topic map, each fact with its exact source passage, and the source documents. Written to `.docs-site/` (added to `.gitignore` on the first run), outside `ai_docs/`; nothing is ranked or recommended.
|
|
19
|
+
|
|
18
20
|
Using the skill in your own project carries no obligation to publish your course or its sources. See [LICENSE](LICENSE) for the software terms and [NOTICE](NOTICE) for attribution.
|
|
19
21
|
|
|
20
22
|
## Primary course output
|
package/gemini-extension.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "course-creator",
|
|
3
|
-
"version": "0.1.0-beta.
|
|
3
|
+
"version": "0.1.0-beta.13",
|
|
4
4
|
"description": "Course creation for defined learners, with progressive explanations, sourced facts and diagnostic independent testing.",
|
|
5
5
|
"author": "Antonio Pinto (https://github.com/Antoneeo)"
|
|
6
6
|
}
|
package/package.json
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@antoneeo/course-creator",
|
|
3
|
-
"version": "0.1.0-beta.
|
|
3
|
+
"version": "0.1.0-beta.13",
|
|
4
4
|
"description": "Design sourced, progressive courses for a defined learner with Agentic SDLC governance and diagnostic learner simulations.",
|
|
5
5
|
"keywords": ["claude-code", "gemini-cli", "codex", "course-design", "skill", "ai-agent"],
|
|
6
6
|
"author": "Antonio Pinto (https://github.com/Antoneeo)",
|
|
7
7
|
"license": "Apache-2.0",
|
|
8
|
-
"publishConfig": { "access": "public"
|
|
8
|
+
"publishConfig": { "access": "public" },
|
|
9
9
|
"bin": {
|
|
10
10
|
"course-creator-init": "scripts/init.js",
|
|
11
11
|
"course-creator-install-skill": "scripts/postinstall.js"
|
|
@@ -35,6 +35,11 @@
|
|
|
35
35
|
"skills/course-creator/scripts/course_check.py",
|
|
36
36
|
"skills/course-creator/scripts/sdlc_core.py",
|
|
37
37
|
"skills/course-creator/scripts/knowledge.py",
|
|
38
|
+
"skills/course-creator/scripts/docs_site.py",
|
|
39
|
+
"skills/course-creator/docs_site/index.html",
|
|
40
|
+
"skills/course-creator/docs_site/app.js",
|
|
41
|
+
"skills/course-creator/docs_site/style.css",
|
|
42
|
+
"skills/course-creator/docs_site/strings.js",
|
|
38
43
|
"skills/course-creator/scripts/entry_point.py",
|
|
39
44
|
"skills/course-creator/scripts/shared_files.py",
|
|
40
45
|
"skills/course-creator/scripts/shared_manifest.json",
|
package/scripts/init.js
CHANGED
|
@@ -1,62 +1,62 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
"use strict";
|
|
3
|
-
const fs = require('fs');
|
|
4
|
-
const path = require('path');
|
|
5
|
-
const { execFileSync } = require('child_process');
|
|
6
|
-
const { SKILL_SOURCE, INSTALLED_SKILL_NAME, SELF_LENS, SIBLING_LENSES,
|
|
7
|
-
CLIENTS, clientDetected, skillTarget, wireOrientHook, detectPython } = require('./lib');
|
|
8
|
-
|
|
9
|
-
const root = process.cwd();
|
|
10
|
-
const seed = new Map([
|
|
11
|
-
['ai_docs/README.md', `---\ndefault_domain: course\n---\n# Project knowledge and course work\n\nRead vision/project_vision.md, INDEX.md and reference/INDEX.md first.\n`],
|
|
12
|
-
['ai_docs/vision/project_vision.md', `---\ndescription: Project benefit and boundaries.\nstatus: DRAFT\n---\n# Project Vision\nStatus: DRAFT\n
|
|
13
|
-
['ai_docs/vision/roadmap.md', `---\ndescription: Project learning and delivery roadmap.\nstatus: DRAFT\n---\n# Roadmap\nStatus: DRAFT\n`],
|
|
14
|
-
['ai_docs/vision/principles.md', `---\ndescription: Decisions that constrain project work.\nstatus: DRAFT\n---\n# Principles\nStatus: DRAFT\n`],
|
|
15
|
-
['ai_docs/strategic/architecture.md', `---\ndescription: Verified component inventory.\nstatus: CURRENT\n---\n# Architecture\n\n## Component Map\n| Component | Capability it owns | Contract | Where |\n|---|---|---|---|\n\n## Architectural Patterns\n<Add only verified patterns.>\n`],
|
|
16
|
-
['ai_docs/strategic/existing_features.md', `---\ndescription: Existing capabilities verified in this project.\nstatus: CURRENT\n---\n# Existing Features\n`],
|
|
17
|
-
['ai_docs/audit/audit_plan.md', `# Audit Plan\n\n| Area | State | Reference | Notes |\n|---|---|---|---|\n| . | PENDING | - | Initial analysis |\n`],
|
|
18
|
-
]);
|
|
19
|
-
function writeIfAbsent(rel, content) {
|
|
20
|
-
const dest = path.join(root, rel);
|
|
21
|
-
if (fs.existsSync(dest)) return false;
|
|
22
|
-
fs.mkdirSync(path.dirname(dest), { recursive: true });
|
|
23
|
-
fs.writeFileSync(dest, content, 'utf8');
|
|
24
|
-
console.log(`Created ${rel}`);
|
|
25
|
-
return true;
|
|
26
|
-
}
|
|
27
|
-
for (const [rel, content] of seed) writeIfAbsent(rel, content);
|
|
28
|
-
for (const dir of ['ai_docs/vision/features', 'ai_docs/reference', 'ai_docs/solutions/courses',
|
|
29
|
-
'ai_docs/audit/reviews']) fs.mkdirSync(path.join(root, dir), { recursive: true });
|
|
30
|
-
|
|
31
|
-
const pointer = `# Course Creator project protocol\n\nUse the installed \`course-creator\` skill when the deliverable teaches a defined learner. It applies Agentic SDLC in Standalone mode: triage, project and course Vision, ANALYSIS, progressive explanations, sourced facts, independent diagnostic testing and a truthful efficacy label. Course files live in \`ai_docs/solutions/courses/<slug>/\`. Read \`ai_docs/README.md\` and the skill before working.\n\nInstall if missing: \`npm i -g @antoneeo/course-creator@beta && course-creator-install-skill\`.\n`;
|
|
32
|
-
const pointers = { claude: 'CLAUDE.md', gemini: 'GEMINI.md', codex: 'AGENTS.md',
|
|
33
|
-
antigravity: 'AGENTS.md' };
|
|
34
|
-
const detected = CLIENTS.filter(clientDetected);
|
|
35
|
-
for (const client of detected) writeIfAbsent(pointers[client.key], pointer);
|
|
36
|
-
writeIfAbsent('.cursorrules', pointer);
|
|
37
|
-
const siblings = new Map();
|
|
38
|
-
for (const client of detected) {
|
|
39
|
-
const parent = path.dirname(skillTarget(client));
|
|
40
|
-
for (const [name, lens] of Object.entries(SIBLING_LENSES)) {
|
|
41
|
-
if (fs.existsSync(path.join(parent, name))) siblings.set(name, lens);
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
if (siblings.size) {
|
|
45
|
-
const rows = [`- \`${INSTALLED_SKILL_NAME}\` — the **${SELF_LENS}** lens`,
|
|
46
|
-
...[...siblings].map(([name, lens]) => `- \`${name}\` — the **${lens}** lens`)];
|
|
47
|
-
writeIfAbsent('AGENTIC_MULTI_LENS.md',
|
|
48
|
-
`# Agentic SDLC family routing\n\nRead \`routing.md\` to choose the owner of each deliverable; \`memory.md\` is shared.\n\n${rows.join('\n')}\n`);
|
|
49
|
-
}
|
|
50
|
-
const python = detectPython();
|
|
51
|
-
for (const client of detected.filter(c => c.key === 'claude')) {
|
|
52
|
-
const result = wireOrientHook({ cwd: root, client, python, docsLabel: 'ai_docs' });
|
|
53
|
-
console.log(`Claude SessionStart orientation: ${result.code}`);
|
|
54
|
-
}
|
|
55
|
-
if (python) {
|
|
56
|
-
try {
|
|
57
|
-
execFileSync(python, [path.join(SKILL_SOURCE, 'scripts/sdlc_check.py'),
|
|
58
|
-
'index', '--root', root], { stdio: 'ignore' });
|
|
59
|
-
console.log('Generated ai_docs indexes.');
|
|
60
|
-
} catch (e) { console.log('Run sdlc_check.py index when Python is available.'); }
|
|
61
|
-
}
|
|
62
|
-
console.log('Course Creator initialization complete.');
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
const { execFileSync } = require('child_process');
|
|
6
|
+
const { SKILL_SOURCE, INSTALLED_SKILL_NAME, SELF_LENS, SIBLING_LENSES,
|
|
7
|
+
CLIENTS, clientDetected, skillTarget, wireOrientHook, detectPython } = require('./lib');
|
|
8
|
+
|
|
9
|
+
const root = process.cwd();
|
|
10
|
+
const seed = new Map([
|
|
11
|
+
['ai_docs/README.md', `---\ndefault_domain: course\n---\n# Project knowledge and course work\n\nRead vision/project_vision.md, INDEX.md and reference/INDEX.md first.\n`],
|
|
12
|
+
['ai_docs/vision/project_vision.md', `---\ndescription: Project benefit and boundaries.\nstatus: DRAFT\n---\n# Project Vision\nStatus: DRAFT\n<!-- STUB: awaiting the owner interview (vision.md §The owner interview). Not a\n Vision: no artifact traces to it. -->\n`],
|
|
13
|
+
['ai_docs/vision/roadmap.md', `---\ndescription: Project learning and delivery roadmap.\nstatus: DRAFT\n---\n# Roadmap\nStatus: DRAFT\n`],
|
|
14
|
+
['ai_docs/vision/principles.md', `---\ndescription: Decisions that constrain project work.\nstatus: DRAFT\n---\n# Principles\nStatus: DRAFT\n`],
|
|
15
|
+
['ai_docs/strategic/architecture.md', `---\ndescription: Verified component inventory.\nstatus: CURRENT\n---\n# Architecture\n\n## Component Map\n| Component | Capability it owns | Contract | Where |\n|---|---|---|---|\n\n## Architectural Patterns\n<Add only verified patterns.>\n`],
|
|
16
|
+
['ai_docs/strategic/existing_features.md', `---\ndescription: Existing capabilities verified in this project.\nstatus: CURRENT\n---\n# Existing Features\n`],
|
|
17
|
+
['ai_docs/audit/audit_plan.md', `# Audit Plan\n\n| Area | State | Reference | Notes |\n|---|---|---|---|\n| . | PENDING | - | Initial analysis |\n`],
|
|
18
|
+
]);
|
|
19
|
+
function writeIfAbsent(rel, content) {
|
|
20
|
+
const dest = path.join(root, rel);
|
|
21
|
+
if (fs.existsSync(dest)) return false;
|
|
22
|
+
fs.mkdirSync(path.dirname(dest), { recursive: true });
|
|
23
|
+
fs.writeFileSync(dest, content, 'utf8');
|
|
24
|
+
console.log(`Created ${rel}`);
|
|
25
|
+
return true;
|
|
26
|
+
}
|
|
27
|
+
for (const [rel, content] of seed) writeIfAbsent(rel, content);
|
|
28
|
+
for (const dir of ['ai_docs/vision/features', 'ai_docs/reference', 'ai_docs/solutions/courses',
|
|
29
|
+
'ai_docs/audit/reviews']) fs.mkdirSync(path.join(root, dir), { recursive: true });
|
|
30
|
+
|
|
31
|
+
const pointer = `# Course Creator project protocol\n\nUse the installed \`course-creator\` skill when the deliverable teaches a defined learner. It applies Agentic SDLC in Standalone mode: triage, project and course Vision, ANALYSIS, progressive explanations, sourced facts, independent diagnostic testing and a truthful efficacy label. Course files live in \`ai_docs/solutions/courses/<slug>/\`. Read \`ai_docs/README.md\` and the skill before working.\n\nInstall if missing: \`npm i -g @antoneeo/course-creator@beta && course-creator-install-skill\`.\n`;
|
|
32
|
+
const pointers = { claude: 'CLAUDE.md', gemini: 'GEMINI.md', codex: 'AGENTS.md',
|
|
33
|
+
antigravity: 'AGENTS.md' };
|
|
34
|
+
const detected = CLIENTS.filter(clientDetected);
|
|
35
|
+
for (const client of detected) writeIfAbsent(pointers[client.key], pointer);
|
|
36
|
+
writeIfAbsent('.cursorrules', pointer);
|
|
37
|
+
const siblings = new Map();
|
|
38
|
+
for (const client of detected) {
|
|
39
|
+
const parent = path.dirname(skillTarget(client));
|
|
40
|
+
for (const [name, lens] of Object.entries(SIBLING_LENSES)) {
|
|
41
|
+
if (fs.existsSync(path.join(parent, name))) siblings.set(name, lens);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
if (siblings.size) {
|
|
45
|
+
const rows = [`- \`${INSTALLED_SKILL_NAME}\` — the **${SELF_LENS}** lens`,
|
|
46
|
+
...[...siblings].map(([name, lens]) => `- \`${name}\` — the **${lens}** lens`)];
|
|
47
|
+
writeIfAbsent('AGENTIC_MULTI_LENS.md',
|
|
48
|
+
`# Agentic SDLC family routing\n\nRead \`routing.md\` to choose the owner of each deliverable; \`memory.md\` is shared.\n\n${rows.join('\n')}\n`);
|
|
49
|
+
}
|
|
50
|
+
const python = detectPython();
|
|
51
|
+
for (const client of detected.filter(c => c.key === 'claude')) {
|
|
52
|
+
const result = wireOrientHook({ cwd: root, client, python, docsLabel: 'ai_docs' });
|
|
53
|
+
console.log(`Claude SessionStart orientation: ${result.code}`);
|
|
54
|
+
}
|
|
55
|
+
if (python) {
|
|
56
|
+
try {
|
|
57
|
+
execFileSync(python, [path.join(SKILL_SOURCE, 'scripts/sdlc_check.py'),
|
|
58
|
+
'index', '--root', root], { stdio: 'ignore' });
|
|
59
|
+
console.log('Generated ai_docs indexes.');
|
|
60
|
+
} catch (e) { console.log('Run sdlc_check.py index when Python is available.'); }
|
|
61
|
+
}
|
|
62
|
+
console.log('Course Creator initialization complete.');
|
package/scripts/lib.js
CHANGED
|
@@ -3,8 +3,8 @@
|
|
|
3
3
|
|
|
4
4
|
const fs = require('fs');
|
|
5
5
|
const path = require('path');
|
|
6
|
-
const os = require('os');
|
|
7
|
-
const crypto = require('crypto');
|
|
6
|
+
const os = require('os');
|
|
7
|
+
const crypto = require('crypto');
|
|
8
8
|
const { execSync } = require('child_process');
|
|
9
9
|
|
|
10
10
|
const PACKAGE_ROOT = path.resolve(__dirname, '..');
|
|
@@ -449,104 +449,104 @@ function pathsEqual(a, b) {
|
|
|
449
449
|
return false;
|
|
450
450
|
}
|
|
451
451
|
}
|
|
452
|
-
function pathInside(child, parent) {
|
|
452
|
+
function pathInside(child, parent) {
|
|
453
453
|
try {
|
|
454
454
|
const rel = path.relative(String(parent), String(child));
|
|
455
455
|
return rel !== '' && !rel.startsWith('..') && !path.isAbsolute(rel);
|
|
456
456
|
} catch (e) {
|
|
457
457
|
return false;
|
|
458
458
|
}
|
|
459
|
-
}
|
|
460
|
-
|
|
461
|
-
// Ownership is per file. An existing directory without our marker belongs to its
|
|
462
|
-
// owner, and a file edited after installation is retained on uninstall.
|
|
463
|
-
const INSTALL_MARKER = '.course-creator-install.json';
|
|
464
|
-
function hashFile(file) {
|
|
465
|
-
return crypto.createHash('sha256').update(fs.readFileSync(file)).digest('hex');
|
|
466
|
-
}
|
|
467
|
-
function sourceFiles(dir = SKILL_SOURCE, prefix = '') {
|
|
468
|
-
const found = [];
|
|
469
|
-
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
|
|
470
|
-
if (entry.isSymbolicLink()) continue;
|
|
471
|
-
const rel = path.join(prefix, entry.name);
|
|
472
|
-
if (entry.isDirectory()) found.push(...sourceFiles(path.join(dir, entry.name), rel));
|
|
473
|
-
else if (entry.isFile()) found.push(rel);
|
|
474
|
-
}
|
|
475
|
-
return found;
|
|
476
|
-
}
|
|
477
|
-
function safeOwnedPath(target, rel) {
|
|
478
|
-
if (path.isAbsolute(rel) || rel.split(/[\\/]/).includes('..')) return null;
|
|
479
|
-
const full = path.resolve(target, rel);
|
|
480
|
-
if (!pathInside(full, target)) return null;
|
|
481
|
-
let cur = path.resolve(target);
|
|
482
|
-
for (const part of path.relative(cur, full).split(path.sep)) {
|
|
483
|
-
cur = path.join(cur, part);
|
|
484
|
-
if (fs.existsSync(cur) && fs.lstatSync(cur).isSymbolicLink()) return null;
|
|
485
|
-
}
|
|
486
|
-
return full;
|
|
487
|
-
}
|
|
488
|
-
function ownedMarker(target) {
|
|
489
|
-
const marker = path.join(target, INSTALL_MARKER);
|
|
490
|
-
if (!fs.existsSync(marker)) return null;
|
|
491
|
-
try {
|
|
492
|
-
if (!fs.lstatSync(marker).isFile() || fs.lstatSync(marker).isSymbolicLink()) return null;
|
|
493
|
-
const data = JSON.parse(fs.readFileSync(marker, 'utf8'));
|
|
494
|
-
return data.skill === INSTALLED_SKILL_NAME && data.files &&
|
|
495
|
-
typeof data.files === 'object' ? data : null;
|
|
496
|
-
} catch (e) { return null; }
|
|
497
|
-
}
|
|
498
|
-
function installOwned(target) {
|
|
499
|
-
if (!fs.existsSync(SKILL_SOURCE)) return { code: 'missing-source' };
|
|
500
|
-
if (fs.existsSync(target) && (!fs.lstatSync(target).isDirectory() ||
|
|
501
|
-
fs.lstatSync(target).isSymbolicLink())) return { code: 'unowned' };
|
|
502
|
-
const existing = fs.existsSync(target) ? fs.readdirSync(target) : [];
|
|
503
|
-
const prior = ownedMarker(target);
|
|
504
|
-
if (existing.length && !prior) return { code: 'unowned' };
|
|
505
|
-
fs.mkdirSync(target, { recursive: true });
|
|
506
|
-
const owned = { ...(prior ? prior.files : {}) };
|
|
507
|
-
let copied = 0, preserved = 0;
|
|
508
|
-
for (const rel of sourceFiles()) {
|
|
509
|
-
const dest = safeOwnedPath(target, rel);
|
|
510
|
-
if (!dest) { preserved++; continue; }
|
|
511
|
-
if (fs.existsSync(dest)) {
|
|
512
|
-
if (!Object.hasOwn(owned, rel) || hashFile(dest) !== owned[rel]) {
|
|
513
|
-
preserved++; continue;
|
|
514
|
-
}
|
|
515
|
-
}
|
|
516
|
-
fs.mkdirSync(path.dirname(dest), { recursive: true });
|
|
517
|
-
fs.copyFileSync(path.join(SKILL_SOURCE, rel), dest);
|
|
518
|
-
owned[rel] = hashFile(dest);
|
|
519
|
-
copied++;
|
|
520
|
-
}
|
|
521
|
-
fs.writeFileSync(path.join(target, INSTALL_MARKER),
|
|
522
|
-
JSON.stringify({ skill: INSTALLED_SKILL_NAME, files: owned }, null, 2) + '\n');
|
|
523
|
-
return { code: 'installed', copied, preserved };
|
|
524
|
-
}
|
|
525
|
-
function uninstallOwned(target) {
|
|
526
|
-
if (!fs.existsSync(target) || fs.lstatSync(target).isSymbolicLink()) {
|
|
527
|
-
return { code: 'unowned' };
|
|
528
|
-
}
|
|
529
|
-
const prior = ownedMarker(target);
|
|
530
|
-
if (!prior) return { code: 'unowned' };
|
|
531
|
-
let removed = 0, preserved = 0;
|
|
532
|
-
for (const [rel, hash] of Object.entries(prior.files)) {
|
|
533
|
-
const dest = safeOwnedPath(target, rel);
|
|
534
|
-
if (!dest || !fs.existsSync(dest) || !fs.lstatSync(dest).isFile() ||
|
|
535
|
-
hashFile(dest) !== hash) { preserved++; continue; }
|
|
536
|
-
fs.unlinkSync(dest);
|
|
537
|
-
removed++;
|
|
538
|
-
}
|
|
539
|
-
fs.unlinkSync(path.join(target, INSTALL_MARKER));
|
|
540
|
-
// Remove only empty directories; a user-owned file keeps its parent.
|
|
541
|
-
const dirs = new Set(Object.keys(prior.files).map(rel => path.dirname(rel)));
|
|
542
|
-
for (const rel of [...dirs].sort((a, b) => b.length - a.length)) {
|
|
543
|
-
const dir = path.resolve(target, rel);
|
|
544
|
-
if (!pathInside(dir, target)) continue;
|
|
545
|
-
try { fs.rmdirSync(dir); } catch (e) { /* non-empty or absent */ }
|
|
546
|
-
}
|
|
547
|
-
try { fs.rmdirSync(target); } catch (e) { /* user files remain */ }
|
|
548
|
-
return { code: 'removed', removed, preserved };
|
|
549
|
-
}
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
// Ownership is per file. An existing directory without our marker belongs to its
|
|
462
|
+
// owner, and a file edited after installation is retained on uninstall.
|
|
463
|
+
const INSTALL_MARKER = '.course-creator-install.json';
|
|
464
|
+
function hashFile(file) {
|
|
465
|
+
return crypto.createHash('sha256').update(fs.readFileSync(file)).digest('hex');
|
|
466
|
+
}
|
|
467
|
+
function sourceFiles(dir = SKILL_SOURCE, prefix = '') {
|
|
468
|
+
const found = [];
|
|
469
|
+
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
|
|
470
|
+
if (entry.isSymbolicLink()) continue;
|
|
471
|
+
const rel = path.join(prefix, entry.name);
|
|
472
|
+
if (entry.isDirectory()) found.push(...sourceFiles(path.join(dir, entry.name), rel));
|
|
473
|
+
else if (entry.isFile()) found.push(rel);
|
|
474
|
+
}
|
|
475
|
+
return found;
|
|
476
|
+
}
|
|
477
|
+
function safeOwnedPath(target, rel) {
|
|
478
|
+
if (path.isAbsolute(rel) || rel.split(/[\\/]/).includes('..')) return null;
|
|
479
|
+
const full = path.resolve(target, rel);
|
|
480
|
+
if (!pathInside(full, target)) return null;
|
|
481
|
+
let cur = path.resolve(target);
|
|
482
|
+
for (const part of path.relative(cur, full).split(path.sep)) {
|
|
483
|
+
cur = path.join(cur, part);
|
|
484
|
+
if (fs.existsSync(cur) && fs.lstatSync(cur).isSymbolicLink()) return null;
|
|
485
|
+
}
|
|
486
|
+
return full;
|
|
487
|
+
}
|
|
488
|
+
function ownedMarker(target) {
|
|
489
|
+
const marker = path.join(target, INSTALL_MARKER);
|
|
490
|
+
if (!fs.existsSync(marker)) return null;
|
|
491
|
+
try {
|
|
492
|
+
if (!fs.lstatSync(marker).isFile() || fs.lstatSync(marker).isSymbolicLink()) return null;
|
|
493
|
+
const data = JSON.parse(fs.readFileSync(marker, 'utf8'));
|
|
494
|
+
return data.skill === INSTALLED_SKILL_NAME && data.files &&
|
|
495
|
+
typeof data.files === 'object' ? data : null;
|
|
496
|
+
} catch (e) { return null; }
|
|
497
|
+
}
|
|
498
|
+
function installOwned(target) {
|
|
499
|
+
if (!fs.existsSync(SKILL_SOURCE)) return { code: 'missing-source' };
|
|
500
|
+
if (fs.existsSync(target) && (!fs.lstatSync(target).isDirectory() ||
|
|
501
|
+
fs.lstatSync(target).isSymbolicLink())) return { code: 'unowned' };
|
|
502
|
+
const existing = fs.existsSync(target) ? fs.readdirSync(target) : [];
|
|
503
|
+
const prior = ownedMarker(target);
|
|
504
|
+
if (existing.length && !prior) return { code: 'unowned' };
|
|
505
|
+
fs.mkdirSync(target, { recursive: true });
|
|
506
|
+
const owned = { ...(prior ? prior.files : {}) };
|
|
507
|
+
let copied = 0, preserved = 0;
|
|
508
|
+
for (const rel of sourceFiles()) {
|
|
509
|
+
const dest = safeOwnedPath(target, rel);
|
|
510
|
+
if (!dest) { preserved++; continue; }
|
|
511
|
+
if (fs.existsSync(dest)) {
|
|
512
|
+
if (!Object.hasOwn(owned, rel) || hashFile(dest) !== owned[rel]) {
|
|
513
|
+
preserved++; continue;
|
|
514
|
+
}
|
|
515
|
+
}
|
|
516
|
+
fs.mkdirSync(path.dirname(dest), { recursive: true });
|
|
517
|
+
fs.copyFileSync(path.join(SKILL_SOURCE, rel), dest);
|
|
518
|
+
owned[rel] = hashFile(dest);
|
|
519
|
+
copied++;
|
|
520
|
+
}
|
|
521
|
+
fs.writeFileSync(path.join(target, INSTALL_MARKER),
|
|
522
|
+
JSON.stringify({ skill: INSTALLED_SKILL_NAME, files: owned }, null, 2) + '\n');
|
|
523
|
+
return { code: 'installed', copied, preserved };
|
|
524
|
+
}
|
|
525
|
+
function uninstallOwned(target) {
|
|
526
|
+
if (!fs.existsSync(target) || fs.lstatSync(target).isSymbolicLink()) {
|
|
527
|
+
return { code: 'unowned' };
|
|
528
|
+
}
|
|
529
|
+
const prior = ownedMarker(target);
|
|
530
|
+
if (!prior) return { code: 'unowned' };
|
|
531
|
+
let removed = 0, preserved = 0;
|
|
532
|
+
for (const [rel, hash] of Object.entries(prior.files)) {
|
|
533
|
+
const dest = safeOwnedPath(target, rel);
|
|
534
|
+
if (!dest || !fs.existsSync(dest) || !fs.lstatSync(dest).isFile() ||
|
|
535
|
+
hashFile(dest) !== hash) { preserved++; continue; }
|
|
536
|
+
fs.unlinkSync(dest);
|
|
537
|
+
removed++;
|
|
538
|
+
}
|
|
539
|
+
fs.unlinkSync(path.join(target, INSTALL_MARKER));
|
|
540
|
+
// Remove only empty directories; a user-owned file keeps its parent.
|
|
541
|
+
const dirs = new Set(Object.keys(prior.files).map(rel => path.dirname(rel)));
|
|
542
|
+
for (const rel of [...dirs].sort((a, b) => b.length - a.length)) {
|
|
543
|
+
const dir = path.resolve(target, rel);
|
|
544
|
+
if (!pathInside(dir, target)) continue;
|
|
545
|
+
try { fs.rmdirSync(dir); } catch (e) { /* non-empty or absent */ }
|
|
546
|
+
}
|
|
547
|
+
try { fs.rmdirSync(target); } catch (e) { /* user files remain */ }
|
|
548
|
+
return { code: 'removed', removed, preserved };
|
|
549
|
+
}
|
|
550
550
|
|
|
551
551
|
// The marker lives under the family's agent-global root (the same root the
|
|
552
552
|
// validator's AGENTIC_SDLC_KB_ROOT seam names), read at call time so the test
|
|
@@ -720,10 +720,10 @@ module.exports = {
|
|
|
720
720
|
detectPython,
|
|
721
721
|
pathsEqual,
|
|
722
722
|
orientHookCommand,
|
|
723
|
-
copyRecursive,
|
|
724
|
-
installOwned,
|
|
725
|
-
uninstallOwned,
|
|
726
|
-
ownedMarker,
|
|
723
|
+
copyRecursive,
|
|
724
|
+
installOwned,
|
|
725
|
+
uninstallOwned,
|
|
726
|
+
ownedMarker,
|
|
727
727
|
loadTemplates,
|
|
728
728
|
templateFor,
|
|
729
729
|
};
|
package/scripts/postinstall.js
CHANGED
|
@@ -1,16 +1,16 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
"use strict";
|
|
3
|
-
const { CLIENTS, clientDetected, skillTarget, installOwned,
|
|
4
|
-
wireGlobalOrientHook, detectPython } = require('./lib');
|
|
5
|
-
|
|
6
|
-
for (const client of CLIENTS) {
|
|
7
|
-
if (!clientDetected(client)) continue;
|
|
8
|
-
const target = skillTarget(client);
|
|
9
|
-
const result = installOwned(target);
|
|
10
|
-
console.log(`${client.label}: ${result.code} (${target})`);
|
|
11
|
-
if (client.key === 'claude' && result.code === 'installed') {
|
|
12
|
-
try { wireGlobalOrientHook({ client, python: detectPython() }); }
|
|
13
|
-
catch (e) { console.log('Session orientation can be wired from ENFORCEMENT.md.'); }
|
|
14
|
-
}
|
|
15
|
-
}
|
|
16
|
-
console.log('Initialize a project with course-creator-init.');
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
const { CLIENTS, clientDetected, skillTarget, installOwned,
|
|
4
|
+
wireGlobalOrientHook, detectPython } = require('./lib');
|
|
5
|
+
|
|
6
|
+
for (const client of CLIENTS) {
|
|
7
|
+
if (!clientDetected(client)) continue;
|
|
8
|
+
const target = skillTarget(client);
|
|
9
|
+
const result = installOwned(target);
|
|
10
|
+
console.log(`${client.label}: ${result.code} (${target})`);
|
|
11
|
+
if (client.key === 'claude' && result.code === 'installed') {
|
|
12
|
+
try { wireGlobalOrientHook({ client, python: detectPython() }); }
|
|
13
|
+
catch (e) { console.log('Session orientation can be wired from ENFORCEMENT.md.'); }
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
console.log('Initialize a project with course-creator-init.');
|
package/scripts/preuninstall.js
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
"use strict";
|
|
3
|
-
const { CLIENTS, skillTarget, ownedMarker, uninstallOwned,
|
|
4
|
-
removeGlobalOrientHooks } = require('./lib');
|
|
5
|
-
|
|
6
|
-
const ownedTargets = CLIENTS.map(skillTarget).filter(ownedMarker);
|
|
7
|
-
try { removeGlobalOrientHooks(ownedTargets); } catch (e) { /* convenience only */ }
|
|
8
|
-
for (const target of ownedTargets) {
|
|
9
|
-
const result = uninstallOwned(target);
|
|
10
|
-
console.log(`${result.code}: ${target} (${result.removed} owned files removed; ` +
|
|
11
|
-
`${result.preserved} retained)`);
|
|
12
|
-
}
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
const { CLIENTS, skillTarget, ownedMarker, uninstallOwned,
|
|
4
|
+
removeGlobalOrientHooks } = require('./lib');
|
|
5
|
+
|
|
6
|
+
const ownedTargets = CLIENTS.map(skillTarget).filter(ownedMarker);
|
|
7
|
+
try { removeGlobalOrientHooks(ownedTargets); } catch (e) { /* convenience only */ }
|
|
8
|
+
for (const target of ownedTargets) {
|
|
9
|
+
const result = uninstallOwned(target);
|
|
10
|
+
console.log(`${result.code}: ${target} (${result.removed} owned files removed; ` +
|
|
11
|
+
`${result.preserved} retained)`);
|
|
12
|
+
}
|