Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .claude-plugin/marketplace.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
},
"metadata": {
"description": "Content-readiness lint with evidence-bounded AI discovery experiments",
"version": "0.6.0"
"version": "0.6.1"
},
"plugins": [
{
Expand Down
2 changes: 1 addition & 1 deletion .claude-plugin/plugin.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "aeoptimize",
"description": "Content-readiness lint and evidence-bounded discovery experiments",
"version": "0.6.0",
"version": "0.6.1",
"author": {
"name": "Te-Shu Wang"
},
Expand Down
12 changes: 6 additions & 6 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ jobs:
with:
path: .github/fixtures/action-low
min-score: '100'
package-spec: ./aeoptimize-0.6.0.tgz
package-spec: ./aeoptimize-0.6.1.tgz

- name: Validate Action outputs
env:
Expand All @@ -101,7 +101,7 @@ jobs:
uses: ./
with:
path: examples/github-action-sample/site
package-spec: ./aeoptimize-0.6.0.tgz
package-spec: ./aeoptimize-0.6.1.tgz

- name: Validate sample outputs
env:
Expand All @@ -120,7 +120,7 @@ jobs:
path: .github/fixtures/action-low
min-score: '0'
fail-on-low-score: 'true'
package-spec: ./aeoptimize-0.6.0.tgz
package-spec: ./aeoptimize-0.6.1.tgz

- name: Validate blocking outputs
env:
Expand All @@ -140,7 +140,7 @@ jobs:
path: .github/fixtures/action-low
min-score: '100'
fail-on-low-score: 'true'
package-spec: ./aeoptimize-0.6.0.tgz
package-spec: ./aeoptimize-0.6.1.tgz

- name: Invalid choice is rejected
id: invalid-choice
Expand All @@ -149,7 +149,7 @@ jobs:
with:
path: .github/fixtures/action-low
fail-on-low-score: sometimes
package-spec: ./aeoptimize-0.6.0.tgz
package-spec: ./aeoptimize-0.6.1.tgz

- name: Out-of-range threshold is rejected
id: invalid-threshold
Expand All @@ -158,7 +158,7 @@ jobs:
with:
path: .github/fixtures/action-low
min-score: '101'
package-spec: ./aeoptimize-0.6.0.tgz
package-spec: ./aeoptimize-0.6.1.tgz

- name: Assert expected failures
env:
Expand Down
2 changes: 1 addition & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

All notable user-visible changes will be documented here. The project follows Semantic Versioning after the v0.6 evidence baseline is released.

## Unreleased
## 0.6.1

### Fixed

Expand Down
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -64,15 +64,15 @@ node -e "const r=require('./aeoptimize-report.json'); process.exit(r.overall.tot
The v0.6 GitHub Action is advisory by default. It reports findings without blocking the workflow:

```yaml
- uses: cucuwang/aeoptimize@v0.6.0
- uses: cucuwang/aeoptimize@v0.6.1
with:
path: dist
```

Projects can explicitly choose blocking mode after accepting a baseline:

```yaml
- uses: cucuwang/aeoptimize@v0.6.0
- uses: cucuwang/aeoptimize@v0.6.1
with:
path: dist
fail-on-low-score: 'true'
Expand Down
2 changes: 1 addition & 1 deletion action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ inputs:
package-spec:
description: npm package spec to install; keep the default outside prerelease testing
required: false
default: 'aeoptimize@0.6.0'
default: 'aeoptimize@0.6.1'

outputs:
score:
Expand Down
2 changes: 1 addition & 1 deletion action/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ inputs:
package-spec:
description: npm package spec to install; keep the default outside prerelease testing
required: false
default: 'aeoptimize@0.6.0'
default: 'aeoptimize@0.6.1'

outputs:
score:
Expand Down
40 changes: 16 additions & 24 deletions docs/release-v0.6.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# v0.6 release and rollback guide

Version 0.6.0 establishes the evidence-bounded scoring, packaging, and GitHub Action contracts. It is not released until npm, the Git tag, and the GitHub Release are each created and read back independently.
Version 0.6.1 hardens the v0.6 evidence-bounded scoring, packaging, and GitHub Action contracts. It is not released until npm, the Git tag, and the GitHub Release are each created and read back independently.

## Release acceptance

Expand All @@ -19,40 +19,32 @@ Publishing, tagging, creating a GitHub Release, changing npm dist-tags, and depr

## Release notes

### Evidence-bounded readiness scoring
### Artifact-bound release verification

- Reframe the score as deterministic content readiness rather than a prediction of ranking or AI citation.
- Treat FAQ structure and `llms.txt` as optional, zero-point signals.
- Remove exact-one-H1 and fixed meta-description-length assumptions.
- Flag unsourced quantitative claims instead of rewarding more numbers.
- Publish a versioned fixture corpus with positive, negative, and false-positive boundaries for every scored rule.
- Build the candidate from a clean worktree and install all three CLI aliases from the exact tarball whose SHA-256 is recorded.
- Require byte-identical candidate manifests from Node.js 22 and 24 before the release can proceed.
- Bind the public verifier's CLI smoke checks to the downloaded, hash-verified npm tarball.
- Fail closed when a CLI alias, tag target, repository identity, or GitHub Release state does not match.
- Exercise every v0.6 rule fixture through the real HTML parser boundary.

### Reproducible automation

- Support maintained Node.js 22 and 24 lines.
- Keep `aeoptimize`, `aeo`, and `aeo-cli` in the packed npm manifest.
- Add an advisory-by-default GitHub Action with explicit blocking mode and stable JSON outputs.
- Add a copyable end-to-end Action sample and release-time contract checks.
- Refresh dependencies and require zero high or critical audit findings at release time.

No ranking, traffic, indexing, rich-result, AI Overview, or citation outcome is claimed by this release.
No scoring rule, rule weight, JSON field, Action input, or Action output changes in this patch. No ranking, traffic, indexing, rich-result, AI Overview, or citation outcome is claimed by this release.

## Publication readback

After an authorized npm publication:

```bash
npm view aeoptimize version dist-tags --json
npm view aeoptimize@0.6.0 version gitHead repository homepage bugs dist --json
consumer_root=$(mktemp -d "${TMPDIR:-/tmp}/aeoptimize-v0.6-consumer.XXXXXX")
npm install --prefix "$consumer_root" aeoptimize@0.6.0
npm view aeoptimize@0.6.1 version gitHead repository homepage bugs dist --json
consumer_root=$(mktemp -d "${TMPDIR:-/tmp}/aeoptimize-v0.6.1-consumer.XXXXXX")
npm install --prefix "$consumer_root" aeoptimize@0.6.1
"$consumer_root/node_modules/.bin/aeoptimize" --version
"$consumer_root/node_modules/.bin/aeo" --version
"$consumer_root/node_modules/.bin/aeo-cli" --version
rm -rf -- "$consumer_root"
```

After separately authorized tag and GitHub Release creation, verify that `v0.6.0` points to the tested release commit and that the Release is published rather than draft or prerelease.
After separately authorized tag and GitHub Release creation, verify that `v0.6.1` points to the tested release commit and that the Release is published rather than draft or prerelease.

The fail-closed public verifier checks npm `latest`, the exact version, public repository identity, the downloaded tarball SHA-256, all three installed CLI aliases, the tag target, and the published GitHub Release. The tarball hash is the required artifact-identity gate. If npm exposes `gitHead`, it must match the expected release commit; absence is reported as informational because npm's publish contract guarantees tarball integrity but does not guarantee that metadata field.

Expand All @@ -64,11 +56,11 @@ bash scripts/verify-release-v0.6.sh <verified-release-commit> <verified-package-

An npm dist-tag rollback changes what `npm install aeoptimize` selects; it does not remove exact-version installs. Never silently move an existing Git tag to different code.

If npm 0.6.0 is unsuitable before a corrective release is available, request separate authorization for each mutation, then:
If npm 0.6.1 is unsuitable before a corrective release is available, request separate authorization for each mutation, then:

```bash
npm dist-tag add aeoptimize@0.5.3 latest
npm deprecate aeoptimize@0.6.0 "Use 0.5.3 until the corrective release is available."
npm dist-tag add aeoptimize@0.6.0 latest
npm deprecate aeoptimize@0.6.1 "Use 0.6.0 until the corrective release is available."
```

Mark the GitHub Release with the same warning. Preserve the `v0.6.0` tag as evidence of what was published, fix forward in 0.6.1, rerun the complete release acceptance suite, and only then move npm `latest` to the corrective version.
Mark the GitHub Release with the same warning. Preserve the `v0.6.1` tag as evidence of what was published, fix forward in 0.6.2, rerun the complete release acceptance suite, and only then move npm `latest` to the corrective version.
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: cucuwang/aeoptimize@v0.6.0
- uses: cucuwang/aeoptimize@v0.6.1
with:
path: site
fail-on-low-score: 'false'
2 changes: 1 addition & 1 deletion examples/github-action-sample/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,4 @@ This directory is a copyable end-to-end sample for the v0.6 Action contract.
- `site/index.html` is a deterministic public input.
- The Action is advisory by default. The sample does not block a pull request on an unreviewed score threshold.

The workflow becomes reproducible only after both `aeoptimize@0.6.0` exists on npm and the immutable `v0.6.0` Git tag points to the matching release commit. Until both artifacts exist, use the local CLI or the release-candidate package during controlled verification.
The workflow becomes reproducible only after both `aeoptimize@0.6.1` exists on npm and the immutable `v0.6.1` Git tag points to the matching release commit. Until both artifacts exist, use the local CLI or the release-candidate package during controlled verification.
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "aeoptimize",
"version": "0.6.0",
"version": "0.6.1",
"description": "Deterministic content-readiness lint for static websites and documentation",
"type": "module",
"main": "./dist/core/index.js",
Expand Down
14 changes: 11 additions & 3 deletions scripts/verify-release-v0.6.sh
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,22 @@
set -u

PACKAGE_NAME=aeoptimize
EXPECTED_VERSION=0.6.0
EXPECTED_TAG=v0.6.0
REPOSITORY=cucuwang/aeoptimize
EXPECTED_COMMIT=${1:-}
EXPECTED_PACKAGE_SHA256=${2:-}
EXPECTED_REPOSITORY_URL=git+https://github.com/cucuwang/aeoptimize.git
EXPECTED_HOMEPAGE=https://github.com/cucuwang/aeoptimize
EXPECTED_BUGS_URL=https://github.com/cucuwang/aeoptimize/issues
SCRIPT_DIR=$(cd "$(dirname "$0")" && pwd)
PACKAGE_JSON="$SCRIPT_DIR/../package.json"

if ! command -v node >/dev/null 2>&1; then
echo "missing required command: node" >&2
exit 2
fi

EXPECTED_VERSION=$(node -e "const fs=require('node:fs');const packageJson=JSON.parse(fs.readFileSync(process.argv[1],'utf8'));process.stdout.write(packageJson.version)" "$PACKAGE_JSON")
EXPECTED_TAG="v$EXPECTED_VERSION"

if [ -z "$EXPECTED_COMMIT" ] || [ -z "$EXPECTED_PACKAGE_SHA256" ]; then
echo "usage: $0 <expected-release-commit> <expected-package-sha256>" >&2
Expand All @@ -26,7 +34,7 @@ if ! [[ "$EXPECTED_PACKAGE_SHA256" =~ ^[0-9a-f]{64}$ ]]; then
exit 2
fi

for command_name in awk curl jq npm git mktemp node; do
for command_name in awk curl jq npm git mktemp; do
if ! command -v "$command_name" >/dev/null 2>&1; then
echo "missing required command: $command_name" >&2
exit 2
Expand Down
2 changes: 1 addition & 1 deletion src/cli/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ const program = new Command();
program
.name('aeoptimize')
.description('Deterministic content-readiness lint for websites and documentation')
.version('0.6.0');
.version('0.6.1');

// ── scan command ───────────────────────────────────────────────────

Expand Down
2 changes: 1 addition & 1 deletion src/core/__tests__/evidence-boundaries.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ describe('public metadata', () => {
const action = await readFile(join(root, 'action.yml'), 'utf8');
const compatibilityAction = await readFile(join(root, 'action/action.yml'), 'utf8');

expect(packageJson.version).toBe('0.6.0');
expect(packageJson.version).toBe('0.6.1');
expect(pluginJson.version).toBe(packageJson.version);
expect(marketplaceJson.metadata.version).toBe(packageJson.version);
expect(cli).toContain(`.version('${packageJson.version}')`);
Expand Down
8 changes: 4 additions & 4 deletions src/core/__tests__/release-contract.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -122,16 +122,16 @@ describe('v0.6 public rule fixture corpus', () => {
});
}

it('keeps the corpus, package, methodology, and Action sample on the same release version', async () => {
it('keeps the v0.6 scoring contract and Action sample aligned with the package release line', async () => {
const packageJson = JSON.parse(await readFile(join(repositoryRoot, 'package.json'), 'utf8'));
const methodology = await readFile(join(repositoryRoot, 'docs/methodology.md'), 'utf8');
const sampleWorkflow = await readFile(
join(repositoryRoot, 'examples/github-action-sample/.github/workflows/aeoptimize.yml'),
'utf8',
);

expect(ruleFixtureCorpusVersion).toBe(packageJson.version);
expect(methodology).toContain(`v${packageJson.version} scoring contract`);
expect(packageJson.version.split('.').slice(0, 2)).toEqual(ruleFixtureCorpusVersion.split('.').slice(0, 2));
expect(methodology).toContain(`v${ruleFixtureCorpusVersion} scoring contract`);
expect(sampleWorkflow).toContain(`uses: cucuwang/aeoptimize@v${packageJson.version}`);
expect(sampleWorkflow).toContain('permissions:\n contents: read');
expect(sampleWorkflow).toContain('path: site');
Expand Down Expand Up @@ -189,7 +189,7 @@ describe('v0.6 JSON automation contract', () => {
expect(packageJson.scripts['release:check']).toBe('bash scripts/verify-release-candidate.sh');
expect(packageJson.scripts.prepublishOnly).toBe('npm run release:check');
expect(releaseGuide).toContain('## Rollback');
expect(releaseGuide).toContain('npm dist-tag add aeoptimize@0.5.3 latest');
expect(releaseGuide).toContain('npm dist-tag add aeoptimize@0.6.0 latest');
expect(releaseGuide).toContain('<verified-package-sha256>');
expect(publicVerifier).toContain('.gitHead');
expect(publicVerifier).toContain('EXPECTED_REPOSITORY_URL');
Expand Down
22 changes: 11 additions & 11 deletions src/core/__tests__/release-verifier.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ const testDirectory = dirname(fileURLToPath(import.meta.url));
const repositoryRoot = join(testDirectory, '../../..');
const verifier = join(repositoryRoot, 'scripts/verify-release-v0.6.sh');
const expectedCommit = '0123456789abcdef0123456789abcdef01234567';
const tarballContent = 'verified aeoptimize v0.6.0 candidate';
const tarballContent = 'verified aeoptimize v0.6.1 candidate';
const expectedTarballHash = createHash('sha256').update(tarballContent).digest('hex');

interface CommandResult {
Expand All @@ -29,7 +29,7 @@ function runVerifier(
env: {
...process.env,
PATH: `${mockBin}:${process.env.PATH}`,
MOCK_LATEST: '0.6.0',
MOCK_LATEST: '0.6.1',
MOCK_NPM_GIT_HEAD: expectedCommit,
MOCK_REPOSITORY_URL: 'git+https://github.com/cucuwang/aeoptimize.git',
MOCK_TAG_COMMIT: expectedCommit,
Expand Down Expand Up @@ -80,13 +80,13 @@ done

case "$url" in
https://registry.npmjs.org/aeoptimize)
printf '{"dist-tags":{"latest":"%s"},"versions":{"0.6.0":{"gitHead":"%s","repository":{"url":"%s"},"homepage":"https://github.com/cucuwang/aeoptimize","bugs":{"url":"https://github.com/cucuwang/aeoptimize/issues"},"dist":{"tarball":"https://registry.npmjs.org/aeoptimize/-/aeoptimize-0.6.0.tgz"}}}}' "$MOCK_LATEST" "$MOCK_NPM_GIT_HEAD" "$MOCK_REPOSITORY_URL"
printf '{"dist-tags":{"latest":"%s"},"versions":{"0.6.1":{"gitHead":"%s","repository":{"url":"%s"},"homepage":"https://github.com/cucuwang/aeoptimize","bugs":{"url":"https://github.com/cucuwang/aeoptimize/issues"},"dist":{"tarball":"https://registry.npmjs.org/aeoptimize/-/aeoptimize-0.6.1.tgz"}}}}' "$MOCK_LATEST" "$MOCK_NPM_GIT_HEAD" "$MOCK_REPOSITORY_URL"
;;
https://registry.npmjs.org/aeoptimize/-/aeoptimize-0.6.0.tgz)
https://registry.npmjs.org/aeoptimize/-/aeoptimize-0.6.1.tgz)
printf '%s' "$MOCK_TARBALL_CONTENT" > "$output_file"
;;
https://api.github.com/repos/cucuwang/aeoptimize/releases/tags/v0.6.0)
printf '{"tag_name":"v0.6.0","draft":%s,"prerelease":%s}' "$MOCK_RELEASE_DRAFT" "$MOCK_RELEASE_PRERELEASE" > "$output_file"
https://api.github.com/repos/cucuwang/aeoptimize/releases/tags/v0.6.1)
printf '{"tag_name":"v0.6.1","draft":%s,"prerelease":%s}' "$MOCK_RELEASE_DRAFT" "$MOCK_RELEASE_PRERELEASE" > "$output_file"
printf '200'
;;
*)
Expand All @@ -98,7 +98,7 @@ esac

await writeExecutable(join(mockBin, 'git'), `#!/usr/bin/env bash
set -euo pipefail
printf '%s\trefs/tags/v0.6.0\n' "$MOCK_TAG_COMMIT"
printf '%s\trefs/tags/v0.6.1\n' "$MOCK_TAG_COMMIT"
`);

await writeExecutable(join(mockBin, 'npm'), `#!/usr/bin/env bash
Expand All @@ -116,7 +116,7 @@ for binary in aeoptimize aeo aeo-cli; do
if [ "$binary" = "$MOCK_MISSING_BINARY" ]; then
continue
fi
printf '#!/usr/bin/env bash\nprintf "0.6.0\\n"\n' > "$prefix/node_modules/.bin/$binary"
printf '#!/usr/bin/env bash\nprintf "0.6.1\\n"\n' > "$prefix/node_modules/.bin/$binary"
chmod +x "$prefix/node_modules/.bin/$binary"
done
`);
Expand All @@ -135,8 +135,8 @@ done
expect(result.stdout).toContain('PASS: npm gitHead matches');
expect(result.stdout).toContain('PASS: npm tarball SHA-256 matches the verified candidate');
expect(result.stdout).toContain('All public release checks passed.');
expect(npmArgs).toMatch(/aeoptimize-0\.6\.0\.tgz/);
expect(npmArgs).not.toContain('aeoptimize@0.6.0');
expect(npmArgs).toMatch(/aeoptimize-0\.6\.1\.tgz/);
expect(npmArgs).not.toContain('aeoptimize@0.6.1');
});

it('fails closed when npm serves a different tarball', async () => {
Expand Down Expand Up @@ -177,7 +177,7 @@ done
});

expect(result.code).toBe(1);
expect(result.stderr).toContain('FAIL: v0.6.0 points to');
expect(result.stderr).toContain('FAIL: v0.6.1 points to');
});

it('fails closed when the GitHub Release is a draft', async () => {
Expand Down
Loading