Evidence-backed application prep for academic and professional jobs.
Project description
这也能投/CanISend
Evidence-backed application prep for academic and professional jobs. CLI/package name: canisend.
这也能投是一款 local-first CLI:从职位广告和本地私人履历证据出发,生成可检查的申请材料包,包括 parsed criteria、fit report、cover letter draft、CV tailoring notes、material checklist 和 Typst-ready source。强主张应当能追溯到本地证据。
It prepares materials only. It does not submit applications, create accounts, fill portals, crawl job sites, upload packages, or answer sensitive declarations. A user may explicitly import one supplied advert URL; CanISend does not run background page discovery or broad scraping.
What It Does
- Creates a private workspace for profile evidence, job folders, prompts, Typst templates, schemas, and agent instructions.
- Imports and filters jobs.ac.uk RSS plus generic RSS/Atom leads without crawling full job pages.
- Creates one local folder per application, with the full advert kept as a manual input.
- Extracts normalized evidence from Typst-first profile sources into
profile/generated/. - Materializes a private, run-scoped Evidence snapshot and durable
evidence_catalog.jsonwithout exposing evidence bodies in workflow control records. - Produces deterministic, reviewable
criterion_matches.jsonproposals from current Criteria and Evidence catalogs. - Preserves user-owned criteria confirmations/corrections and apply/hold/skip decisions through explicit-consent, revision/hash compare-and-swap operations with privacy-safe receipts.
- Provides the locally accepted Stage 2 Decision Spine: guarded user-owned corrections, Decision and Brief YAML, deterministic Criteria/Evidence/Match/required-document projections, and executable unresolved/omit/orphan blockers.
- Provides two Stage 3 guarded executors: host-agent or configured-provider Cover Letter plus host-agent Research Statement, each with an evidence-bound structured Draft, independent deterministic Review, user-owned dispositions, derived per-document readiness, and unique outputs.
- Adds deterministic aggregate
package_reviewover the exact current document plan and document receipts. Missing required documents and provable Evidence-receipt conflicts are blockers; tone and narrative alignment remain explicit human-review work, and the stage never rewrites a Draft. - Preserves package finding decisions in an independent user-owned CAS file and derives body-free application-package
readiness from exact required-document, aggregate Review, and decision receipts.
check-packagerederives APP-Q5; this review boundary is not rendering approval or submission evidence. - Derives a body-free required-document execution fan-out, so guarded Cover Letter/Research Statement work, confirmed omissions, unresolved tasks, and document kinds without implemented executors remain distinguishable across agent hosts.
- Renders current deterministic Match proposals and a current blocker-free structured Cover Letter into compatible views, plus an exact reviewed Research Statement into standalone views, while safely closing stale, drifted, blocked, mixed-profile, direct-library, or explicit LLM-draft paths. Document readiness never implies package/submission readiness.
- Generates
parsed_job.json, preparation questions, fit reports, cover letter drafts, CV tailoring notes, criteria checklists, material review checklists, and structured Typst content. - Runs deterministic local generation by default, with explicit opt-in for LLM-backed evidence augmentation, parsing, or drafting.
- Ships bridge files plus a self-contained workspace skill pack for Codex, Claude Code, and IDE agents.
Quick Start
Choose one installation method. CanISend requires Python 3.11 or newer.
Install as an isolated CLI with uv
Use this after the production release is available on PyPI:
uv tool install canisend
Install as an isolated CLI with pipx
If you prefer the pip ecosystem but still want an isolated command-line tool:
pipx install canisend
Install with pip in a virtual environment
Use this when you want CanISend available inside a project-specific Python environment:
python -m venv .venv
source .venv/bin/activate
python -m pip install --upgrade pip
python -m pip install canisend
Install the current TestPyPI build
For the current TestPyPI build, install canisend==0.3.0b1 while still resolving dependencies from PyPI:
uv tool install \
--index-url https://test.pypi.org/simple/ \
--extra-index-url https://pypi.org/simple/ \
canisend==0.3.0b1
Or with pip inside an active virtual environment:
python -m pip install \
--index-url https://test.pypi.org/simple/ \
--extra-index-url https://pypi.org/simple/ \
canisend==0.3.0b1
Run the packaged fake-data workflow before using private profile or job data:
canisend run-example --workspace /tmp/canisend-example --overwrite
Inspect the generated dossier:
/tmp/canisend-example/jobs/2026-06-15_example-university_lecturer-in-applied-economics/
parsed_job.json
00_preparation_questions.md
02_fit_report.md
03_cover_letter_draft.md
05_criteria_checklist.md
07_material_review_checklist.md
typst/
From a development checkout, prefix CLI commands with uv run:
uv run canisend --help
uv run canisend run-example --workspace /tmp/canisend-example --overwrite
Core Workflow
private profile -> generated evidence -> private run snapshot -> Evidence catalog
| |
full advert -> Parsed Job -> stable Criteria ----------------+ v
proposed criterion matches
|
v
user-owned application Decision
|
v
user-owned Brief -> required-document plan
|
v
guarded document-scoped structured Draft
(Cover Letter or Research Statement)
|
v
deterministic independent Review
1. Initialize a private workspace
Normal users should install the CLI and keep private application data in a separate workspace. They do not need to fork this repository.
canisend init-workspace --workspace ~/CanISendWorkspace
canisend doctor --workspace ~/CanISendWorkspace
The workspace contains private profile data, job leads, job folders, editable prompt copies, Typst templates, schemas, examples, and agent-readable skills:
~/CanISendWorkspace/
canisend.yaml
.env.example
.gitignore
AGENTS.md
CLAUDE.md
profile/
jobs/
job_leads/
prompts/
templates/
schemas/
agent-skills/
canisend/
canisend-job-intake/
canisend-application-package/
canisend-submission-readiness/
After upgrades:
uv tool upgrade canisend
canisend update-workspace --workspace ~/CanISendWorkspace
canisend doctor --workspace ~/CanISendWorkspace
update-workspace preserves local prompt, template, and skill edits by default. Use --overwrite only when packaged defaults should replace local copies.
If doctor reports deprecated packaged files from an older release, remove only those retired defaults with:
canisend update-workspace --workspace ~/CanISendWorkspace --prune-deprecated
doctor also reports local default resources that differ from the packaged version. Use update-workspace --overwrite only after deciding those local prompt, template, schema, or bridge edits should be replaced.
2. Prepare profile evidence
Put your real modernpro CV and statements under ~/CanISendWorkspace/profile/typst/. These files stay local, and profile/ is ignored by git except for .gitkeep.
Create starter profile files if needed:
canisend init-profile --workspace ~/CanISendWorkspace --mode typst
Generate normalized evidence:
canisend extract-profile-evidence --workspace ~/CanISendWorkspace
When local Typst extraction misses evidence, you can explicitly opt into provider-backed augmentation:
canisend extract-profile-evidence \
--workspace ~/CanISendWorkspace \
--llm-augment
The profile manifest lives at profile/profile.yaml. Generated evidence is written to profile/generated/ and cited with item-level references such as:
profile/generated/cv.evidence.md#Teaching/cv-001
Review item-level evidence citations before trusting any generated claim.
Current Typst extraction also writes a source-hash receipt into each generated evidence file. If a file was produced
by an older CanISend version and the resumable Evidence stage reports evidence.source_receipt_missing, rerun
extract-profile-evidence; if the underlying Typst source has changed, re-extraction also resolves
evidence.source_receipt_stale. Do not hand-edit the receipt to make stale evidence appear current.
3. Import leads and create one job folder
Fetch jobs.ac.uk RSS leads locally:
canisend fetch-jobs-ac-uk \
--workspace ~/CanISendWorkspace \
--feed-url "<jobs.ac.uk RSS url>" \
--include economics \
--exclude phd
Any stable RSS or Atom source can use the generic entrypoint:
canisend fetch-job-feed \
--workspace ~/CanISendWorkspace \
--source-name "Example University" \
--feed-url "https://example.edu/jobs.atom" \
--include economics \
--exclude phd
Generic feeds default to job_leads/<source-name>.json. They use the same normalized lead fields and local filters as
jobs.ac.uk. Feed records are discovery leads, not full adverts.
Create a job folder from a selected zero-based lead index:
canisend new-job-from-lead \
--workspace ~/CanISendWorkspace \
--lead-index 0 \
--institution "University X" \
--deadline "2026-06-15"
For a generic feed output, pass its lead file explicitly:
canisend new-job-from-lead \
--workspace ~/CanISendWorkspace \
--leads-file job_leads/example-university.json \
--lead-index 0 \
--institution "Example University"
You can also create a job manually:
canisend new-job \
--workspace ~/CanISendWorkspace \
--title "Lecturer in Economics" \
--institution "University X" \
--deadline "2026-06-15" \
--english-variant "UK English" \
--writing-style "direct, warm, evidence-led" \
--source-url "https://www.jobs.ac.uk/job/example"
If these preferences are unknown, leave them unset. CanISend will mark them as needs_confirmation and include language/style questions in 00_preparation_questions.md.
Paste the full advert, or import it from a supported local file, into jobs/<job-slug>/job_advert.md before relying on parsed criteria or generated
drafts. A user-provided PDF is a first-class intake path:
canisend new-job \
--workspace ~/CanISendWorkspace \
--title "Lecturer in Economics" \
--institution "University X" \
--advert-file ~/Downloads/lecturer-job.pdf
--source-url records metadata only. When the user explicitly wants one supplied URL imported, add --fetch-url;
the response may be HTML or PDF. This bounded one-URL import is different from crawling or searching a site:
canisend new-job \
--workspace ~/CanISendWorkspace \
--title "Lecturer in Economics" \
--institution "University X" \
--source-url "https://example.edu/jobs/123" \
--fetch-url
4. Generate draft materials
Run the deterministic local pipeline:
canisend run \
--workspace ~/CanISendWorkspace \
--job jobs/<job-slug>
Generated output includes:
jobs/<job-slug>/
parsed_job.json
00_preparation_questions.md
01_job_summary.md
02_fit_report.md
03_cover_letter_draft.md
04_cv_tailoring_notes.md
05_criteria_checklist.md
06_final_application_package.md
07_material_review_checklist.md
08_research_statement.md # conditional: current reviewed Research Statement
typst/
cover_letter.typ
application_package.typ
research_statement.typ # conditional standalone document
Generated Typst files are the editable source of truth for final formatting. Content JSON files may still be emitted as compatibility/debug artifacts, but normal edits should happen in the .typ files.
The Research Statement files appear only after its exact current Draft, deterministic blocker-free Review, and
user dispositions derive document readiness reviewed. They remain standalone: they are not embedded in the
application-package source and do not change the existing package gate.
CanISend records hashes of its generated Typst baseline. On a later run, an unchanged source updates normally. If a
source has been edited, the user version is preserved and the new generation is written as *.generated.typ for
review instead of silently overwriting the edit. Pending Cover Letter or application-package candidates block
check-package; every pending candidate blocks render-typst, and run --git-add-materials skips staging so
Markdown and Typst cannot be recorded as a mismatched set. A standalone Research Statement candidate stays outside
the package gate but must still be reconciled before Typst rendering.
To track edits to generated application materials in a private git repository, opt in after generation:
canisend run \
--workspace ~/CanISendWorkspace \
--job jobs/<job-slug> \
--git-add-materials
Without the flag, interactive terminals are asked whether to add generated application materials to git; non-interactive runs skip git staging unless the flag is set. CanISend stages only the generated preparation questions, fit report, cover letter draft, CV tailoring notes, criteria checklist, final package, material review checklist, an eligible standalone Research Statement, and editable Typst sources. It does not stage raw adverts, source URLs, parsed job JSON, compatibility JSON, PDFs, or profile files, and it never commits automatically.
LLM-backed evidence augmentation, parser, and draft generation are explicit opt-in modes. Configure a provider before using them:
ACADEMIC_PREP_LLM_PROVIDER=openai-compatible
OPENAI_API_KEY=...
OPENAI_BASE_URL=https://api.openai.com/v1
OPENAI_MODEL=...
canisend extract-profile-evidence \
--workspace ~/CanISendWorkspace \
--llm-augment
canisend run \
--workspace ~/CanISendWorkspace \
--job jobs/<job-slug> \
--llm-parser \
--llm-drafts
For local CLI model access, use the command provider:
ACADEMIC_PREP_LLM_PROVIDER=command
ACADEMIC_PREP_LLM_COMMAND="codex exec --json"
ACADEMIC_PREP_LLM_TIMEOUT_SECONDS=300
The LLM-backed evidence augmenter only accepts items tied to a supporting source_text found in the local profile source. The LLM-backed parser must return JSON matching the parsed_job.json contract. Draft outputs must cite profile evidence; unknown citations fail validation. Missing evidence should be marked as a gap, not replaced with unsupported claims.
5. Review, render, and submit manually
Review files in this order:
parsed_job.jsoncriteria.json- Evidence state and receipts in
evidence_catalog.json(its body is private profile data) - Proposed classifications and gaps in
criterion_matches.json 00_preparation_questions.md05_criteria_checklist.md02_fit_report.md03_cover_letter_draft.md04_cv_tailoring_notes.md07_material_review_checklist.md08_research_statement.mdwhen generated from a reviewed Research Statementtypst/cover_letter.typtypst/research_statement.typwhen presenttypst/application_package.typ06_final_application_package.md
Use 00_preparation_questions.md to confirm US English vs UK English, writing style, and the grill-me details that make the application specific. Use 07_material_review_checklist.md to track the cover letter draft, CV tailoring notes, placeholders, item-level evidence citation checks, and next manual actions.
After reviewing the Markdown drafts, directly edit typst/cover_letter.typ and the other applicable .typ sources
for final wording and layout. The files include stable // CANISEND: section ... or Claim markers so agents can make
bounded edits without rewriting the whole Typst source.
Run a read-only package check before treating generated materials as ready for user review:
canisend check-package \
--workspace ~/CanISendWorkspace \
--job jobs/<job-slug>
The check reports incomplete advert or parsed-job inputs, missing or structurally incomplete Typst sources, explicit review blockers, unresolved bracketed placeholders, unknown profile evidence citations, and APP-Q5 aggregate receipt failures. A legacy package remains readable but cannot pass without current aggregate Review and package decisions. The command does not modify files unless an explicit machine-readable gate report is requested:
canisend check-package \
--workspace ~/CanISendWorkspace \
--job jobs/<job-slug> \
--write-report
The report records safe relative input labels and SHA-256 hashes. A later canisend run marks an existing report
STALE, so it cannot be mistaken for a check of regenerated materials.
Render Typst only when needed:
canisend render-typst \
--workspace ~/CanISendWorkspace \
--job jobs/<job-slug>
Rendering requires a local typst binary. Source generation does not. Submit manually through the institution portal outside this tool.
Agent Usage
Codex, Claude Code, and IDE agents can run the canisend workflow by opening the private workspace as the project root. This is an agent-assisted workflow, not a local-only workflow: any file, PDF, webpage, or generated material the agent reads or summarizes may be processed by the agent model provider.
- Codex and AGENTS.md-aware tools should read
AGENTS.md. - Claude Code should read
CLAUDE.md, which importsagent-skills/canisend/SKILL.md. - IDE agents can read
AGENTS.mdand thenagent-skills/canisend/SKILL.md.
Agents should start with:
canisend agent context --workspace . --format json
Inspect the versioned runtime contract before choosing an operation:
canisend agent capabilities --format json
The contract foundation provides JSON output for doctor, new-job, new-job-from-lead, list-jobs, and
check-package. Each
successful invocation writes one canisend.agent/v1 response to stdout. Responses use workspace-relative or opaque
artifact references and hashes rather than private document bodies. A failed package gate remains a successful
operation result (ok: true) but exits non-zero; operational failures return ok: false with a stable error code.
The locally accepted Stage 2 resumable workflow exposes Parse, Confirm, Evidence, Match, guarded user-owned mutation, Brief, document planning, and compatible structured views through the same shell-capable hosts. Start a deterministic fresh session with:
canisend agent context --workspace . --job jobs/<job-slug> --format json
canisend stage status --workspace . --job jobs/<job-slug> --format json
canisend extract-profile-evidence --workspace .
canisend stage run --workspace . --job jobs/<job-slug> --stage evidence --mode deterministic --format json
canisend stage run --workspace . --job jobs/<job-slug> --stage parse --mode deterministic --format json
canisend stage run --workspace . --job jobs/<job-slug> --stage confirm --mode deterministic --format json
canisend corrections status --workspace . --job jobs/<job-slug> --format json
canisend corrections init --workspace . --job jobs/<job-slug> --confirm-user-owned-write --format json
canisend corrections status --workspace . --job jobs/<job-slug> --format json
status is read-only. Record the current Tier 2 artifact sha256 and the
canisend.user_artifact_revision extension. Put exactly one supported operation in a bounded strict YAML or JSON
patch file—do not ask an agent to replace confirmed_corrections.yaml directly:
operation: confirm_criterion
criterion_id: criterion_<32-lowercase-hex>
Accept that one scoped update and immediately refresh Confirm:
canisend corrections update \
--workspace . \
--job jobs/<job-slug> \
--patch-file /safe/scratch/correction-patch.yaml \
--expected-revision <revision-from-status> \
--expected-sha256 <sha256-from-status> \
--confirm-user-owned-write \
--format json
canisend stage run --workspace . --job jobs/<job-slug> --stage confirm --mode deterministic --format json
Every semantic corrections patch requires current Parse and Confirm. Empty initialization has no active correction
and is fingerprint-neutral; each accepted semantic update makes Confirm stale, so rerun Confirm before
corrections status and the next patch. Do not batch corrections against one catalog baseline.
Supported operations are confirm_criterion, correct_criterion, withdraw_criterion, and confirm_empty. An
extraction with no criteria remains unknown until the user explicitly chooses confirm_empty; initializing an empty
corrections record does not mean confirmed_empty.
When Criteria and Evidence are current, propose matches and record the user's separate Decision:
canisend stage run --workspace . --job jobs/<job-slug> --stage match --mode deterministic --format json
canisend decision status --workspace . --job jobs/<job-slug> --format json
canisend decision init --workspace . --job jobs/<job-slug> --confirm-user-owned-write --format json
canisend decision status --workspace . --job jobs/<job-slug> --format json
Create one strict patch, for example:
operation: set_decision
decision: apply
rationale_mode: keep
Apply it with decision update, passing --patch-file, the revision/hash returned by the latest decision status,
and --confirm-user-owned-write. undecided is not an implicit apply, hold, or skip. Match remains advisory and
cannot write this Decision.
If a later Parse, Confirm, Evidence, or Match refresh changes the Decision basis, the accepted value and raw
application_decision.yaml bytes remain present. decision status derives
canisend.decision_basis_status=review_required; inspect the new basis, then use a new set_decision patch (including
the same value when reconfirming it) with the latest revision/hash. Staleness is never written into the user YAML.
If an accepted mutation reports recovery required or a receipt-pending state, recover only its opaque ID:
canisend user-mutation recover \
--workspace . \
--job jobs/<job-slug> \
--mutation-id mutation_<32-lowercase-hex> \
--confirm-user-owned-write \
--format json
Fresh status also detects a process interruption between publishing a complete private target link and removing CanISend's temporary link. It remains read-only and routes the same opaque mutation ID to explicit recovery; ordinary hard links are still rejected.
Brief work starts only from a current confirmed decision=apply. Brief status is body-free; initialization and every
scoped change use the same explicit-consent revision/hash CAS boundary as corrections and Decision:
canisend brief status --workspace . --job jobs/<job-slug> --format json
canisend brief init --workspace . --job jobs/<job-slug> --confirm-user-owned-write --format json
canisend brief status --workspace . --job jobs/<job-slug> --format json
Use brief update with one bounded strict patch file, the latest revision/hash, and
--confirm-user-owned-write; never replace application_brief.yaml directly. Language, writing style, motivation,
emphasis, exclusions, the advert-document requirement-set confirmation, and document choices remain field-level user
decisions. An empty Parsed Job document list is unconfirmed, not confirmed_empty; only an explicit
confirm_document_requirements patch may record confirmed_empty against the current requirements-basis hash.
For a non-empty set, every extracted item must resolve to one complete, positive advert document member. Missing,
ambiguous, conditional, alternative, qualified, truncated, or unreconciled source context remains unconfirmed; its
section/item anchor is part of the basis, so a source move or change requires a new confirmation.
Generate the current core-owned plan deterministically after the Brief exists:
canisend stage run --workspace . --job jobs/<job-slug> --stage brief --mode deterministic --format json
The plan records body-free status counts and blocker codes in AgentResponse. Its Tier 2 body remains ask-first for an
agent because it can contain advert source text and application strategy. An unconfirmed requirement set, unresolved
choice, required + omit, required document without a preparation action, or orphaned old choice blocks later
Draft/Verify work. Stage 2 is locally accepted, but these artifacts alone do not establish Draft,
application-package, or submission readiness.
Derive the current multi-document fan-out without reading document bodies:
canisend documents status --workspace . --job jobs/<job-slug> --format json
The response binds the exact plan hash and reports aggregate ready, partially_dispatchable, blocked, or
no_work state. Cover Letter and Research Statement are currently available guarded executors. Confirmed teaching,
supporting, diversity, publication, CV, and other routes remain explicit executor_unavailable work until their own
structured schemas, validators, and promotion paths are implemented; they are never silently treated as complete.
Draft and Review execution state is owned by the composite (stage, document_id) identity from the current Required
Document Plan. One current dispatchable target is resolved automatically for compatibility. When both Cover Letter
and Research Statement are ready, the host must obtain their stable IDs from the Tier 2 plan after approval and pass
the selected --document-id <document_...> on stage status, prepare, run, or cancel; an omitted ambiguous,
mismatched, malformed, or unsupported target fails before a task is created.
When the plan contains a blocker-free confirmed prepare Cover Letter or Research Statement, a host agent can enter
the guarded structured Draft path without starting a second model call (include --document-id when required):
canisend stage prepare \
--workspace . \
--job jobs/<job-slug> \
--stage draft \
--mode host-agent \
--format json
After the user approves the returned read-private-draft-inputs consent, the agent reads only the TaskSpec-declared
Tier 2 inputs and writes schema-valid candidate JSON to fresh private scratch. It passes that file to stage submit
with the returned --task path, then passes the immutable TaskResult from the response to stage apply; neither the
agent nor a provider writes the run directory, cover_letter_draft.json, or research_statement_draft.json
directly. Research Statement candidates validate against research-statement-draft.schema.json and require
research_overview, research_contributions, and future_agenda sections before Review is blocker-free.
For Cover Letter only, after explicit Tier 3 approval to transmit the seven declared Draft inputs, the same structured path can call the configured provider and complete prepare, candidate submission, validation, and promotion in one command. Research Statement configured-provider execution fails closed as unsupported:
canisend stage run \
--workspace . \
--job jobs/<job-slug> \
--stage draft \
--mode configured-provider \
--allow-provider-backed \
--format json
The consent flag is required on each non-cached invocation. The provider receives only parsed_job.json,
criteria.json, evidence_catalog.json, criterion_matches.json, application_decision.yaml,
application_brief.yaml, and required_document_plan.json. It proposes sections and Claim semantics only; the core
derives identity, current-basis receipts, stable Claim IDs, generation metadata, and review state. Raw provider output
is bounded and is not stored. Failure, invalid output, or input drift promotes nothing, while an already submitted
candidate resumes without another provider call. Legacy canisend run --llm-drafts remains a separate compatibility
path.
Review the promoted Draft independently and deterministically:
canisend stage run \
--workspace . \
--job jobs/<job-slug> \
--stage review \
--mode deterministic \
--format json
When --document-id was supplied for Draft, pass the same ID to Review. Review resolves only the Draft instance for
that document; cache, retry, failure, and recovery records for another document cannot satisfy it.
Unsupported factual claims, document-specific missing sections, and detectable Brief-exclusion conflicts remain blockers. Partial support, semantic support, and non-factual Claim-kind classification remain review work. For either Cover Letter or Research Statement, use body-free status with the same stable document ID, then initialize and disposition one finding at a time with the current revision/hash:
canisend review-dispositions status --workspace . --job jobs/<job-slug> --document-id <document_...> --format json
canisend review-dispositions init --workspace . --job jobs/<job-slug> --document-id <document_...> \
--confirm-user-owned-write --format json
canisend review-dispositions update --workspace . --job jobs/<job-slug> --patch-file <patch.yaml> \
--document-id <document_...> --expected-revision <revision> --expected-sha256 <sha256> \
--confirm-user-owned-write --format json
The option may be omitted only when the current plan has exactly one supported prepared document. Cover Letter keeps
review_dispositions.yaml; Research Statement uses the independent
research_statement_review_dispositions.yaml. accepted is valid only for non-blockers;
revision_required keeps that document in review. A changed Draft/Review preserves the old decisions and requires
reset_for_current_review. When every current non-blocker finding is accepted, that document derives reviewed
while its Draft and Review stay proposed. Cover Letter readiness feeds its compatibility views and existing package
checks. Research Statement readiness may feed only a standalone Markdown/Typst view; it is not embedded in the
application package and does not affect package readiness.
After dispositioning the selected documents, run aggregate consistency Review without selecting a document ID:
canisend stage run \
--workspace . \
--job jobs/<job-slug> \
--stage package_review \
--mode deterministic \
--format json
package_review_findings.json binds the current Parsed Job, Brief, Required Document Plan, derived execution plan,
and every observed Draft/Review/disposition/readiness receipt. The body-free response reports counts and reason
codes. Required omitted, unavailable, missing, stale, blocked, unreviewed, or revision-required documents block the
aggregate result. An exact repeated factual assertion with different Evidence receipts also blocks; semantic
support, proportionality, tone, and narrative alignment are never guessed. Correction proposals target one document
and Claim set and may be applied only through a new guarded Draft candidate. This Review is not package readiness or
submission approval.
Inspect aggregate readiness without exposing finding bodies, then initialize or change the independent user-owned package decisions with explicit consent and the latest revision/hash:
canisend package-review status --workspace . --job jobs/<job-slug> --format json
canisend package-review init --workspace . --job jobs/<job-slug> \
--confirm-user-owned-write --format json
canisend package-review update --workspace . --job jobs/<job-slug> --patch-file <patch.yaml> \
--expected-revision <revision> --expected-sha256 <sha256> \
--confirm-user-owned-write --format json
Use set_package_finding_disposition or clear_package_finding_disposition for one current finding; use
reset_for_current_package_review after the aggregate Review changes. Blockers cannot be accepted. Only complete
current decisions over individually reviewed required documents derive application-package reviewed; that state
still does not mean rendered, submitted, or received.
Evidence and Parse are independent after intake, so their deterministic runs may be ordered either way; Match waits
for current Confirm and Evidence outputs. Host-agent execution applies to Parse and Draft, while configured-provider
execution currently applies only to Draft; Evidence, Confirm, Match, Brief, Review, and Package Review are
deterministic-only. For
current-host Parse or Draft reasoning,
stage prepare --mode host-agent writes a TaskSpec plus an immutable preparation receipt under the job's
workflow/runs/ directory. Non-document stages retain the frozen 1.0 wire shape; document-scoped Draft/Review
records use backward-readable control-contract 1.1 and carry the stable document_id through result, validation,
manifest, terminal claim, promotion, state, CLI, and AgentResponse. After explicit approval to read the full advert,
the host creates candidate JSON only in a fresh scratch file and passes it to stage submit --candidate-file; the
guarded service writes the declared
candidate, TaskResult, and submission receipt without following symlink or hard-link aliases. stage apply then
rechecks task integrity, active status, input freshness, scope, hashes, schema, source receipts, and output drift
before atomically promoting parsed_job.json. Agents never write a run path or authoritative stage artifact directly.
If inputs or dependencies change while a task is active, run
canisend stage cancel --workspace . --job jobs/<job-slug> --stage <stage> [--document-id <document_...>] before
preparing a replacement; audit
records and any candidate remain available.
Confirm projects Parsed Job v1 into criteria.json with stable criterion IDs, confidence, and separate
source/confirmation states. A resolved source has one span; a missing source stays unknown, while an ambiguous source
exposes candidate spans without choosing one. It reports review_required for unconfirmed or source-unknown
criteria, orphaned corrections, and an empty extraction that has not been explicitly confirmed. An optional
confirmed_corrections.yaml is user-owned: Confirm reads it, while neither Parse nor Confirm creates, rewrites, or
deletes it. Prefer corrections status|init|update for agent-assisted writes; these accept one discriminated patch,
explicit consent, and the current revision/hash baseline. Direct manual YAML edits are still valid and must follow
schemas/confirmed-corrections.schema.json; status and stage reruns validate without normalizing or rewriting the
file. An explicitly consented scoped update creates the canonical next revision and may not preserve YAML comments.
A changed source receipt or parsed
interpretation receives a new criterion ID and leaves the old correction visible for reconciliation instead of
attaching it to a different requirement.
Evidence prepares one immutable private input at
workflow/runs/<run-id>/inputs/evidence-snapshot.json, then promotes evidence_catalog.json. The snapshot, Evidence
candidate, and promoted catalog may contain normalized profile text and deliberately duplicate it inside the ignored
private job data plane. They remain until the user removes the private run or job folder; CanISend does not claim to
erase them automatically. TaskSpec, state, receipts, manifests, errors, ordinary command output, and AgentResponse
extensions contain only privacy-safe paths, hashes, IDs, reason codes, and counts. Resumable Evidence rejects
workspace-external profile roots, path escapes, symlinks, hard-link aliases, non-regular files, and bounded-input
violations. Legacy commands may retain their existing external-profile behavior, but that does not expand the
Evidence TaskSpec v1 boundary.
Match reads only the current job-local criteria.json and evidence_catalog.json. It writes
criterion_matches.json with one deterministic strong, partial, weak, missing, or unknown proposal per
criterion, explicit privacy-safe gaps, matcher provenance, and opaque catalog references. It does not copy evidence
text, generated-evidence headings, or legacy locators. Every record has review_state=proposed: Match is not an
application decision, does not confirm applicant claims, and does not make a package ready. The user-owned Decision
is managed separately through decision status|init|update; Brief uses brief status|init|update, Review decisions
use review-dispositions status|init|update, and deterministic
Brief-stage planning writes required_document_plan.json. For a current deterministic Match using the configured
workspace profile, canisend run now projects the same proposal graph into 02_fit_report.md,
05_criteria_checklist.md, structured HR checks, and Typst package content. If the current structured Draft and
deterministic Review also validate and Review has no blocker findings, the same run projects each Cover Letter Claim
once into 03_cover_letter_draft.md, Cover Letter/package content JSON, and Typst. Complete exact dispositions make
that document reviewed; stale/drifted state, a different
parsed view, a profile override, blocked/missing Draft or Review, a direct library call without workspace provenance,
or --llm-drafts keeps the compatible fallback path. Match, Draft, and Review remain proposed; document readiness is
derived and never becomes a Decision or whole-package result.
When the Research Statement Draft and Review pass the same currentness checks and every non-blocker finding is
accepted, the run also projects each Research Claim once into conditional standalone Markdown and Typst files. The
projection binds exact Draft, Review, disposition, readiness, and Markdown hashes. It is deliberately absent from
the application-package source and check-package inputs. If a prior Research projection becomes ineligible, a
rerun replaces generated views with a body-free unavailable state; an edited Typst primary is preserved and receives
a candidate for explicit reconciliation.
Use canisend doctor --workspace . when a human-readable environment diagnostic is also useful.
A fresh Codex, Claude Code, or IDE shell session resumes from the same durable workspace state by running the same
agent context command; it does not need the previous chat transcript. The fake-data conformance fixture under
examples/agent_handoff/ demonstrates this host-neutral handoff. Parse and Draft use durable task/result exchange
through the existing CLI; Confirm, Evidence, Match, required-document planning, and Review reuse deterministic
runtime paths, while corrections, Decision, and Brief use separate user-owned mutation operations. None requires a
platform-specific API, network, or MCP transport; deterministic stages need no configured provider.
They may run local deterministic CLI commands, inspect generated evidence, and review current job artifacts. They must
ask first before reading full private CVs, statements, full job adverts, references, PDFs, source URLs, Evidence
snapshots/candidates/catalogs, criteria.json, criterion_matches.json, application_brief.yaml,
required_document_plan.json, either structured Draft/Review, package_review_findings.json, review_dispositions.yaml,
research_statement_review_dispositions.yaml, generated packages, or enabling
LLM-backed CLI flags/providers. Criteria may contain corrected wording; Match, Brief, the document plan, Draft, and
Review remain Tier 2. They must not
scrape pages, submit applications, upload packages, fabricate evidence, or commit private profile/job data.
Original profile inputs under profile/ are protected. Agents should normally produce profile-improvement suggestions inside the job folder, not rewrite the source CV or statements. A write back to profile/typst/*.typ, profile/*.md, or other non-generated profile input is allowed only through an orchestrator task that declares edits_profile_input: true, depends on a prior review task, uses privacy tier 2 or higher, and is launched with --allow-profile-input-edits --confirm-profile-input-edit --confirm-profile-input-edit-again.
For coordinated multi-CLI review, use canisend orchestrate with an explicit local YAML plan:
canisend orchestrate \
--workspace ~/CanISendWorkspace \
--job jobs/<job-slug> \
--plan orchestration.yaml \
--dry-run
Worker entries declare either kind or command. Built-in kind values are codex, claude, antigravity, agy, and custom; antigravity and agy share the same default command. Use command to override the preset for the CLI installed on your machine, and use prompt_mode to choose how the generated task prompt is delivered: stdin, arg, or none.
workers:
codex-reviewer:
kind: codex
claude-reviewer:
kind: claude
prompt_mode: arg
agy-reviewer:
kind: agy
command: "agy --print"
Worker entries also support max_parallel_tasks, supports_native_subagents, and privacy_tier_limit. Task entries declare role, inputs, outputs, writes, depends_on, privacy_tier, optional agent_count, and optional edits_profile_input for tightly controlled original profile source edits. The orchestrator runs dependency-ready tasks in parallel, enforces worker concurrency limits, writes run artifacts under jobs/<job-slug>/orchestration/runs/, and requires --allow-private-sources, --allow-provider-backed, or the profile-input edit confirmation flags for higher-risk tasks.
Privacy modes:
- Direct CLI deterministic mode can be local-only when no agent reads private content and no LLM flags/providers are used.
- Agent-assisted mode means agent-read content may enter the agent model context.
- LLM-backed CLI mode means selected context may be sent to the configured provider or local command through
stage run --stage draft --mode configured-provider --allow-provider-backedor flags such asextract-profile-evidence --llm-augment,--llm-parser, or--llm-drafts.
Detailed agent guidance lives in:
agent-skills/canisend/
SKILL.md
references/
workflow.md
job-lifecycle.md
file-contracts.md
typst-profile.md
provider-config.md
quality-gates.md
agent-orchestration.md
platforms.md
privacy.md
prompts/ contains LLM prompt files used by the application pipeline. Workspace-local agent-skills/ contains the
complete main and focused skill pack, so routes from the main skill do not depend on a global installation.
Skill Distribution
The project also ships a reusable skill pack for cases where you want a narrower agent behavior without opening a
CanISend workspace as the active project. skills/ is the canonical pack used by exports and workspace updates. The
root Codex plugin manifest at .codex-plugin/plugin.json exposes that pack, while agent-skills/canisend/ remains a
one-release compatibility mirror of the canonical main skill.
The repository-native substitute for an external sync_hermes_tap contract is
python scripts/sync_workspace_skill_mirror.py --check. CI and local release checks fail on missing, extra, or
content-drifted mirror entries; run the script without --check to safely rebuild the compatibility mirror.
Use the main canisend skill for full workspace workflows. Use canisend-job-intake for source-to-advert intake,
canisend-application-package for coordinated package construction, and canisend-submission-readiness for the final
manual-submission gate. Material-focused skills cover job fit, research and teaching statements, cover letters, CV
tailoring, humanization, application email, interview preparation, criteria checks, and material review.
The material-focused skill IDs are canisend-job-fit, canisend-research-statement,
canisend-teaching-statement, canisend-cover-letter, canisend-cv-tailoring, canisend-humanizer,
canisend-application-email, canisend-interview-prep, canisend-criteria-check, and canisend-material-review.
For a Codex marketplace repository, mount this repository as the plugin source:
git submodule add https://github.com/jxpeng98/CanISend plugins/canisend
Then add a marketplace entry that points to ./plugins/canisend.
To export skills from an installed package instead of a checkout:
canisend export-skills --target ~/plugins/canisend --kind codex-plugin
canisend export-skills --target ~/.claude/skills --kind skills-only
codex-plugin writes .codex-plugin/ plus skills/. skills-only writes only the skill folders for agents that install skills directly.
Privacy Boundaries
This repository is intended to be open source. Personal application data should stay local:
profile/is ignored by git except for.gitkeep.jobs/generated job folders are ignored by git unless selected generated materials are explicitly staged withcanisend run --git-add-materials.job_leads/feed outputs are ignored by git..env, API keys, rendered PDFs, raw job adverts, real source URLs, parsed job JSON, and profile files should not be committed.- Evidence snapshots, Evidence candidates,
evidence_catalog.json, andcriterion_matches.jsonstay inside ignored private job folders. The first three may contain duplicated normalized profile text; removing a run or job remains an explicit user retention decision. criteria.json,criterion_matches.json,confirmed_corrections.yaml,application_decision.yaml,application_brief.yaml,required_document_plan.json, both structured Draft/Review pairs,review_dispositions.yaml,research_statement_review_dispositions.yaml,package_review_findings.json,package_review_dispositions.yaml, and private mutation/stage candidates are Tier 2. Criteria may contain corrected wording; Match is body-minimized, while Brief, plan, Draft, Review, and dispositions may reveal private motivation, exclusions, source text, application strategy, or prose. Mutation receipts are Tier 1 and contain none of those bodies; neither do workflow control records, errors, ordinary command output, or AgentResponse.- User YAML may be edited manually. Status and stage reruns do not normalize it; an explicitly consented scoped update creates a canonical next revision and may not preserve comments. Revision/hash CAS coordinates cooperative CanISend writers in a stable job directory; it does not linearize a normal editor save in the final replace window or a hostile same-user rename. Run status immediately before changing a file and avoid concurrent manual saves.
- Resetting/clearing the current Decision or Brief field, removing a document choice, or withdrawing a correction is a semantic update, not disk erasure. Historical corrections and private-mode Tier 2 mutation candidates (0600 on POSIX) deliberately retain old bodies for audit and recovery. Keep job folders private and git-ignored, include them in backups only intentionally, and delete the relevant private mutation events or whole job when retention is no longer wanted. CanISend does not currently promise automatic secure erasure, including from backups or filesystem snapshots.
- Sensitive declarations such as right-to-work, visa, disability, equality monitoring, health, criminal record, and conflicts remain user-only.
这也能投只是材料准备工具,不是提交凭证。
Maintainer Release
Release automation lives in GitHub Actions for jxpeng98/CanISend.
Local checks:
uv run python -m pytest -v
uv build
uvx twine check dist/*
uv run python -m canisend.package_check dist/*.whl
CI runs the same test/build/resource-check sequence on pushes and pull requests. The release workflow uses PyPI Trusted Publishing with OIDC:
- Pushing
test/v<version>publishes to TestPyPI only. - Pushing
v<version>bNorv<version>rcNpublishes to TestPyPI, smoke-tests the TestPyPI package, then publishes a PyPI prerelease and creates a GitHub prerelease. - Pushing
v<version>publishes to TestPyPI, smoke-tests the TestPyPI package, then publishes a stable PyPI release and creates a GitHub Release. - TestPyPI and PyPI need a Trusted Publisher for
.github/workflows/release.ymlwith environments namedtestpypiandpypi.
Preferred tag-driven release orchestration:
scripts/release.sh test --version 0.3.0.dev2
scripts/release.sh beta --version 0.3.0b1
scripts/release.sh stable --version 0.3.0
The script updates pyproject.toml and src/canisend/__init__.py, runs local checks, commits the version bump, pushes the current branch, then creates and pushes the matching git tag:
git tag -a test/v0.3.0.dev2 HEAD -m "canisend 0.3.0.dev2 TestPyPI"
git tag -a v0.3.0b1 HEAD -m "canisend 0.3.0b1 prerelease"
git tag -a v0.3.0 HEAD -m "canisend 0.3.0 stable"
release.yml is the only remote publisher. It always publishes to TestPyPI first, and only promotes v* tags to
prerelease or stable PyPI after the TestPyPI publish and smoke test succeed. The release script verifies the candidate
commit on its remote branch before tagging; stable releases must be reachable from origin/main. Published tags,
including v0.2.0, are never reused.
Use test/v* with a disposable version because TestPyPI package versions cannot be overwritten.
Use RELEASE.md for the full TestPyPI and PyPI release playbook. Version updates must change both pyproject.toml and src/canisend/__init__.py.
Repository Layout
src/canisend/ CLI and application pipeline
prompts/ LLM prompt templates
templates/typst/ modernpro Typst templates
schemas/ JSON schema contracts
agent-skills/ one-release compatibility mirror of the main skill
skills/ canonical reusable and workspace-installed skill pack
.codex-plugin/ Codex plugin manifest for the skill pack
platform-bridges/ AGENTS.md and CLAUDE.md workspace bridges
examples/end_to_end/ fully local fake-data workflow
examples/agent_handoff/ host-neutral agent contract fixtures
tests/ CLI, pipeline, packaging, release, and contract tests
assets/ project logo and README media
RELEASE.md maintainer release playbook
docs/stage3-migration.md Stage 3 workspace upgrade and recovery guide
See canisend_v1_proposal.md for the original V1 engineering proposal,
docs/superpowers/specs/2026-07-09-discovery-and-workflow-v2-design.md for detailed multi-source and stage-hardening
constraints, and docs/superpowers/specs/2026-07-11-cli-first-workflow-optimization-roadmap.md for the current
delivery roadmap. The current Stage 3 execution plan is
docs/superpowers/plans/2026-07-13-evidence-backed-draft-foundation.md; the 2026-07-11 Decision Spine and 2026-07-10
Agent Runtime plans remain the accepted Stage 2 and Stage 1 records. Existing private workspaces should follow
docs/stage3-migration.md before first use with the Stage 3 prerelease.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file canisend-0.3.0b1.tar.gz.
File metadata
- Download URL: canisend-0.3.0b1.tar.gz
- Upload date:
- Size: 727.9 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
5e615e7c5df74729e1377ad29675ea524dd7b965d876cf5faf36c954637803d7
|
|
| MD5 |
f992361e31e499137cc2faacc1ff8b73
|
|
| BLAKE2b-256 |
731ca47b97a3dd9f0f3bb7fb5d1f295fce93e8d42575fcb3c229dc57ba601582
|
Provenance
The following attestation bundles were made for canisend-0.3.0b1.tar.gz:
Publisher:
release.yml on jxpeng98/CanISend
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
canisend-0.3.0b1.tar.gz -
Subject digest:
5e615e7c5df74729e1377ad29675ea524dd7b965d876cf5faf36c954637803d7 - Sigstore transparency entry: 2171353479
- Sigstore integration time:
-
Permalink:
jxpeng98/CanISend@f3a377c877e4091b250eaab48acbfd2a1d38b026 -
Branch / Tag:
refs/tags/v0.3.0b1 - Owner: https://github.com/jxpeng98
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@f3a377c877e4091b250eaab48acbfd2a1d38b026 -
Trigger Event:
push
-
Statement type:
File details
Details for the file canisend-0.3.0b1-py3-none-any.whl.
File metadata
- Download URL: canisend-0.3.0b1-py3-none-any.whl
- Upload date:
- Size: 466.4 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
10319be701e3fe125c67fd21fbfe6e492483ef9bdebcdcb29117f5068de93ecd
|
|
| MD5 |
e0f41e0fdd6e52b32b4cea7984bd4666
|
|
| BLAKE2b-256 |
efdb1117c79ab769287ef20c74ff0957eb8326d500a0fa4711a192bdda79c9a0
|
Provenance
The following attestation bundles were made for canisend-0.3.0b1-py3-none-any.whl:
Publisher:
release.yml on jxpeng98/CanISend
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
canisend-0.3.0b1-py3-none-any.whl -
Subject digest:
10319be701e3fe125c67fd21fbfe6e492483ef9bdebcdcb29117f5068de93ecd - Sigstore transparency entry: 2171353496
- Sigstore integration time:
-
Permalink:
jxpeng98/CanISend@f3a377c877e4091b250eaab48acbfd2a1d38b026 -
Branch / Tag:
refs/tags/v0.3.0b1 - Owner: https://github.com/jxpeng98
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@f3a377c877e4091b250eaab48acbfd2a1d38b026 -
Trigger Event:
push
-
Statement type: