mirror of
https://github.com/openai/codex.git
synced 2026-04-24 14:45:27 +00:00
Remove plan from system skills (#8374)
Removes plan from system skills. It has been rewritten into `create-plan` for evaluation and feedback: https://github.com/openai/skills/pull/22
This commit is contained in:
@@ -1,202 +0,0 @@
|
||||
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
@@ -1,180 +0,0 @@
|
||||
---
|
||||
name: plan
|
||||
description: Generate a plan for how an agent should accomplish a complex coding task. Use when a user asks for a plan, and optionally when they want to save, find, read, update, or delete plan files in $CODEX_HOME/plans (default ~/.codex/plans).
|
||||
metadata:
|
||||
short-description: Generate a plan for a complex task
|
||||
---
|
||||
|
||||
# Plan
|
||||
|
||||
## Overview
|
||||
|
||||
Draft structured plans that clarify intent, scope, requirements, action items, testing/validation, and risks.
|
||||
|
||||
Optionally, save plans to disk as markdown files with YAML frontmatter and free-form content. When drafting in chat, output only the plan body without frontmatter; add frontmatter only when saving to disk. Only write to the plans folder; do not modify the repository codebase.
|
||||
|
||||
This skill can also be used to draft codebase or system overviews.
|
||||
|
||||
## Core rules
|
||||
|
||||
- Resolve the plans directory as `$CODEX_HOME/plans` or `~/.codex/plans` when `CODEX_HOME` is not set.
|
||||
- Create the plans directory if it does not exist.
|
||||
- Never write to the repo; only read files to understand context.
|
||||
- Require frontmatter with **only** `name` and `description` (single-line values) for on-disk plans.
|
||||
- When presenting a draft plan in chat, omit frontmatter and start at `# Plan`.
|
||||
- Enforce naming rules: short, lower-case, hyphen-delimited; filename must equal `<name>.md`.
|
||||
- If a plan is not found, state it clearly and offer to create one.
|
||||
- Allow overview-style plans that document flows, architecture, or context without a work checklist.
|
||||
|
||||
## Decide the task
|
||||
|
||||
1. **Find/list**: discover plans by frontmatter summary; confirm if multiple matches exist.
|
||||
2. **Read/use**: validate frontmatter; present summary and full contents.
|
||||
3. **Create**: inspect repo read-only; choose plan style (implementation vs overview); draft plan; write to plans directory only.
|
||||
4. **Update**: load plan; revise content and/or description; preserve frontmatter keys; overwrite the plan file.
|
||||
5. **Delete**: confirm intent, then remove the plan file if asked.
|
||||
|
||||
## Plan discovery
|
||||
|
||||
- Prefer `scripts/list_plans.py` for quick summaries.
|
||||
- Use `scripts/read_plan_frontmatter.py` to validate a specific plan.
|
||||
- If name mismatches filename or frontmatter is missing fields, call it out and ask whether to fix.
|
||||
|
||||
## Plan creation workflow
|
||||
|
||||
1. Scan context quickly: read README.md and obvious docs (docs/, CONTRIBUTING.md, ARCHITECTURE.md); skim likely touched files; identify constraints (language, frameworks, CI/test commands, deployment).
|
||||
2. Ask follow-ups only if blocked: at most 1-2 questions, prefer multiple-choice. If unsure but not blocked, state assumptions and proceed.
|
||||
3. Identify scope, constraints, and data model/API implications (or capture existing behavior for an overview).
|
||||
4. Draft either an ordered implementation plan or a structured overview plan with diagrams/notes as needed.
|
||||
5. Immediately output the plan body only (no frontmatter), then ask the user if they want to 1. Make changes, 2. Implement it, 3. Save it as per plan.
|
||||
6. If the user wants to save it, prepend frontmatter and save the plan under the computed plans directory using `scripts/create_plan.py`.
|
||||
|
||||
|
||||
## Plan update workflow
|
||||
|
||||
- Re-read the plan and related code/docs before updating.
|
||||
- Keep the plan name stable unless the user explicitly wants a rename.
|
||||
- If renaming, update both frontmatter `name` and filename together.
|
||||
|
||||
## Scripts (low-freedom helpers)
|
||||
|
||||
Create a plan file (body only; frontmatter is written for you). Run from the plan skill directory:
|
||||
|
||||
```bash
|
||||
python ./scripts/create_plan.py \
|
||||
--name codex-rate-limit-overview \
|
||||
--description "Scope and update plan for Codex rate limiting" \
|
||||
--body-file /tmp/plan-body.md
|
||||
```
|
||||
|
||||
Read frontmatter summary for a plan (run from the plan skill directory):
|
||||
|
||||
```bash
|
||||
python ./scripts/read_plan_frontmatter.py ~/.codex/plans/codex-rate-limit-overview.md
|
||||
```
|
||||
|
||||
List plan summaries (optional filter; run from the plan skill directory):
|
||||
|
||||
```bash
|
||||
python ./scripts/list_plans.py --query "rate limit"
|
||||
```
|
||||
|
||||
## Plan file format
|
||||
|
||||
Use one of the structures below for the plan body. When drafting, output only the body (no frontmatter). When saving, prepend this frontmatter:
|
||||
|
||||
```markdown
|
||||
---
|
||||
name: <plan-name>
|
||||
description: <1-line summary>
|
||||
---
|
||||
```
|
||||
|
||||
### Implementation plan body template
|
||||
|
||||
```markdown
|
||||
# Plan
|
||||
|
||||
<1-3 sentences: intent, scope, and approach.>
|
||||
|
||||
## Requirements
|
||||
- <Requirement 1>
|
||||
- <Requirement 2>
|
||||
|
||||
## Scope
|
||||
- In:
|
||||
- Out:
|
||||
|
||||
## Files and entry points
|
||||
- <File/module/entry point 1>
|
||||
- <File/module/entry point 2>
|
||||
|
||||
## Data model / API changes
|
||||
- <If applicable, describe schema or contract changes>
|
||||
|
||||
## Action items
|
||||
[ ] <Step 1>
|
||||
[ ] <Step 2>
|
||||
[ ] <Step 3>
|
||||
[ ] <Step 4>
|
||||
[ ] <Step 5>
|
||||
[ ] <Step 6>
|
||||
|
||||
## Testing and validation
|
||||
- <Tests, commands, or validation steps>
|
||||
|
||||
## Risks and edge cases
|
||||
- <Risk 1>
|
||||
- <Risk 2>
|
||||
|
||||
## Open questions
|
||||
- <Question 1>
|
||||
- <Question 2>
|
||||
```
|
||||
|
||||
### Overview plan body template
|
||||
|
||||
```markdown
|
||||
# Plan
|
||||
|
||||
<1-3 sentences: intent and scope of the overview.>
|
||||
|
||||
## Overview
|
||||
<Describe the system, flow, or architecture at a high level.>
|
||||
|
||||
## Diagrams
|
||||
<Include text or Mermaid diagrams if helpful.>
|
||||
|
||||
## Key file references
|
||||
- <File/module/entry point 1>
|
||||
- <File/module/entry point 2>
|
||||
|
||||
## Auth / routing / behavior notes
|
||||
- <Capture relevant differences (e.g., auth modes, routing paths).>
|
||||
|
||||
## Current status
|
||||
- <What is live today vs pending work, if known.>
|
||||
|
||||
## Action items
|
||||
- None (overview only).
|
||||
|
||||
## Testing and validation
|
||||
- None (overview only).
|
||||
|
||||
## Risks and edge cases
|
||||
- None (overview only).
|
||||
|
||||
## Open questions
|
||||
- None.
|
||||
```
|
||||
|
||||
## Writing guidance
|
||||
|
||||
- Start with 1 short paragraph describing intent and approach.
|
||||
- Keep action items ordered and atomic (discovery -> changes -> tests -> rollout); use verb-first phrasing.
|
||||
- Scale action item count to complexity (simple: 1-2; complex: up to about 10).
|
||||
- Include file/entry-point hints and concrete validation steps where useful.
|
||||
- Always include testing/validation and risks/edge cases in implementation plans; include safe rollout/rollback when relevant.
|
||||
- Use open questions only when necessary (max 3).
|
||||
- Avoid vague steps, micro-steps, and code snippets; keep the plan implementation-agnostic.
|
||||
- For overview plans, keep action items minimal and set non-applicable sections to "None."
|
||||
@@ -1,114 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Create or overwrite a plan markdown file in $CODEX_HOME/plans."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
from plan_utils import get_plans_dir, validate_plan_name
|
||||
|
||||
DEFAULT_TEMPLATE = """# Plan
|
||||
|
||||
<1-3 sentences: intent, scope, and approach.>
|
||||
|
||||
## Requirements
|
||||
- <Requirement 1>
|
||||
- <Requirement 2>
|
||||
|
||||
## Scope
|
||||
- In:
|
||||
- Out:
|
||||
|
||||
## Files and entry points
|
||||
- <File/module/entry point 1>
|
||||
- <File/module/entry point 2>
|
||||
|
||||
## Data model / API changes
|
||||
- <If applicable, describe schema or contract changes>
|
||||
|
||||
## Action items
|
||||
[ ] <Step 1>
|
||||
[ ] <Step 2>
|
||||
[ ] <Step 3>
|
||||
[ ] <Step 4>
|
||||
[ ] <Step 5>
|
||||
[ ] <Step 6>
|
||||
|
||||
## Testing and validation
|
||||
- <Tests, commands, or validation steps>
|
||||
|
||||
## Risks and edge cases
|
||||
- <Risk 1>
|
||||
- <Risk 2>
|
||||
|
||||
## Open questions
|
||||
- <Question 1>
|
||||
- <Question 2>
|
||||
"""
|
||||
|
||||
|
||||
def read_body(args: argparse.Namespace) -> str | None:
|
||||
if args.template:
|
||||
return DEFAULT_TEMPLATE
|
||||
if args.body_file:
|
||||
return Path(args.body_file).read_text(encoding="utf-8")
|
||||
if not sys.stdin.isatty():
|
||||
return sys.stdin.read()
|
||||
return None
|
||||
|
||||
|
||||
def main() -> int:
|
||||
parser = argparse.ArgumentParser(
|
||||
description="Create a plan file under $CODEX_HOME/plans or ~/.codex/plans."
|
||||
)
|
||||
parser.add_argument("--name", required=True, help="Plan name (lower-case, hyphen-delimited).")
|
||||
parser.add_argument("--description", required=True, help="Short plan description.")
|
||||
parser.add_argument(
|
||||
"--body-file",
|
||||
help="Path to markdown body (without frontmatter). If omitted, read from stdin.",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--template",
|
||||
action="store_true",
|
||||
help="Write a template body instead of reading from stdin or --body-file.",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--overwrite",
|
||||
action="store_true",
|
||||
help="Overwrite the plan file if it already exists.",
|
||||
)
|
||||
args = parser.parse_args()
|
||||
|
||||
name = args.name.strip()
|
||||
description = args.description.strip()
|
||||
validate_plan_name(name)
|
||||
if not description or "\n" in description:
|
||||
raise SystemExit("Description must be a single line.")
|
||||
|
||||
body = read_body(args)
|
||||
if body is None:
|
||||
raise SystemExit("Provide --body-file, stdin, or --template to supply plan content.")
|
||||
|
||||
body = body.strip()
|
||||
if not body:
|
||||
raise SystemExit("Plan body cannot be empty.")
|
||||
if body.lstrip().startswith("---"):
|
||||
raise SystemExit("Plan body should not include frontmatter.")
|
||||
|
||||
plans_dir = get_plans_dir()
|
||||
plans_dir.mkdir(parents=True, exist_ok=True)
|
||||
plan_path = plans_dir / f"{name}.md"
|
||||
|
||||
if plan_path.exists() and not args.overwrite:
|
||||
raise SystemExit(f"Plan already exists: {plan_path}. Use --overwrite to replace.")
|
||||
|
||||
content = f"---\nname: {name}\ndescription: {description}\n---\n\n{body}\n"
|
||||
plan_path.write_text(content, encoding="utf-8")
|
||||
print(str(plan_path))
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
@@ -1,49 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
"""List plan summaries by reading frontmatter only."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import json
|
||||
|
||||
from plan_utils import get_plans_dir, parse_frontmatter
|
||||
|
||||
|
||||
def main() -> int:
|
||||
parser = argparse.ArgumentParser(description="List plan summaries from $CODEX_HOME/plans.")
|
||||
parser.add_argument("--query", help="Case-insensitive substring to filter name/description.")
|
||||
parser.add_argument("--json", action="store_true", help="Emit JSON output.")
|
||||
args = parser.parse_args()
|
||||
|
||||
plans_dir = get_plans_dir()
|
||||
if not plans_dir.exists():
|
||||
raise SystemExit(f"Plans directory not found: {plans_dir}")
|
||||
|
||||
query = args.query.lower() if args.query else None
|
||||
items = []
|
||||
for path in sorted(plans_dir.glob("*.md")):
|
||||
try:
|
||||
data = parse_frontmatter(path)
|
||||
except ValueError:
|
||||
continue
|
||||
name = data.get("name")
|
||||
description = data.get("description")
|
||||
if not name or not description:
|
||||
continue
|
||||
if query:
|
||||
haystack = f"{name} {description}".lower()
|
||||
if query not in haystack:
|
||||
continue
|
||||
items.append({"name": name, "description": description, "path": str(path)})
|
||||
|
||||
if args.json:
|
||||
print(json.dumps(items))
|
||||
else:
|
||||
for item in items:
|
||||
print(f"{item['name']}\t{item['description']}\t{item['path']}")
|
||||
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
@@ -1,53 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Shared helpers for plan scripts."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
import re
|
||||
from pathlib import Path
|
||||
|
||||
_NAME_RE = re.compile(r"^[a-z0-9]+(-[a-z0-9]+)*$")
|
||||
|
||||
|
||||
def get_codex_home() -> Path:
|
||||
"""Return CODEX_HOME if set, else ~/.codex."""
|
||||
return Path(os.environ.get("CODEX_HOME", "~/.codex")).expanduser()
|
||||
|
||||
|
||||
def get_plans_dir() -> Path:
|
||||
return get_codex_home() / "plans"
|
||||
|
||||
|
||||
def validate_plan_name(name: str) -> None:
|
||||
if not name or not _NAME_RE.match(name):
|
||||
raise ValueError(
|
||||
"Invalid plan name. Use short, lower-case, hyphen-delimited names "
|
||||
"(e.g., codex-rate-limit-overview)."
|
||||
)
|
||||
|
||||
|
||||
def parse_frontmatter(path: Path) -> dict:
|
||||
"""Parse YAML frontmatter from a markdown file without reading the body."""
|
||||
with path.open("r", encoding="utf-8") as handle:
|
||||
first = handle.readline()
|
||||
if first.strip() != "---":
|
||||
raise ValueError("Frontmatter must start with '---'.")
|
||||
|
||||
data: dict[str, str] = {}
|
||||
for line in handle:
|
||||
stripped = line.strip()
|
||||
if stripped == "---":
|
||||
return data
|
||||
if not stripped or stripped.startswith("#"):
|
||||
continue
|
||||
if ":" not in line:
|
||||
raise ValueError(f"Invalid frontmatter line: {line.rstrip()}")
|
||||
key, value = line.split(":", 1)
|
||||
key = key.strip()
|
||||
value = value.strip()
|
||||
if value and len(value) >= 2 and value[0] == value[-1] and value[0] in ('"', "'"):
|
||||
value = value[1:-1]
|
||||
data[key] = value
|
||||
|
||||
raise ValueError("Frontmatter must end with '---'.")
|
||||
@@ -1,41 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Read plan frontmatter without loading the full markdown body."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import json
|
||||
from pathlib import Path
|
||||
|
||||
from plan_utils import parse_frontmatter
|
||||
|
||||
|
||||
def main() -> int:
|
||||
parser = argparse.ArgumentParser(description="Read name/description from plan frontmatter.")
|
||||
parser.add_argument("plan_path", help="Path to the plan markdown file.")
|
||||
parser.add_argument("--json", action="store_true", help="Emit JSON output.")
|
||||
args = parser.parse_args()
|
||||
|
||||
path = Path(args.plan_path).expanduser()
|
||||
if not path.exists():
|
||||
raise SystemExit(f"Plan not found: {path}")
|
||||
|
||||
data = parse_frontmatter(path)
|
||||
name = data.get("name")
|
||||
description = data.get("description")
|
||||
if not name or not description:
|
||||
raise SystemExit("Frontmatter must include name and description.")
|
||||
|
||||
payload = {"name": name, "description": description, "path": str(path)}
|
||||
if args.json:
|
||||
print(json.dumps(payload))
|
||||
else:
|
||||
print(f"name: {name}")
|
||||
print(f"description: {description}")
|
||||
print(f"path: {path}")
|
||||
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
@@ -27,6 +27,14 @@ fn write_skill(home: &Path, name: &str, description: &str, body: &str) -> std::p
|
||||
path
|
||||
}
|
||||
|
||||
fn system_skill_md_path(home: impl AsRef<Path>, name: &str) -> std::path::PathBuf {
|
||||
home.as_ref()
|
||||
.join("skills")
|
||||
.join(".system")
|
||||
.join(name)
|
||||
.join("SKILL.md")
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn user_turn_includes_skill_instructions() -> Result<()> {
|
||||
skip_if_no_network!(Ok(()));
|
||||
@@ -158,13 +166,15 @@ async fn skill_load_errors_surface_in_session_configured() -> Result<()> {
|
||||
async fn list_skills_includes_system_cache_entries() -> Result<()> {
|
||||
skip_if_no_network!(Ok(()));
|
||||
|
||||
const SYSTEM_SKILL_NAME: &str = "skill-creator";
|
||||
|
||||
let server = start_mock_server().await;
|
||||
let mut builder = test_codex()
|
||||
.with_config(|config| {
|
||||
config.features.enable(Feature::Skills);
|
||||
})
|
||||
.with_pre_build_hook(|home| {
|
||||
let system_skill_path = home.join("skills/.system/plan/SKILL.md");
|
||||
let system_skill_path = system_skill_md_path(home, SYSTEM_SKILL_NAME);
|
||||
assert!(
|
||||
!system_skill_path.exists(),
|
||||
"expected embedded system skills not yet installed, but {system_skill_path:?} exists"
|
||||
@@ -172,14 +182,15 @@ async fn list_skills_includes_system_cache_entries() -> Result<()> {
|
||||
});
|
||||
let test = builder.build(&server).await?;
|
||||
|
||||
let system_skill_path = test.codex_home_path().join("skills/.system/plan/SKILL.md");
|
||||
let system_skill_path = system_skill_md_path(test.codex_home_path(), SYSTEM_SKILL_NAME);
|
||||
assert!(
|
||||
system_skill_path.exists(),
|
||||
"expected embedded system skills installed to {system_skill_path:?}"
|
||||
);
|
||||
let system_skill_contents = fs::read_to_string(&system_skill_path)?;
|
||||
let expected_name_line = format!("name: {SYSTEM_SKILL_NAME}");
|
||||
assert!(
|
||||
system_skill_contents.contains("name: plan"),
|
||||
system_skill_contents.contains(&expected_name_line),
|
||||
"expected embedded system skill file, got:\n{system_skill_contents}"
|
||||
);
|
||||
|
||||
@@ -206,12 +217,13 @@ async fn list_skills_includes_system_cache_entries() -> Result<()> {
|
||||
|
||||
let skill = skills
|
||||
.iter()
|
||||
.find(|skill| skill.name == "plan")
|
||||
.find(|skill| skill.name == SYSTEM_SKILL_NAME)
|
||||
.expect("expected system skill to be present");
|
||||
assert_eq!(skill.scope, codex_protocol::protocol::SkillScope::System);
|
||||
let path_str = skill.path.to_string_lossy().replace('\\', "/");
|
||||
let expected_path_suffix = format!("/skills/.system/{SYSTEM_SKILL_NAME}/SKILL.md");
|
||||
assert!(
|
||||
path_str.ends_with("/skills/.system/plan/SKILL.md"),
|
||||
path_str.ends_with(&expected_path_suffix),
|
||||
"unexpected skill path: {path_str}"
|
||||
);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user